Modular Monolith Integration with NServiceBus

Hi :slight_smile:

I know there is a similar topic already, but not sure how similar so I’ll try to post a bit more details about the architecture POC I’m asked to do.

I’m trying to build a POC for a new system based built on top of modular monolith architecture using NServiceBus for integration (comm between the modules). Modular monolith is mandatory since the product owner does not feel this should be using microservices architecture just yet, he wants to keep it simple as possible, but also as the product matures and userbase grows, it needs to be “convertible” to microservices (scale out).

Anw, POC so far implements loosely coupled modules, which can be easily converted to standalone apps. Every module has its own composition root, settings etc. And main initialization of the modules is handled in one API project.

As for integration, we would like to use NServiceBus, having one endpoint (bidirectional) per module. The only thing that is shared between modules is ModuleName.Integration class lib projects with messages, commands, and events.

The initial draft idea was to have one endpoint console application per module (acting as both receiver and sender) and every endpoint has its own configuration. As this is still a monolith where everything is hosted under one host or web application, we would still rather use multi-hosting by using web jobs under one app service plan, if it is possible. I do realize we need to be careful to scope assembly scanning by endpoint instance and probably set DI containers per instance.

For this, we would use only SQL Server Transport and modules are built in such a way that every module uses its own database so the plan was to use a module database for module-specific endpoint transport and persistence with transaction mode set to TransactionScope. So every endpoint would have its own queues in a module-specific database.

The flow would be that module feature handlers handle both API requests > business data (saving to DB) and publishing of events (transactional). Any NserviceBus handler subscribed to a certain event would also use module-specific features if needed to handle business data. All routing settings would be provided in the bootstrapping API to all endpoint configuration instances.

Any comments or suggestions would be appreciated for this POC theory for now (integration part), are we missing something, does it makes sense at all to go down that path, to keep endpoints in a bounded context per module? Or it would make more sense just to use one endpoint for all modules and reference them and the messages to that one endpoint. Anw, the product owner would like to use NServiceBus for an integration solution, we already used it in a couple of simpler projects, including Sagas and other functionalities.

Thank you.

Hi Petar,

Thanks for spiking this interesting discussion.

Given these constraints are given by the product owner, has the team been tasked to think about how to set proper boundaries and constraints within the modular monolith? Will you be using a single mono-repository? How will the solutions be organized? How do you make sure things within the repository solution only reference the things they are supposed to reference?

I’m asking these questions because I feel large once we explore a bit the constraints there and what the team has selected to make sure the context boundaries are properly set, it might become apparent what a reasonable initial approach could be.

Yep, that is one of the “caveats” that comes with hosting multiple endpoints within the same process. I’m thinking though that any solution you select about how to properly scope dependencies within the modular monolith will also “simplify” the management of controlling assembly scanning and DI scoping in general. Since that is a problem, a modular monolith using DI containers needs to have a solution for. How are you planning to organize modules, components, namespaces and DI in your modular monolith independent of NServiceBus?

I have seen both approaches being used successfully. For example, I have seen teams putting good guidelines and automated rules in place that allow them to start with a “single monolithic” endpoint and then gradually start moving out parts into more endpoints when necessary. It requires, though, a lot of “discipline” to not accidentally start watering down the separations of components.