The architecture of otto.de is based on the concept of vertical decomposition: the whole system is vertically split into several loosely coupled applications.
Every „vertical“ is responsible for a single business domain such as „Order“, „Search & Navigation“, „Product“, etc. It has its own presentation layer, persistence layer and a separate database. From the development perspective, every vertical is implemented by exactly one team and no code is shared between the different systems.
For some time, a different approach to decompose large systems is becoming more and more popular: microservices. What are the similarities and differences of both kinds of architectures and how is it possible to get the best of both? This is what I want to discuss in this text. Let us start with microservices: In short, „the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API“ (Martin Fowler).
Sometimes, people refer to the code size: for example, a microservice should not have more than 100 lines of code. Personally, I do not find such definitions helpful, for several reasons. I prefer definitions from the feature perspective: A microservice should be responsible for a single use case. In other words, the „Single Responsibility Principle“ known from object oriented development should be adopted to applications.
Let us have a look at an example from our online shop. There are different areas in the shop where we are promoting products: similar products, products other people were interested in, and so on. In a monolithic application, we would need a database of product data, having all the attributes needed for any kind of recommendation (in addition to all the information needed by other components of the shop). In the case of otto.de, this is a noticeable amount of data and a rather complex data model.
The problem is that it is hardly possible to find a data structure that fits well for every use case. Using a microservice architecture, finding the „perfect“ solution for every kind of problem is much easier. For example, we could have a microservice only responsible for serving a single recommendation type. One service would have to serve „similar articles“, another one „best rated products“, and so on. The data needed to identify „similar articles“ is very different from the data used to select „best rated products“, so both services would have different data structures, fitting exactly their needs.
Not only the data structures would differ: the services could also use different implementations, for example, one using in-memory calculations while others would make use of a relational database. A microservice architecture has a whole slew of advantages when compared to monolithic architectures:
- Microservices are small enough to be developed by only a few developers in a short time.
- A single developer is able to understand all aspects of a service.
- It is possible to choose the „best“ programming language for different kind of problems.
- The code of the service remains compact, without having too much boilerplate code, no need to use heavyweight frameworks or abstraction layers.
- Together with REST there is an architecture to realize a loosely coupled system of services. There are other options like thrift or protocol buffers, having similar characteristics.
- Microservices can be developed and deployed independent from each other. This is an important premise for continuous delivery.
- The development stays highly efficient, while developing monolithic architectures tends to slow down over time.
- Because microservices are small, it is easy to replace them by newer, better implementations.
- The development of small services scales better: it is easier to establish independent teams in a microservice architectures than in monolithic ones.
- The scalability of a distributed system is much better than a monolithic one because the different services can be scaled independently.
On the downside of microservices: like every distributed system, the operation of such an architecture is much more complex than the operation of monolithic systems. We need more hardware resources, it is a must to automate everything, monitoring and alarming is more complex and so on.
Another problem is the comprehensibility of a pure microservice architecture for complex systems. Because the system is loosely coupled, consisting of independent applications, it is not easy for developers to „see“ the overall structure of the system.
This is where vertical decomposition comes into the game. The two concepts fit perfectly together, because vertical decomposition introduces another dimension to scale the system, and they help to give microservices a structure.
Microservices, on the other hand, help us to cut down verticals into smaller pieces, so they do not turn into large monolithic applications.
From the outside, a vertical is a black box. There are some REST interfaces, but the internal structure (or micro architecture) is up to the development team responsible for the vertical. Whether or not a team is choosing microservices to realize a vertical is irrelevant from the perspective of other teams or systems, because it is only the URLs of the different REST resources that may differ.
Below the line, combining vertical decomposition with microservices is a great way to build large systems. Like namespaces in object oriented applications are structuring code, verticals are grouping microservices, so it is easier to understand the overall architecture. Microservices are a way to implement verticals, preventing the teams from turning an initially small vertical into a 100.000 lines of code monster application.