Profiles For NServiceBus Host

Moving a system reliably from development through test to production is a core capability for any development organization.

Manual configuration and code changes make this process error-prone and make versioning the system a nightmare.

The NServiceBus Host provides facilities of profiles designed specifically to ease this process and provide structure when versioning the configuration of a system. Read about the host.

Configuration difficulties

Starting out with NServiceBus development isn't always easy. There are many configuration options for levels of logging, technologies for storing subscribers, and types of saga storage (to name a few). Often, you want an appropriate combination of all these options as long as you can change it later. Profiles give you that flexibility.

NServiceBus comes with three profiles out of the box: Lite, Integration, and Production. Each profile configures a cohesive combination of technologies:

  • Lite keeps everything in memory with the most detailed logging.
  • Integration uses technologies closer to production but without scale-out and less logging.
  • Production uses scale-out friendly technologies and minimal file-based logging.

Specifying which profiles to run

To tell the host to run using a specific profile, you need to pass the namespace-qualified type of the profile class to the NServiceBus host as a command-line parameter. Specify the Lite profile, as follows:

NServiceBus.Host.exe NServiceBus.Lite

You may be concerned about the use of command-line parameters. Be aware that when installing the NServiceBus host as a Windows Service, all provide profiles are baked into the installation. Second, having the ability to sit down at a tester workstation and turn on and off various behaviors without touching configuration files or code makes isolating bugs much easier. It may take some time to get used to, but the benefits are worth it.

If you just run the host without specifying a profile, NServiceBus defaults to the Production profile. You can pass in as many profiles as you want and NServiceBus runs them all.

Writing your own profile

Writing a profile is as simple as defining a class that implements the NServiceBus.IProfile marker interface. Here's an example:

namespace YourNamespace
{
    public class YourProfile : NServiceBus.IProfile { }
}

To tell the host to run your profile and the NServiceBus Lite profile together:

NServiceBus.Host.exe YourNamespace.YourProfile NServiceBus.Lite

As you can see, the profile itself does not contain any behavior itself. It is just a place-holder around which different kinds of behavior can be hooked. See how those behaviors are connected to their profiles.

Profile behaviors

To provide behavior around a profile, implement the NServiceBus.Hosting.Profiles.IHandleProfile<T> interface where T is the given profile. For example, an email component

  • Does nothing with the Lite profile
  • Writes emails to disk with the Integration profile
  • Uses an SMTP server with the Production profile

Set it up as follows:

class LiteEmailBehavior : IHandleProfile<NServiceBus.Lite>
{
    public void ProfileActivated()
    {
        // set the NullEmailSender in the container
    }
}

class IntegrationEmailBehavior : IHandleProfile<NServiceBus.Integration>
{
    public void ProfileActivated()
    {
        // set the FileEmailSender in the container
    }
}

class ProductionEmailBehavior : IHandleProfile<NServiceBus.Production>
{
    public void ProfileActivated()
    {
        // set the SmtpEmailSender in the container
    }
}
class LiteEmailBehavior : IHandleProfile<NServiceBus.Lite>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the NullEmailSender in the container
    }
}

class IntegrationEmailBehavior : IHandleProfile<NServiceBus.Integration>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the FileEmailSender in the container
    }
}

class ProductionEmailBehavior : IHandleProfile<NServiceBus.Production>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the SmtpEmailSender in the container
    }
}
class LiteEmailBehavior : IHandleProfile<NServiceBus.Lite>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the NullEmailSender in the container
    }
}

class IntegrationEmailBehavior : IHandleProfile<NServiceBus.Integration>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the FileEmailSender in the container
    }
}

class ProductionEmailBehavior : IHandleProfile<NServiceBus.Production>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set the SmtpEmailSender in the container
    }
}

With these classes, switching profiles doesn't only change NServiceBus behaviors but also your own behaviors as a consistent set. There is no worry about keeping different parts of a configuration file in sync or changing the configuration file your application uses. You can also have multiple classes provide behaviors for the same profile, or you can have a single class handle multiple profiles (by implementing IHandleProfile<T> for each profile type) if you want identical behavior across profiles.

Dependent profile behaviors

