Tenant-based selection of dependencies

I’m currently helping a client implement a multi-tenant service based on NServiceBus. First things first: yes, I’m aware of the article Injecting tenant-aware components into message handlers, and I’ve read it, downloaded the sample code, and contemplated my options. I hope something simpler would be possible.

My client has quite a few message handlers like this one:

public class CreateCustomerHandler : IHandleMessages<CreateCustomer>
{
    public CreateCustomerHandler(HttpClient client)
    {
        Client = client;
    }

    public HttpClient Client { get; }

    public async Task Handle(
         CreateCustomer message,
         IMessageHandlerContext context)
    {
        // Use Client here to communicate with a REST API...
    }
}

The client dependency should come equipped with tenant-specific credentials (and possibly other configuration). Thus, we’ll have to find a way to create an HttpClient object per tenant, or possibly per message.

The problem isn’t one of lifetime configuration; I know about DependencyLifecycle.InstancePerUnitOfWork.

The problem is how to pick or create an HttpClient object for a given tenant. We’ll have some configuration data that’ll enable us create and configure an HttpClient object based on a tenant ID, but how do we configure NServiceBus so that a message handler receives an HttpClient dependency for the tenant that relates to the message?

I’m aware that we could inject some hypothetical IHttpClientFactory or IDictionary<int, HttpClient> as a dependency instead of just an HttpClient dependency, but I’d prefer not doing that, since it just makes everything (including unit testing) more complicated than it has to - not to mention that it’d violate the Dependency Inversion Principle.

Does NServiceBus have an extensibility point we can override to create instances of dependencies for a given message?

I’m aware of the four overloads of IConfigureComponents.ConfigureComponent, but I can’t see any obvious way to react to any tenant ID in that API. Perhaps I’m missing something?

Hey @ploeh

Some advanced DI usage scenarios aren’t that easy to support by the DI abstraction (e.g. providing context to the resolution process), so I can’t come up with a much smoother way of achieving what you want using DI right now besides the ideas you’ve probably already explored.

A more flexible approach would be to not use DI in the first place but make use of the context we’re passing down in the pipeline. In your behavior you’d use something like:

context.Extensions.Set(httpClient);

and in your handler you can retrieve the client using:

context.Extensions.Get<HttpClient>();

This is fully testable but it requires you to setup the HttpClient in the test context instead of just passing it in the constructor though.

@Tim, thank you for your response. Yes, I see how that might be an alternative. I didn’t know of this other option, but I see how it could work.

At the moment, I still lean towards Constructor Injection because of its more explicit nature, but the choice currently hinges on some fairly subtle considerations.

Hi Mark

I’d probably go down the path of using the IHttpClientFactory and create named clients per tenant. This is how the HTTP Factory is intended to use. I agree with you that it slightly complicates the testing of handlers but I’m wondering if that is such a big deal. One could argue that if the selection of the right infrastructure per tenant (here the http client) is hidden away in some IoC magic it is less visible what’s going on and you’d have to properly test that logic as well. By having it in the handler it is visible in the code how named clients are required for tenant specific handlers. Another option would be to introduce a simple wrapper like this

class TenantAwareHttpClientFactory {
   ctor(IHttpClientFactory factory)

   public virtual HttpClient CreateFrom<TMessage>(TMessage message) where TMessage : ITenantProvider   {
      return factory.Create(message.Tenant);
   }
}

and then inject that into the handler. For the handler tests you could then use a simple testable implementation of it.

Regards
Daniel

Daniel, thank you for answering. Yes, we’ve done something like that for now, but that doesn’t mean that I consider it optimal.

Part of the reason I write here on the forums is to learn how to use NServiceBus better, when a web search doesn’t enlighten me. I’ve only been using NServiceBus professionally for about half a year now (and that’s even only part-time). In general, I’ve only run into a few design issues so far.

Another motivation is that I hope that when a programmer with decades of programming experience runs into (ostensible) limits of the framework, that might be feedback about what could be improved.

I’m wondering if that is such a big deal

In itself, it’s not, but as a rule, legacy code grows from many small choices that are, in themselves, no big deals.

Sometimes, I just have to bite the bullet and accept that things didn’t get to be as nice as I’d like them to be, but when I can see a better design, but a framework prevents me from implementing it, that is, I believe, valuable feedback about that framework.

One could argue that if the selection of the right infrastructure per tenant […] is hidden away in some IoC magic […] and you’d have to properly test that logic as well

That is, I think, the fairer argument. I’d very much like to be able to test that selection mechanism as well, but it’s a completely unrelated to what a message handler does. I don’t see why I should have to bundle (and test) those two unrelated concerns. That gives the class two reasons to change, and thereby violates the Single Responsibility Principle in addition to violating the Dependency Inversion Principle.

This is why I dislike ‘IoC magic’ and prefer Pure DI.

Hi Mark

