Handle Dependency in NServiceBus

I was using poor-man’s injection in NServiceBus until this morning I figured out how to handle dependency in NServiceBus. It’s easy, but I never got chance or brave enough to try it out

NServiceBus uses SpringBuilder as default. I want to stick with StructureMap.

Here is official doc for how to wire StructureMap up with NServiceBus: http://sourceforge.net/apps/mediawiki/nservicebus/index.php?title=Additional_containers

    // Default
//    public class EndpointConfig : IConfigureThisEndpoint, AsA_Server
//    {
//    }
    public class EndpointConfigUsingStructuremap : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
    {
        #region IWantCustomInitialization Members

        public void Init()
        {
            Configure.With()
                .StructureMapBuilder(new Container(new DependencyRegistry()))
                .XmlSerializer();

            // you can leave the rest of the config off since "AsA_Server" handles it
        }

        #endregion
    }

    public class DependencyRegistry : Registry
    {
        public DependencyRegistry()
        {
            // Manually set
//            For<IFileService>().Use<FileService>();

            // Or auto scan
            Scan(x =>
            {
                x.TheCallingAssembly();
                x.AssemblyContainingType<IFileService>();
                x.WithDefaultConventions();
            });

        }

    }

 public class DbChangeHandler : IMessageHandler<DatabaseChangedCommand>
    {
        private readonly IFileService _fileService;

        public DbChangeHandler(IFileService fileService)
        {
            _fileService = fileService;
        }

        public void Handle(DatabaseChangedCommand message)
        {
         ...
// My old syntax, poor-man/static injection.
//            FileService.TouchFile(message.DatabaseName, path);
            _fileService.TouchFile(message.DatabaseName, path);
        }
    }

Amazing!

Another team from our company is using Ninject which is not in the out-of-box container list supported by NServiceBus (yet) , google found a solution by Aaron Jenson, https://github.com/aaronjensen/nservicebus.objectbuilder.ninject I will get there soon.

Updated on Mar. 11, 2011:

This code from https://gist.github.com/326321 works in Ninject 2.2 and NServiceBus 2.


public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
    {
        #region IWantCustomInitialization Members

        public void Init()
        {
            Configure.With()
                .Log4Net<ConsoleAppender>(x => { x.Threshold = Level.Info; }
                ).Log4Net<FileAppender>(x =>
                {
                    x.File = "build_trigger_log.txt";
                    x.Threshold = Level.Info;
                }
                )
                .NinjectBuilder()
                .XmlSerializer();

            // you can leave the rest of the config off since "AsA_Server" handles it
        }

        #endregion
    }

internal class CustomModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IFileService>().To<FileService>();
        }
    }

// NinjectObjectBuilder is from https://gist.github.com/326321
    public static class ConfigureNinjectBuilder
    {
        public static Configure NinjectBuilder(this Configure config)
        {
            ConfigureCommon.With(config, new NinjectObjectBuilder(new StandardKernel(new CustomModule())));
            return config;
        }
    }

Note:

  1. If using NServiceBus Testing framework, constructor inject might not work, should use setter inject instead.
  2. When using autobinding in Ninject, there is a conflict in IMessageSerliazer class, my quick workaround is providing the sample type instead of ScanAssemblyMatch(‘*”), or use Scan WithoutNamespaceOf(“NServiceBus”) to allow NSB use it’s own autobinding on Msmq stuff.
  3. NSB supports both ctor injection and setter injection, but NSB.Testing framework need a default no-arg ctor, so setter injection is the only choice if using NSB.Testing.

 [TestFixture]
    public class DbChangeHandlerTest
    {
        [SetUp]
        public void Setup()
        {
            mockFileService = MockRepository.GenerateMock<IFileService>();

            Test.Initialize();

            systemUnderTest = Test.Handler<DbChangeHandler>()
                .WithExternalDependencies(h => h.file_service = mockFileService);
        }

        IFileService mockFileService;
        Handler<DbChangeHandler> systemUnderTest;

        [Test]
        public void should_call_file_service_to_touch_file()
        {
            systemUnderTest
                .OnMessage<DatabaseChangedCommand>(m => m.DatabaseName = "");

            mockFileService.AssertWasCalled(x => x.TouchFile("", ""), opt => opt.IgnoreArguments());
        }

        [Test]
        public void should_response_to_database_changed_command()
        {
            systemUnderTest
                .OnMessage<DatabaseChangedCommand>(m => m.DatabaseName = "");
        }

        [Test]
        public void should_return_error_code_zero()
        {
            systemUnderTest
                .ExpectReturn(errCode => errCode == 0)
                .OnMessage<DatabaseChangedCommand>(m => m.DatabaseName = "");
        }
    }

