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