Moving a system from development through test to production reliably
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 the facilities of profiles
designed specifically to ease this process and provide
structure to versioning the configuration of a system.
More information on the host can be found here.
Configuration Difficulties
Starting out with NServiceBus development isn't always easy - there are lots of configuration options available. You can choose different levels of logging, different technologies for storing subscribers, and different types of saga storage (to name a few). Often, you'll want an appropriate combination of all of these options as long as you know you can change it later. Profiles give you that.
NServiceBus comes with 3 profiles out of the box - Lite, Integration, and Production. Each profile sets 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, the namespace-qualified type of the profile class needs to be passed to the NServiceBus host as a command-line parameter. Specifying the Lite profile is done as follows:
NServiceBus.Host.exe NServiceBus.Lite
If you're concerned about the fact that command-line parameters are used, that's understandable. First of all, know that when installing the NServiceBus host as a windows service, all profiles that are provided become 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 takes some time to get used to, but it's worth it.
If you just run the host without specifying a profile, NServiceBus will default to the Production profile. You can also pass in as many profiles as you want and NServiceBus will run them all.
Writing your own profile
Writing a profile is as simple as defining a class which implements the marker interface NServiceBus.IProfile. 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 you'd invoke:
NServiceBus.Host.exe YourNamespace.YourProfile NServiceBus.Lite
As you can see, the profile itself doesn't contain any behavior itself - it's just a placeholder around which different kinds of behavior can be hooked. Here's how those behaviors get connected to their profiles.
Profile behaviors
In order to provide behavior around a profile, implement the interface NServiceBus.IHandleProfile<T> where T is the given profile.
For example, let's say we have an email component in our system that we'd like not to do anything for the Lite profile, write emails to disk for the Integration profile, and uses an SMTP server for the Production profile. Here's how we'd set that up:
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 } }
With these classes, switching profiles doesn't just change NServiceBus behaviors but also your own applicative behaviors as a consistent set. No need to worry about keeping different parts of a configuration file in sync or changing which 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 the exact same behavior across profiles.
Dependent Profile Behaviors
There may be cases where you want slight variations of behavior based on certain properties of the class which implements IConfigureThisEndpoint. Also, you don't necessarily want all profile handlers to be dependent on the type that implements IConfigureThisEndpoint - just to have the ability to check whether it also implements some other interface they care about.
The host itself does this in how it handles publishers. Endpoints which don't publish don't need to have a subscription storage. Those that are publishers will need to have different storage technologies configured based on profile. Just as the host defines the interface AsA_Publisher and customizes behavior around it, you can do the same with your own interfaces.
In order for a profile handler to get access to the type which implements IConfigureThisEndpoint, it has to implement IWantTheEndpointConfig like so:
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; } }
This enables you to extend the host and write additional profiles and behaviors that customize various aspects of your system, all the while maintaining loose-coupling and composability between the various parts of your system.
Logging Behaviors
Logging is another kind of behavior that you may want to change from one profile to another. However, unlike other profile behaviors, logging levels and sinks need to be defined before any other components are configured - even before the container. For that reason, logging configuration is kept separate from other profile behaviors.
The logging behaviors that have been set up for the 3 built-in profiles are shown below:
| Profile | Appender | Threshold |
| Lite | Console | Debug |
| Integration | Console | Info |
| Production | Rolling File | Configurable, Warn by default |
When running under the production profile, the logs will be written to a file called 'logfile' in the same directory as the exe. The file will grow to a maximum size of 1MB and then a new file will be created. A maximum of 10 files will be held and then the oldest file will be erased. If no configuration exists, the logging threshold used will be Warn. Configuring the logging threshold is done by including the following in the application config file:
<configSections> <section name="Logging" type="NServiceBus.Config.Logging, NServiceBus.Core" /> </configSections> <Logging Threshold="ERROR" />
In order for changes to the configuration to have an effect, the process needs to be restarted.
If you want different logging behaviors than these, see the next section.
Customized Logging
In order to specify logging for a given profile, write a class which implements IConfigureLoggingForProfile<T> where T is the profile type. The implementation of this interface will be similar to that described for IWantCustomLogging in the host page.
class YourProfileLoggingHandler : IConfigureLoggingForProfile<YourProfile> { public void Configure(IConfigureThisEndpoint specifier) { // setup your logging infrastructure then call NServiceBus.SetLoggingLibrary.Log4Net(null, yourLogger); } }
Notice that here the host already passes you the instance of the class which implements IConfigureThisEndpoint so you don't need to implement IWantTheEndpointConfig.
Important: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 also can allow only one of these classes for all profile types passed in the command-line.
See the logging documentation for more information.
Behaviors requiring initialization to be complete
In your profile handlers, you may occasionally want to make use of the container to build an object for you. The only issue is that the necessary type may not have been registered yet and therefore you'd like to wait until initialization is complete before performing that behavior.
For that, you can just implement the IWantToRunWhenConfigurationIsComplete interface. Doing this will tell NServiceBus to call you at that stage of the configuration process:
class MyProfileHandler : IHandleProfile<MyProfile>,IWantToRunWhenConfigurationIsComplete { public void ProfileActivated() { wasActivated = true; // configure the object } public void Run() { if(!wasActivated) return; var obj = Builder.Build<SomeType>(); // do something with the object } static bool wasActivated; public IBuilder Builder{get;set;} }
This approach is particularly useful when you want your profile to then hook into an event on the object you used the container to build, allowing you to have your profile perform activities at specific points in the lifecycle of the application - not just at startup.
