by Michal Dorner
We have been building multi-layered applications for so long it becomes the de-facto standard pattern. The main idea is to split the application into at least three distinct layers: Presentation, Business and Data access.
In theory, this pattern provides clear separations of concerns. By segregating an application into layers, developers acquire the option of modifying a specific layer, instead of reworking the entire application. Unfortunately, it turns out this doesn’t reflect reality.
As the application grows, the layers grow with it until they become monoliths of their own. Each layer has a direct dependency on a lower layer and an invisible semantic dependency on the upper layer. The actual implementation of every layer is driven be requirements of the upper layer. In the end, business aspects are spread everywhere. Therefore, implementing a business feature almost always requires modifying every layer in the system. Our code is localised around technology aspects while the axis of changes is usually around business aspects. This makes both maintenance and extension of the system much harder.
In contrast to the n-tier architecture, the idea around vertical (feature) slices is to localise code around business aspects. Layers representing physical interfaces like API or database schema are preserved. Rest is split into slices where each slice should support a single business feature. Important is to minimise the coupling between slices and maximise coupling within a slice. In some extent this is similar to the microservice architecture, however, the application is still a single deployment unit.
Conceptual differences between DDD style n-tier architecture, feature slices and microservices are illustrated in the following figure:
Microservice architecture is using the same principle but introduces also a physical separation between feature slices. This is crucial for scaling or independent development and deployment of individual services. Along with these advantages come a cost of increased complexity in deployment, versioning and code sharing. Therefore, if you don’t need any of the additional benefits of microservices it’s better to keep the monolithic approach with internal feature slices.
Term scalability in software engineering usually refers to usage scalability (e.g. number of requests system can handle) or scalability over time (e.g. amount of aggregated data system can handle). However, there is also another commonly forgotten aspect – scalability of the code itself. It means how long the system can be extended and modified before the codebase becomes unmaintainable.
Feature slices isolate ever-changing business requirements into separate modules. This significantly helps with keeping the overall structure of the system reasonable. Feature slices basically pushes for single responsibility principle and open-closed principle applied on an application level.
CQRS, Transaction Scripts and DDD
Feature slices are a natural fit for request/response applications like web APIs. A good approach is to treat each request as a distinct use case. This plays very well with the CQRS pattern – HTTP GET request are queries while HTTP POST, PUT and DELETE requests are commands. Each command/query can then decide for itself how to best fulfil the requirements.
For most cases, they can be implemented as transaction scripts. Transaction scripts organise logic primarily as a single procedure without additional domain abstractions. As Martin Fowler wrote in his book “Framework Design Guidelines: Domain Logic Patterns”:
The glory of the Transaction Script is its simplicity. Organising logic this way is natural for applications with only a small amount of logic, and it involves very little overhead either in performance or in understanding.
For really complex cases where a transaction script would be hard to reason about it’s still possible to use some different approach. A common solution is to use Domain Driven Design (DDD). However, DDD again implies layers and all the disadvantages mentioned earlier. Even Microsoft recommends that DDD should be applied only to complex domains where the model and the linguistic processes provide clear benefits in the communication and in the formulation of a common understanding of the domain.
Layered architecture and Domain Driven Design are here for a long time. Now it’s time to at least admit they no longer deserve the status of a de-facto standard for enterprise software designs.
On the other side, there is a lot of hype regarding the microservice architecture. While it solves many problems it also brings a new one.
An alternative is to use feature slices. It solves many of the issues related to layered architecture while it doesn’t bring the additional complexity of microservices.