“In the new world, it is not the big fish that eats the small fish, it’s the fast fish that eats the slow fish.” — Klaus Schwab.
Unfortunately, microservices are today the development standard for any large product. Why is that? Because the market has become overloaded with competitors and not only big companies compete with each other but everyone participates in the race. Everyone wants to deliver new features in their products quickly, frequently, and reliably. And the big products have to grow as fast as the small ones. But usually big products are much more complex in terms of communication between team members, deploying, fixing bugs and so on. As in many other situations, the solution is, as always, the creation of an additional level of abstraction — decomposition of teams, encapsulation of the logic of individual components, and decoupling — some high-level SOLID.
“This is the Unix philosophy: Write programs that do one thing and do it well. Write programs that do one thing and do it well.” — Doug McIlroy
Individual microservices are only independent and decoupled if they can evolve independently. Isolation is a prerequisite for autonomy. Only when services are isolated can they be fully autonomous and make decisions independently, act independently, cooperate, and coordinate with others to solve problems.
This gives each service the freedom to represent its state in any way it wants, and store it in the format and medium that is most suitable. Some services might choose a traditional RDBMS, some a NoSQL databases, some a Time-Series database, and some to use an Event Log through techniques such as Event Sourcing and Command Query Responsibility Segregation (CQRS).
Also, if synchronous communication is used between the services — even if it is only for a subset of the services — you are introducing strong coupling and are putting yourself in the hands and mercy of the other systems you work with. For example, REST is most often synchronous which makes it not a suitable default protocol for inter-service communication. So asynchronous boundary between services is necessary in order to decouple them, and their communication flow, in time — allowing concurrency, and in space — allowing distribution and mobility. This is a major problem with distributed systems — the complexity of asynchronous communication while some services may not be available. Using it we are moving from the ACID world (Atomicity, Consistency, Isolation, Durability) to the BASE world (Basically Available, Soft state, Eventual consistency).
As you can imagine, the deployment of dozens of small services involves much higher overhead than delivering a monolith. Each service requires load balancing, separate CI/CD pipelines, logging, and process monitoring — the same things you would configure once for a monolith. This becomes even more complex and confusing when you have to scale services independently and have different technology stacks. This leads to incredible levels of flexibility, responsiveness, and efficiency, but at the same time, it also comes with huge operating costs in terms of support. However, with the rise of containerization, the advent of k8s and the development of Platform-as-a-Service, creating a reliable and manageable platform for microservices has become very simple. Without al those new tools even a single service can take over entire teams of IT operations specialists.
So what do we get in the end?
- Microservices allow us to structure our systems in the same way that we structure our teams, sharing responsibilities among team members and giving them the freedom to own their work.
- Microservices allow a team to implement a new feature or make changes without having to rewrite a large portion of the existing codebase.
- The microservices make it easier for an app to scale and change with increased demand.
- it’s easier to choose the technology stack which is best suited for the required functionality instead of being required to take a more standardized, one-size-fits-all approach.
- Services can change direction without massive costs
But microservice architecture brings its own challenges and tradeoffs. While individual services become more robust and less complex, the overall system takes on the many challenges of distributed systems at every level. Each service has its own overhead, and although that cost is reduced by an order of magnitude by running in a PaaS environment, you still need to configure monitoring and alerting and similar things for each microservice. Microservices also make testing and releases easier for individual components but incur a cost at a system integration level.
Despite the challenges, microservices are here to stay because they map better than anything else to the software landscape of the future: distributed processing, parallel development, platform-as-a-service deployment, and ubiquitous use.