Advertisements

My 1st NSB app

Backgroud:

  • DBA doesn’t want to check db scripts into our sourcecontrol, they insist to work on their own networking sharing folder.
  • CC.net must kick off whenever db script folder has been changed, to check if the new or modified script breaks the build.
  • Nightly database build is kind of late, because the build result can only be known next day morning. Build-on-folder-modification should be a better solution.
  • Some kind of folder monitor system should be introduced. (Code can be found on http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx )
  • CC.net URL trigger is the best candidate to build-trigger until we find some kind of API for cc.net.
  • I had to set the build condition to force instead of the default IfModificationExists, otherwise cc.net keep ignoring the build even the html file indeed is modified by BuildTrigger AC.
  • Use NSB to loosely couple the folder monitor and build trigger services, (2 ACs), I realized the benefit of deploying any of them without effect the other during the NSB experiment, a very flexible and reliable approach.

 

 

Couldn’t find logfile in NSB service?

According to the docs on NSB, http://www.nservicebus.com/Profiles.aspx, logfile should be in the exe folder. I got this working in command line mode but not in service mode.

The trick is, I need to manually pass the NServiceBus.Production parameter to the executable command of the NSB Service. (Is it supposed to be picked up by NSB Service automatically?)

To do this, open windows registry, locate the key of HKLM\SYSTEM\CurrentSet\services\<MyService>\ImagePath

Modify it’s value by appending NServiceBus.Production parameter at the end. Restart NSB service, the logfile is right in my exe folder.

Not sure is this a bug for NSB or windows service? Tried passing “NServiceBus.Production” as the start up parameter in service properties, no good.

Tried svn latest, too many new changes in trunk, app broken, had to rollback to stable version 2.0.0.1145.

Update: This bug has been fixed in 2.5.0.1446.

According to the document:

  • When running under the production profile, (all 3 profiles are set in when handler running in NT service mode),¬† the logs will be written to a file called ‘logfile’ in the same directory as the exe.
  • Setting log in code works, like this :
    Configure.With()
                    .Log4Net<ConsoleAppender>(x => { x.Threshold = Level.Info; }
                    ).Log4Net<FileAppender>(x =>
                    {
                        x.File = "build_trigger_log.txt";
                        x.Threshold = Level.Info;
                    }
                    )
                    .NinjectBuilder()
                    .XmlSerializer();
    

    but better not to do it instead using profile to control log. In fact the production profile will create that “logfile” anyway no matter we set the log file name in code or not.

  • The log threshold set in code or in profile can both be override by app.config:
      <configSections>
        <section name="Logging" type="NServiceBus.Config.Logging, NServiceBus.Core" />
      </configSections>
    
      <Logging Threshold="ERROR" />
    

NServiceBus, WCF and WebService

NServiceBus is not a replacement for WCF, but the only way to host NServiceBus process is through NServiceBus.host.exe, instead of IIS or WAS.

Those message handlers are automatically wired up by NServiceBus.host.exe, you don’t want to do it by yourself by code at WCF server side.

NServiceBus process can be exposed as WCF service, as shown in the WcfIntegration sample app, the endpoint is set in app.config:

<add baseAddress=”http://localhost:9009/services/cancelOrder&#8221; />

The only method in this WCF service is process().

What’s the reason to do this instead of client send the message by itself?

Maybe, for some reason, the client, including web server, can not talk to MSMQ directly, by calling a WCF service, the WCF service can send the message on client’s request.

The WCFIntegration sample in NSB codebase is combining the message sending and processing in the same project, kind of confuse.

In WebServiceBridge sample, this is a little bit clear, so called bridge between client and NSB message processor, as it demonstrated.

My NServiceBus demo

While watching NServiceBus video from EVan, I created my first NServiceBus demo application. It’s funny the presenter failed in almost all the demos, my local version works just fine. The difference is I’m using the svn trunk version.
Notes:

  1. MSMQ must installed.
  2. Prefer use queueexplorer to moniter msmq.
  3. Must add <UnicastBusConfig LocalAddress=”yourInputQueueName”> in the version I’m using, to solve the “Format Name is invalid” problem when startup.
  4. Manually create a NServiceBus.Host.exe.Config file to set scan path.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
<add key="EndpointConfigurationTypeScanned" value="C:\work_dir\New folder\NServiceBusDemo\NServiceBusDemo.Processing\bin\Debug\"/>
<!-- or using  <add key="EndpointConfigurationType" value="YourNamespace.YourTypeName, YourAssembly"/> -->
</appSettings>
   <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>