Rob Cook


Home | Donate

Modularity


2024-05-03

I dislike microservices. They are hard to debug, hard to deploy, and hard to manage. I understand the problems they solve, but there is a lot of software (particulary in small and medium sized enterprises) that will never have those problems.

What then is a developer to do? Everyone seems to be bought in on microservices for all but the most trivial of applications. How can you argue for a good old fashioned monolith? People are beginning to agree that microservices are hard to get right. There is a growing body of advice out there that cautions against starting a new project with them. Instead start with a monolith and gradually migrate pieces of it to thier own services. The end goal is still microservices though. Is there an alternative?

A modular monolith

A relatively recent development in the architecture space is the idea of a modular monolith. You retain the ease of deployment and debugging of a monolith and gain the separation of concerns of microservices. The difference is these services are logical separations of the same process. No more round trip over the network, and the serialisation and deserialisation that entails. Communication happens in process, so is much quicker.

How is such an application organised then? Can we avoid the fate of so many monoliths over time, of degrading into the dreaded big ball of mud? Yes we can. Leaning on traditional layering and the idea of vertical slices, we can organise a decoupled set of modules that retain a highly organised structure.


Solution
- Module A (folder)
- Module B (folder)
-- API (project)
--- Controller.cs
-- Contracts (project)
--- Reuqest.cs
--- Response.cs
-- Application (project)
--- Handler.cs
-- Domain (project)
--- Model1.cs
--- Model2.cs
-- Infrastructure (project)
--- Repository.cs
- Module C (folder)
appsettings.json
Program.cs

Above we have a sample layout of a .NET solution. Instead of one project per layer, as in a traditional monolith, we have multiple projects per module. The view above shows the structure of Module B following clean architecture, but you are free to follow whatever layering you prefer. Also, each module is free to follow a different layering if it makes sense to.

Inter-module communication can be kept decoupled by use of the mediator pattern (the library MediatR is ideal for this). Only the request and response classes need to be exposed as public (held in the Contracts project in this example).

Whilst there are advocates of this architecture as a stepping stone toward microservices, I would argue it is a vaild architecture in its own right. Getting module boundaries right is hard, and one of the reasons a lot of microservices projects devolve into a distributed monolith. With a modualr monolith, the cost of refactoring modules is much cheaper. Change is just easier.

You also gain the the ability to easily carve off modules into thier own microservice if this ever becomes necessary. This need not be a permanent change either, modules can be rejoined with the monolith if standing them up as a separate service no longer makes operational sense.

I really like this architecture pattern. It makes for a decoupled system that is easy to develop, debug, and deploy. Monoliths are back in fashion.

end