0

We have successfully integrated our multitenancy strategy with MassTransit due to some help from Chris Patterson. However we are stumbling over getting our (Automatonymous) sagas multitenant. I have something that works but I am not at all comfortable with it. We are using the "schema per tenant" database strategy, but are willing to flex this for sagas if that is the cleanest way to solve it.

We have tenant ID on the header of all messages. We scrape it off the IConsumeContext<> of incoming messaging and put it back on the IPublishContext<> of outgoing messages. This works fine with ISagaRepository<>.GetSaga(...) because one of its parameters is IConsumeContext<>. The problem is, when we call the other ISagaRepository<> methods, they do not have IConsumeContext<>, and we have no way of filtering by tenant within the repository. If we stick with our current database strategy, we have know the tenant so we know what schema to hit. If we change to have centralized tenant tables, we have to include tenant in the filtering because the thing that it is being correlated by is not necessarily unique across tenants.

The PropertySagaLocator<,> seems to be the key point based on my current understanding. In its Find(IConsumeContext<>) method we have the tenant context we need accessible, but it is not being passed down to the saga repository.

In my current attempt to get this working, I have therefore created a property saga locator for multitenancy that works with a specialized tenant saga repository and gives it the tenant context that it needs to use its .Where(...) method appropriately. But here's where it gets ugly. The PropertySagaLocator<,> concrete class is being instantiated by Automatonymous, and so to swap this out, I have to start at the edge of Automatonymous, at one of the .StateMachineSaga(...) extension methods and swap out concrete classes all the way down to the point where it is integrating with MassTransit by using the PropertySagaLocator<,> since it is a chain of concrete classes instantiating each other all the way down. I am not comfortable with making such a deep cut through Automatonymous, but it seems to me that whether we take the "schema per tenant" strategy or switch it, we are stuck with needing to integrate at this same point.

The other aspect of this is that we need to put tenant ID on outgoing messages when Automatonymous' .Publish(...) notation is employed. The way that I am currently doing this is with a decorator pattern on ServiceBus, and currently the point at which I am injecting the decorated, tenant-specific service bus is when the bus is copied from the consume context to the instance state, i.e. in my overrides of the saga message sink GetHandlers() method.

Does anyone have experience with how to integrate Automatonymous sagas with multitenancy? What we are doing now just seems to invasive and we would like to hit a more natural seam.

Community
  • 1
  • 1
Keith Pinson
  • 7,835
  • 7
  • 61
  • 104
  • The extension points in MT3 (particularly in regards to use with Automatonymous) are much better, so this will likely get easier with that release. The schema per tenant is a deep approach, but the ability to carry a header through the message chain is something I'd really like to ensure is well supported. I'd be happy to work with you on this to make sure it addresses your needs as I feel your use case is not unique. – Chris Patterson Jul 30 '15 at 15:19
  • @ChrisPatterson hey thanks. I added an answer below about what I've changed to, and it's a lot better, but doesn't allow me to correlate by property. – Keith Pinson Jul 30 '15 at 15:33

1 Answers1

0

I've found another approach that's a lot less invasive, but is more restrictive. Specifically, you cannot use the PropertySagaLocator, i.e. all your correlations have to be Correlation IDs by inheriting from the CorrelatedBy<> interface. Make sure you don't do any StateMachineSagaRepository<>.Correlate(...) calls, because if you do, it will use the property locator, even if you give it the actual Correlation ID.

What that allows me to do is avoid the use of any methods on the saga repository except GetSaga(...), where I have the context I need for our multitenancy strategy. I then throw a NotImplementedByDesignException in the others.

That leaves me with only one thing to worry about; how to get tenant ID on the headers of messages going out from .Publish(...) calls. To do this I just subclassesed ConsumeContext<> and simultaneously implemented IConsumeContext<> and then overrode the Bus property with new so that I could set the bus on it. I then had a decorator pattern of the service bus that ensures that the bus publishes with tenant header no matter what method you call on it. Then in my saga repository I wrapped the actions I return in a lambda that passes my subclassed consume context along with my tenant-specific decorated bus into the consumer for the saga instead of just the straight consume context. This results in the bus that gets set on the saga state being specific to that tenant, and all outgoing messages then have tenant ID on them.

Keith Pinson
  • 7,835
  • 7
  • 61
  • 104