Software is changing at a sickening speed. Only a few years ago, we were thrilled that Spring saved us from the bureaucratic EJB, now a 3-tier Spring/Hibernate/Relational DB application is considered classic (euphemism for old).
Luckily, some ideas in software never grow old. I do not like pitting micro service against monolithic, because we have long learned our lessons and evolved from writing monolithic applications. At the lowest level, we use object-oriented ideas to define objects, we assemble objects into components, and we assemble components into an application. At every level, we try to adhere to the basic abstraction rule: loose coupling with tight cohesion. With this rule comes heuristics, such as: single responsibility - one abstraction should have only one responsibility. What makes an application monolithic, compared with micro service, is in its deployment. The 3-tier classic application is typically bundled into a war and deployed to some web server. While for a micro service application, a single web page might get information from different services, each is deployed and running independently.
As long as you live on the earth, even in the cloud, there are some basic physical laws that you can’t break. “loose coupling with tight cohesion” is one of them. A micro-service architecture helps enforce this law, but if you get it wrong, it will punish you even more. When everything is bundled in a big ball (monolithically), even with well-intended interface design, there is nothing in the language itself (e.g. java) that prevents you from sneaking around those well- intended interfaces and directly invoke things behind interfaces. Often, an architect can draw a beautifully structured design diagram with layers of boxes clearly separated and communicating through well-defined channels, but in reality, those boxes of layers might talk to each other under the radar, and you’d have to trace into code to find out those “small” talks, so the beautiful architecture diagram is deep down a big ugly smelly mess. With a micro-service architecture, because services are separate processes, you have no other choices but to talk through the well-defined channels. That is good, right? Well, if you always have to deploy multiple services all together, you are virtually having a monolithic application, the fact that this monolithic application is divided into multiple services only increase overhead.
So good micro service architecture has to embody “loose coupling with tight cohesion” so that one change inside one service won’t have domino effect on other services. How to divide services is both a functional, technical and organizational question and has to be taken seriously.
The second law that you can’t break is “Conway's law”: organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations. I first felt this law when I was working on a project management system. This system has resource module, time module, project module, request module etc, I thought it was strange, because it was a project management system, resource, time, project, none of these domains alone meant anything, they had to work together to accomplish any business value. Then I read about this law, and all became clear: this team was structured into resource team, time team, project team etc, so the system structure reflected the team structure perfectly. And also the system suffered from the problem described above: although these modules were supposed to have well-defined interfaces, in reality, inside code, calls were made haphazardly and over the years, the system devolved into a big mess. Late on, this team felt financial pain and had to combine teams, so time and resource teams were combined, project and portofoli teams became one team -- and you could image how the supposed interfaces of these modules became dissolved. With micro service architecture, you are still bound by conway's law. How you design services will be partly decided by organizational factors, and if communications among teams are vague, slow and chaotic, communications among services will be vague, slow, and chaotic. Managing multiple services presents many challenges, and those challenges are not only technical, but also organizational (or managerial).
- Service evolution
Now that teams are organized around services, they have much more freedom to evolve their services. But no service lives in an island, they have to consider how to handle dependent services. If a service marches on its evolution path without consideration of its neighbors, the hell will break loose. Even with great technical design patterns, some form of communication, negotiation and compromise is required.
With a monolithic application, when something goes wrong, you know the culprit is that monolithic application. Ok, the culprit is some module (or modules) inside that monolithic application, so responsibility shifting, finger pointing can happen. With micro service architecture, even with good logging and monitoring systems, it is hard to pin down the culprit, and teams have to work closely together to troubleshoot.
- Architecture safety
Services have to be good citizens. If for some reason, they malfunction (e.g. can’t handle requests, dead-lock, hang, memory exhaustion etc), they need to let their neighbors know, they shouldn’t bring their neighbors down. This requires teams understanding and negotiating usage scenarios and communication etiquettes, for instance, how many requirement at peak, how to behave in peak time.
In bad days, I feel old, and it makes me happy to think that even in this rapid changing world, some laws still hold.