You may want slight variations of behavior based on the properties of the class that implements IConfigureThisEndpoint. Also, you don't necessarily want all profile handlers to be dependent on the type that implements IConfigureThisEndpoint, just for it to check whether it also implements some other interface. The host itself does this when it handles publishers. Endpoints that don't publish don't need to have a subscription storage. Those that are publishers do need different storage technologies configured, based on profile. Just as the host defines the AsAPublisher interface and customizes behavior around it, you can do the same with your own interfaces.

For a profile handler to access the type that implements IConfigureThisEndpoint, it has to implement IWantTheEndpointConfig, like this:

class MyProfileHandler : IHandleProfile<MyProfile>, IWantTheEndpointConfig
{
    public void ProfileActivated()
    {
        if (Config is AnInterfaceICareAbout)
        {
            // set something in the container
        }
        else
        {
            // set something else in the container
        }
    }

    public IConfigureThisEndpoint Config { get; set; }
}
class MyProfileHandler : IHandleProfile<MyProfile>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set something else in the container
    }
}
class MyProfileHandler : IHandleProfile<MyProfile>
{
    public void ProfileActivated(BusConfiguration config)
    {
        // set something else in the container
    }
}

This lets you extend the host and write additional profiles and behaviors to customize various aspects of your system, all while maintaining loose-coupling and composability between the various parts of your system.

Logging behaviors

Logging is another kind of behavior that you can change from one profile to another. However, unlike other profile behaviors, logging levels and sinks need to be defined before you configure other components, even before the container. For that reason, logging configuration is kept separate from other profile behaviors.

The logging behavior configured for the three built-in profiles is shown:

Profile Appender Threshold
Lite Console Debug
Integration Console Info
Production Rolling File Configurable, Warn by default

When running under the production profile, the logs are written to 'logfile' in the same directory as the exe. The file grows to a maximum size of 1MB and then a new file is created. A maximum of 10 files is held and then the oldest file is erased. If no configuration exists, the logging threshold is Warn. To configure the logging threshold see changing logging level via config file.

For changes to the configuration to have an effect, the process must be restarted.

If you want different logging behaviors than these, see the next section.

Customized logging

To specify logging for a given profile, write a class that implements IConfigureLoggingForProfile<T> where T is the profile type. The implementation of this interface is similar to that described for IWantCustomLogging in the host page.

class YourProfileLoggingHandler : IConfigureLoggingForProfile<YourProfile>
{
    public void Configure(IConfigureThisEndpoint specifier)
    {
        // setup your logging infrastructure here
    }
}
class YourProfileLoggingHandler : NServiceBus.Hosting.Profiles.IConfigureLoggingForProfile<YourProfile>
{
    public void Configure(IConfigureThisEndpoint specifier)
    {
        // setup your logging infrastructure here
    }
}
class YourProfileLoggingHandler : NServiceBus.Hosting.Profiles.IConfigureLoggingForProfile<YourProfile>
{
    public void Configure(IConfigureThisEndpoint specifier)
    {
        // setup your logging infrastructure here
    }
}

Here, the host passes you the instance of the class that implements IConfigureThisEndpoint so you don't need to implement IWantTheEndpointConfig.

While you can have one class configure logging for multiple profile types, you can't have more than one class configure logging for the same profile. NServiceBus can allow only one of these classes for all profile types passed in the command-line.

See the logging documentation for more information.

Persistence

When you use NServiceBus.Host.exe out of the box, you can utilize one of the available profiles. The following table shows which persistence technology each pre-built profile configures by default. In addition, you can override the configured defaults.

The following table summarizes the different persistence technologies being used by the built-in profiles.

Before configuring persistence technology, to avoid overriding your configurations, the profiles check if other types of storage are used.
- In-Memory RavenDB NHibernate MSMQ
Timeout Lite Integration/Production - Keeps a queue for management
Subscription Lite Integration/Production - -
Saga Lite Integration/Production - -
Gateway Lite MultiSite - -
Distributor - - - Distributor

Default persisting technology

The AsAServer role activates the timeout manager. This role does not explicitly determine which persisting technology to use. Hence, the default persisting technology for timeout manager (RavenDB) is used.

Similarly to the AsAServer role, the various profiles activate the different NServiceBus features, without explicitly configuring the persisting technology.


Last modified 2016-01-11 12:55:00Z