Thanks for this wonderful discussion! Whenever people reach out to us about design and implementation it gives us an opportunity to further explore the desires and needs of our customers. Furthermore, it shows us where we make/made assumptions that might not be aligned with how our customers see the product.

but that doesn’t mean that I consider it optimal.

Would you be able to explain a bit more what you would consider optimal and how a tenant-based component injection into handlers should look like so that you would consider it optimal?

In itself, it’s not, but as a rule, legacy code grows from many small choices that are, in themselves, no big deals.

I hear you. It is definitely something I always put an eye on and I wanted my teams to be aware of tradeoffs that are being made and what the consequences of those could be.

Sometimes, I just have to bite the bullet and accept that things didn’t get to be as nice as I’d like them to be, but when I can see a better design, but a framework prevents me from implementing it, that is, I believe, valuable feedback about that framework.

Absolutely! Keep them coming! NServiceBus has definitely a few rough edges that we need to smoothen out. For example, we are pushing towards a more pure DI world as you call it. Reality is though that it takes us very long to arrive there because NServiceBus grew with a conforming container abstraction from very early on.

but it’s a completely unrelated to what a message handler does

That is an interesting point that I would like to further explore. One of the things that comes up when we internally discuss tenant-based endpoints is where the tenant-awareness should be. It seems that you tend to be on the side of “it should clearly not be in the handler” but then the question is where should it be? One way is to put it closer to the middleware so that means that logic would need to be in the behaviors. But for performance reasons we decided to make the behaviors “singletons” per endpoint which means that anything that is bound to the execution of a message has to be resolved and provided within the invocation method of the behavior. This has the drawback though that dependencies need to be “floated” over the handler context into the handler. Because the handler context is an extensible framework piece those dependencies need to be put on the context bag. Once you do that any handler that uses that mechanism can only be tested when the arrange part of the test sets that dependency into the extension part of the testable handler context. For scenarios like that the context becomes sort of a mini-locator which is also not really beautiful and slightly complicates testing.

Another option would be to push the tenant into the configuration layer of an endpoint. Essentially have an endpoint variant per tenant and potentially even opt into uniquely addressable queues. That would make the handler logic as well as the middleware completely unaware of the concept of tenants. Furthermore it would allow you to make meaningful decision about messages belonging to all tenants or to a single tenant. With that messages belonging to all tenants would automatically be benefiting from competing consumers (all tenants listening to a shared queue) while messages belonging to a single tenant would only be handled by that single-tenant and thus you would not suffer from noisy neighbor effects. Unfortunately, this significantly complicates deployment and potentially also routing with NServiceBus.

Regards
Daniel

Whatever I write here, I’m aware that it’s just my opinion. People have varied ways in which they write code, and I’m not going to insist too much that my way is better. So the following is idiosyncratic, although I believe that it’s at least partially based on principles of software design.

I view a message handler (at least as long as it isn’t a Saga) as analogous to a Controller in a typical MVC-based web framework such as ASP.NET Core. A message/request arrives and you handle it. In a web application you return an HTTP response. With NServiceBus, you either integrate with some other system (e.g. database) or you react to the incoming message by sending new messages. In both cases, the interaction is quick and conceptually stateless.

What would I consider optimal? Something like ASP.NET Core’s IControllerActivator. A polymorphic type that I can implement and register with NServiceBus that’d basically create message handlers. Something like

public interface IMessageHandlerActivator
{
    object Create(MessageHandlerContext context);
    void Release(MessageHandlerContext context, object messageHandler);
}

I don’t know what that MessageHandlerContext would look like, but it’d need to carry enough information about the desired object type and context to enable run-time selection or creation of the desired object type.

I’d particularly find it useful if there was a way to peek at the message via the context, so that I could use a run-time value on the message (e.g. a TenantId) to select or create any tenant-specific dependencies that’d need to go into the handler’s constructor.

Where should tenant-selection live? When I develop web applications, I usually put it in the Composition Root. If I had something like the above IMessageHandlerActivator, I’d write an implementation like this:

public class CompositionRoot : IMessageHandlerActivator
{
    public object Create(MessageHandlerContext context)
    {
        // This is just copied from one of my ASP.NET Core code bases;
        // it doesn't have to look exactly like this.
        var messageHandlerType =
            context.ActionDescriptor.MessageHandlerTypeInfo.AsType();

        var tenantId = PeekTenantId(context);

        if (messageHandlerType == typeof(MyHandler))
            return new MyHandler(
                SomeSingletonDependency,
                SelectTenantDependency(tenantId));

        throw new InvalidOperationException(
            $"Unknown message handler type: {messageHandlerType}.");
    }

    public void Release(MessageHandlerContext context, object messageHandler)
    {
         /*...*/
    }
}

Again, I’m in no way insisting that this is clearly superior; it’s jut the code that I’d like to be able to write, because it enables me to prevent implementation details from leaking into the message handler itself.