Making Modular Monoliths Work

Think Slices, not Layers

Making Modular Monoliths Work

Think Slices, not Layers

Microservices have become part of the software engineering cultural zeitgeist to the extent that alternative approaches to architecture and development are treated as somehow inferior. Given the challenges that running microservices present, I usually recommend beginning development of new projects and systems as a single deployable unit — the monolith.

Sam Newman, in the book “Building Microservices”, agrees with this approach. He recommends leveraging microservices only if you can become convinced of the benefits for your system, not as a default for every project.

A monolithic architecture is a choice, and a valid one at that. I’d go further and say that in my opinion it is the sensible default choice as an architectural style. In other words, I am looking for a reason to be convinced to use microservices, rather than looking for a reason not to use them.

Sam Newman — Building Microservices, 2nd Edition

A common technique for organizing the architecture of a large system is the “layered” or “n-tier” architecture, named due to the organizational principle of dividing a system into multiple layers according to the function of each layer, as depicted by the following diagram:

assets/layered-architecture.png

Architecture organized as layers

One of the powerful features of the layered architecture pattern is the separation of concerns among components. Components within a specific layer deal only with logic that pertains to that layer. For example, components in the presentation layer deal only with presentation logic, whereas components residing in the business layer deal only with business logic. This type of component classification makes it easy to build effective roles and responsibility models into your architecture, and also makes it easy to develop, test, govern, and maintain applications using this architecture pattern due to well-defined component interfaces and limited component scope.

Mark Richards, Software Architecture Patterns

To keep the layers easy to manage we typically enforce the separation between layers by requiring that dependencies between layers go in only one direction, top to bottom, starting at presentation and moving downwards through the layered stack. That is, a class in the presentation layer can depend on a class in the business layer, but the reverse is not true; a class in the business layer cannot depend on a class in the presentation layer. Isolating layers in this way means that changes made in one layer of the architecture generally don’t impact or affect components in other layers

assets/layered-dependencies.png

Dependencies in a layered architecture

The downside of organizing code as horizontal layers is that whenever we add or change a feature in an application, it requires touching many of the different layers: updating the user interface, adding data fields to application objects, making changes to database queries, and so on. By focusing on minimizing coupling between layers, we force development teams to change and update code across a wide-ranging swath of code that may be shared between multiple groups within the organization.

assets/layered-changes.png

Changes in a layered architecture propagate across the codebase

Slices, Not Layers

An alternative approach to the layered architecture is to organize our code across vertical slices of business functionality. These slices are determined based on business demands, rather than enforced by technical constraints. With code organized as vertical slices, when we add or change a feature in an application, we update the same set of layers as before, but this time our changes are scoped to the area of business concern currently being worked on.

assets/sliced-architecture.png

Organizing by vertical slices

With this approach, most abstractions melt away, and we don’t need any kind of “shared” layer abstractions like repositories, services, controllers. Sometimes these are still required by our tools (like controllers or ORM units-of-work) but we keep our cross-slice logic sharing to a minimum.

Jimmy Bogard, Vertical Slice Architecture

With this approach, each area of the business domain can leverage architectural patterns that are appropriate for the challenge. You can use a Transaction Script or a Table Module for simpler parts of your application and full Domain Modelling for more complex areas.

With code organized as vertical slices, as our system continues to grow, organizing our code around of business functionality provides us a cleaner entry point into deploying multiple services in a microservice architecture when the time comes: each module organized as a vertical slice of business functionality in our modular monolith is an excellent candidate to be broken off into an independently deployable microservice. This way, we can gradually work our way toward a microservice architecture rather than jumping in with both feet right from the beginning.

Vertical slices are not without their own pain points — you will likely still force developers to use the same programming language and frameworks. But it may allow you to effectively scale your monolith more effectively than the traditional layered architecture.

See also