The hidden monolith
I decided to start an article based on the “Hidden monolith”. Micro-services are the latest architecture style that everyone is employing. Often companies hear the new buzz word “Micro services” and say – “We must do that” … and when you start to analyze their system you realize… they got it a bit wrong, their system is still a monolith but distributed over many services yet still tied together due to their interdependence….and this is the hidden monolith….
Let me just state that doing a modular monolith is not wrong as long as it fits into your architecture requirements and it may even represent a migration from a monolith architecture style but what I can state is that; you will not have true micro service architecture style system with this approach. The modular monolith approach will cause coupling which will result in modules affecting each other and limiting your deployment options.
I tried to cover some of the variations that I have seen below but let me first cover a definition or two.
What is a Monolith.
A monolith is an architectural style or a software development pattern that has all the code modules of a system in a single code base that is compiled and distributed together.
This used to be the defacto way in which software was produced a few years ago but due to the problems associated to stability and scaling it has lost favor with architects and the word monolith are often associated with the way not to do things.
What is a Micro-service.
Where the monolith had all the code of all modules in a singular code base the micro-service in concept is a way to modularize a system by breaking a system up into smaller independent services to provide a user with a business solution.
Each service contains its own UI, business logic and persistence layer. The services are developed to function alone by itself, it encapsulates all its own logic and it shares nothing and requires nothing from any other services.
This allows these services to be isolated and scaled independently in things like containers.
Why is a Monolith bad?
A monolith design is not bad. It is just bad if used in the incorrect situations and most modern applications have requirements that can not be fulfilled by the Monolith as a it results in.
- High complexity and tightly coupled code. Spaghetti code.
- Difficult to make changes in large applications.
- Difficult or impossible to estimate work.
- Easy to introduce regressions.
- Slow delivery time due to complexity. See Will coding standards save your project?
- Difficult to evolve unrelated use cases.
- Unable to scale or isolated parts of the application with different run time behaviors.
Example 1. Shared UI & Shared Persistence.
The example above is a common mistake and I have even done it myself. In this example the system shares a common website and database and although the system has been modularized using services it remains a monolith.
- The UI and persistence is shared and the UI can not scale in relation to the services. Even if the services can be scaled you will have a bottleneck in the UI.
- Deployment options are a bit limited with the shared UI and if you scaled the UI as separate component you will incur the cost of configuration or carrying the unneeded services along.
- Changes in the UI for specific services can easily affect other services causing changes to ripple through the solution.
- Database changes for specific service will most likely introduce bugs in the other services.
- The Database is shared and you can not isolate only the db section you need for a specific service. This is a challenge if a specific service need to be moved to a specific location in the cloud.
- The shared UI and database represents the single points of failure of the application.
Example 2. Shared UI
The example above as you can see is a variation of the previous example. In this example again all the services share a common UI but at-least it has its own persistence which would allow you to scale and move the services independently.
- The UI and persistence is shared and the UI can not scale in relation to the services. Even if the services can be scaled you will have a bottleneck in the UI.
- Deployment options are a bit limited with the shared UI and if you scaled the UI as separate component you will incur the cost of configuration or carrying the unneeded services along.
- Changes in the UI for a specific service can easily affect other services causing changes to ripple through the solution.
- The shared UI represents the single points of failure of the application.
Example 3. Share persistance
In this example the UI and services have been separated and you are very close to a micro-service architecture. One of the limitations of design in this example is that the Services share a common database or persistence and as a result they will affect each other.
- The shared UI and database represents the single points of failure of the application.
- The Database is shared and you can not isolate only the db section you need for a specific service. This is a challenge if a specific service need to be moved to a specific location in the cloud.
Example 4. Shared Services
In this example the “friends stay tougher”, each service has its own UI and persistence but for some reason it has been coupled at the service layer. So although the system has been designed to consist out of independent micro services, the services are not truly independent. When ever you observe this in a system it is a very good indication that he decomposition of the services are wrong as the services want to be one in reality.
To break the direct dependency between the services a message bus would need to be introduced thereby decoupling the direct dependency but this can also be viewed as a hidden monolith as the services are not independent from each other and for a use case to be fulfilled they depend on each other. Other symptoms of such a type of system is when a system is very chatty with lots of traffic or message heavy.
Example 5. Shared code.
In this example we can see the 2 services are coupled to each other via some dependencies, now this is not all bad. One can not have an application without some coupling. The key is deciding what can be shared and what not. The micro services are independent from each other at run time and can be scaled independently but the hidden monolith may potentially still exists preventing the services from evolving desperately from each other. This increases the chances for unexpected regressions.
As a guide framework or utilitarian code may be shared.
- .net framework
- 3rd party components
- file parsers like CSV or XML file readers.
- Message Bus infrastructure code.
- Security
- Logging
- Auditing
Business context code, independent of use-cases that are allowed to be shared.
- Validation
- Transformation
- Translation
Example of code that should not be shared or reused by another service.
- Data contracts of services.
- Orchestration code.
- Constants relating to business logic.
- Business rules.
- Processing logic