I’m trying to build a proof of concept for an enterprise system based on the modular monolith architecture using NServiceBus. Hence, the modules/bounded contexts live in the same process and communicate with each other using events.
When a module publishes an event, it is then handled in another module, or in multiple modules, coexisting in the same process. This works as expected.
I am wondering if this is a usual scenario for NServiceBus and if it’s suitable for production. Are there any particular aspects that we should pay attention to in such a scenario?
My main concern would be related to “congestion”. When using a monolithic/modular architecture there will be only one endpoint, unless you’re going down the route of multi-hosting, which means a couple of things:
one input queue where all messages will back up
multiple subscribers won’t work and the subscriber logical endpoint is one, so if more than one module needs to subscribe only one of them will effectively receive the message
In the proof of concept that I have built, the events are published by a Web API application. These events are then handled by a console application (one nsb endpoint) in several modules (separate assemblies). What I could test until now, is that an event published by the Web API is received by all modules that have a handler for it, in the console app. Only when the console app is scaled out, the event is received by just one of the console app instances. This behavior is actually desired for the modular monolith.
So using multi-hosting would be rather justified by the possible congestion risk. Is that right?
By the way, can multi-hosting be implemented also in a .Net Framework application? Is there a code sample available?
Yes, you’re correct. My bad, what I really wanted to say is that when handling events in such a scenario, all handlers will be invoked in the context of the same incoming message. Something like the following:
input queue: message
endpoint picks up the message
endpoint looks for handlers
endpoint finds n handlers for the incoming message type
endpoint will invoke all found handlers (in a sort of for loop)
If one of the handlers fails the incoming message will be retried and all handlers will be invoked again, which might lead to some issues.
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
...
endpointConfiguration.EnableInstallers();
var transport = endpointConfiguration.UseTransport<RabbitMQTransport>();
transport.ConnectionString("host=localhost");
transport.UseConventionalRoutingTopology();
var endpointInstance = await NServiceBus.Endpoint.Start(endpointConfiguration).ConfigureAwait(false);
...
}
}