Microservices are currently attracting criticism because microservice systems often do not have a good structure. But microservices only define how the structure is implemented. How good the structure is does not depend on whether the modules are microservices or classic modules. Domain-Driven Design (DDD) and Bounded Context are useful techniques to achieve a good structure. They are enjoying a renaissance thanks to microservices. If the outcome of the microservices discussion is an increased focus on DDD and better structured software, that’s an important success - no matter if modules are implemented as microservices or not.
Monoliths: usually poorly structured
To suggest deployment monoliths as a solution for poorly structured systems, seems absurd to me. I’ve done many reviews of deployment monoliths. The structures of the monoliths almost always left much to be desired. My explanation: Dependencies between modules sneak into the source code too easily. The only exception is when an architecture management tool is used or other measures are taken to enforce the module boundaries. But that was only the case with a few of the deployment monoliths I studied.
Because microservices have their own source code projects, and interfaces between the microservices are implemented, e.g. as REST interfaces, dependencies do not sneak in so easily with microservices. A developer would have to create a dependency on another source code project and call an interface. This is much more complicated than, for example, using a class from another Java package. The split into microservices thus supports compliance with the module boundaries.
Once a good structure has been established, it can be implemented with microservices. Deployment monoliths require further measures to enforce the structure in the long run.
Beyond the structure
It might seem that of these two alternatives, a deployment monolith is the better choice because of its lower complexity. Only one system has to be operated and deployed. But microservices have emerged as an architectural approach to solve real world problems:
Each microservice can be deployed independently. Deploying a single, small microservice is easier and faster than deploying a large and complex deployment monolith. Also, the risk decreases because the changes only affect a single microservice, and thus a single module. Therefore, the changes are not that big, so there are fewer opportunities for mistakes. Rolling back changes and strategies such as Blue/Green Deployment or Canary Releasing are also easier to implement than with a deployment monolith because of the small size of the microservices. When implementing continuous delivery for a deployment monolith, it is always worthwhile to consider at least a partial migration to microservices, as this can significantly reduce the complexity of the continuous delivery pipeline.
There is freedom of technology. If a system should be maintained for a very long time, then eventually there will be a migration to a moreup-to-date technology. A deployment monolith must be migrated to a new technology in one step. Microservices can be migrated individually with less risk and effort.
When each microservice is developed by one team, teams can independently deploy microservices and therefore new features. Likewise, they can make technical decisions largely independently of each other. This independence allows the teams to work very autonomously. This is especially useful in complex projects because it saves coordination effort.
Ultimately, microservices allow even more decoupling of the modules: technical decisions or deployment are limited to a microservice. Likewise, the failure of a process only leads to the crash of a single microservice. In addition, firewalls can create additional security barriers between microservices, and they can be scaled independently of each other. This results in a decoupling in terms of technologies, deployment, failure and security.
Thus, microservices support decoupling, which is a major goal of modules.
More or less effort?
Of course, microservices lead to increased complexity, during tests or for operations, for example. This additional complexity must be balanced against better modularization, greater decoupling, and independent deployment. After all, it only makes sense to choose an architectural approach if it is the simplest solution for the scenario. Microservices were originally invented because they were not just an easy solution for some scenario, but actually they were pretty much the only solution for large teams and fast deployment.
Excluding microservices as “too complex” right from the start is just as unhelpful as condemning deployment monoliths. And it is important not only to ask the question, which modularization technology to use, but above all to implement a good split in modules.
Microservices and deployment monoliths are different approaches to modularization. They have pros and cons and fit different scenarios. Even more important than the chosen modularization approach is that the structure of the software is solid and useful.