Introduction
This sample shows how to configure the dependency injection infrastructure built into NServiceBus to instantiate and inject tenant-aware components. The approach allows the code in the handlers to focus on the actual business logic without having to deal with multi-tenant aspects of the problem.
This sample simulates a multi-tenant solution where the session provided to handlers is connected to individual tenant databases based on the value of a tenant
header on the incoming message.
Code walk-through
Creating the behavior
The behavior will wrap handler invocations and:
- Open a session to the relevant tenant database.
- Make the session available to the message handlers.
- Commit or rollback the session depending on the outcome of the handler.
- Dispose the session.
class MyUowBehavior :
Behavior<IIncomingPhysicalMessageContext>
{
public override async Task Invoke(IIncomingPhysicalMessageContext context, Func<Task> next)
{
var tenant = context.MessageHeaders["tenant"];
var session = context.Builder.GetService(typeof(MySession)) as MySession;
session.Initialize(tenant);
try
{
await next();
await session.Commit();
}
catch (Exception)
{
await session.Rollback();
throw;
}
}
}
Note that because the user can't provide arguments when resolving the session instance, the session has to be initialized after construction. The Initialize
method is defined on the concrete MySession
class. It is not visible to the handler because the handler requires only the ISession
interface.
Registering the behavior
The following code is needed to register the behavior in the receive pipeline.
var pipeline = endpointConfiguration.Pipeline;
pipeline.Register(new MyUowBehavior(), "Manages the session");
endpointConfiguration.RegisterComponents(c =>
{
c.AddScoped<IMySession, MySession>();
c.AddScoped<MySession, MySession>();
});
The lifecycle has to be specified as DependencyLifecycle.
in order to ensure there is a single instance of session created for each message being handled.
Message handlers
One of the benefits of a unit of work is that multiple handlers for the same message will share the same session and commit/rollback together. This is how the handlers look:
class MyMessageHandler1 :
IHandleMessages<MyMessage>
{
static readonly ILog log = LogManager.GetLogger<MyMessageHandler1>();
readonly IMySession session;
public MyMessageHandler1(IMySession session)
{
this.session = session;
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
await session.Store(new MyEntity());
log.Info($"{context.MessageId} got UOW instance {session.GetHashCode()}");
}
}
The session instance is injected via the constructor. This is the same instance as the one resolved and initialized in the MyUowBehavior
.
Running the sample
Run the sample. Once running, press any key to send messages. Note that for each given message the two message handlers will get the same session instance and that the instance is connected to the given tenant specified on the incoming message.