Skip to content

Monolith

A modular monolith has many overlapping advantages with microservices. However, in a monolith architecture it is a lot easier to bypass the modularity and therefore creating a big ball of mud. One way to enforce the modularity is by writing architectural fitness functions.

The hardest thing about building modular monoliths is keeping them modular.

One of the major benefits of a modular monolith is how easy it is to share code. But that same ease is also their biggest flaw. Ironically, code sharing becomes the main vector of contamination.

Monolith and microservices should not be on a black-white scale but rather on a continuous scale. If we have a modular monolith we can extract specific modules into services for optimization or because they have specific needs (such as individual deployable).

Concerns

  • Long compilation times.
  • Long-running test.
  • Deployment to production takes a long time.
    • Single deployment unit.
  • Limited language agnosticism.
  • Centralized development.

Modular

The module’s independence is determined by three main factors:

  • number of dependencies (to other modules) - could be reduced e.g. with a mediator
  • strength of dependencies (how chatty is the module with another one) - should these modules be merged?
  • stability of the modules on which the module depends on (how many modules need a change when I change my module?) - merge?

Technical layers are often used as "modules" but this is very bad in regards to "stability" since a new feature usually needs a change in all modules (UI, API, Business, DB). Vertical Slice seems more reasonable.

Module must have well-defined interface (a contract). We only want to have one "arrow" from Module A -> Module B.

A modular component is a building block of the application that has a well-defined role and responsibility in the system and a well-defined set of operations.

It is often difficult to enforce modularity in monolith and it often relies on discipline. A concrete solution could look like this.

Example

The best way to separate modules in .Net is by creating projects and keep everything where possible internal or private. We usually need at least one WebApi project (executable) and could then add projects (class libraries) for each module. A module would contain simple interfaces for the WebApi to use (such as ServiceCollectionExtensions to register services in the program as well as the endpoint definitions such as controller or minimal api definition). In addition we would add a Module.Contracts project for a clean communication interface. The WebApi can reference the module, all other modules can only reference the Contracts module.
We can even build the modules in a way that they can run on their own. E.g.: Each module has an executable Api where we reuse the ServiceCollectionExtensions but mock external modules.

---
title: Project references
---
graph TD
    subgraph WebApiModule
        WebApi
    end

    subgraph OrdersModule
        Orders
        Orders.Contracts

        Orders --> Orders.Contracts
    end

    subgraph PaymentModule
        Payment
        Payment.Contracts

        Payment --> Payment.Contracts
    end

    WebApi --> Orders
    WebApi --> Payment
    Orders --> Payment.Contracts
    Payment --> Orders.Contracts

For inter-module-communication we would introduce something like a mediator (easier) or message bus (more complex) to still have a clear separation.

---
title: Direct communication (bad practices)
---
graph 
    ModuleA <--> ModuleB
    ModuleA <--> ModuleC
    ModuleA <--> ModuleD
    ModuleB <--> ModuleC
    ModuleB <--> ModuleD
    ModuleC <--> ModuleD

---
title: Communication with middleman (good practices)
---
graph 
    ModuleA <--> Middleman
    ModuleB <--> Middleman
    ModuleC <--> Middleman
    ModuleD <--> Middleman
    Middleman(MessageBroker or Mediator)

Example

Notes

Get and Updates alike can be done via messages (I can also have a "GetBlaBlaRequestEvent" which produces an "BlaBlaResponseEvent" in another system).
Complexity increases drastically again if we do it like that (separate projects, strict contracts, message broker or mediator, async everything, ... ) and we should decide if that's worth it but it prevents a "big ball of mud" and is less complex then microservices.

Related

microservice
vertical-slice