Things that I hate in software #1: microservices
Foto di Andre Hunter su Unsplash

Things that I hate in software #1: microservices

In the recent years, since probably 2018 or so, there is a fad in the software industry: the microservice architecture.

This fad led to several headaches for developers, broken promises and no real benefit, except for cloud vendors, that made money for software that made essentially nothing, except sharing messages. But let's dig it deeper.

Note: these are my opinions and I am available to debate and discuss it politely.

What this article is not

This article is not a criticism for a particular technology. I, myself, am using Spring Boot as a daily driver, although it is used for microservices in the Java world. I think that Spring Boot is a great piece of software and many more technologies are good. I focus on the architecture, not the specific library or framework.

This article is not about serverless architecture, although it follows an architecture similar to microservices. They are totally different and I will write an article later.

A brief history

Source: https://www.dataversity.net/a-brief-history-of-microservices/

Microservices start from the ashes of Service-Oriented Architecture (SOA) that was a hype since 1999 to 2005 and involves extensive use of SOAP protocol. But they share the same idea: separate the code to enable modularization. SOA however focuses its architecture on services, exposed via SOAP-HTTP while microservices do not focus on a certain technology, but on the modules themselves.

In 2005 the REST concept started to arise, although it is a bit different to the one we know today. JSON was only one of many representations of resources, while today it is the default. Even HTML was one kind of representation. From the end of 2008, with the arrival of super-fast JavaScript engines like V8 in Chrome and the AJAX increased usage, parsing JSON in client code was feasible. This led to the architecture we see today, where the client code is completely separated to the server code, that sparked the creation of pure front-end developers.

In 2011 the concept of Microservices arised slowly, although the fad started only later, my idea is that it happened in 2018 or so, but feel free to correct me.

This concept is followed by the DevOps movement, they share some concepts with totally different outcomes.

Criticism of the monolith

The microservice movement started with a fundamental criticism of the "monolith", i.e. a derogatory term to indicate the typical development that was made before the advent of microservices.

The "monolith" is a single application that has all the inner workings inside, typically in a single process. Clusters can be made, but with the same code running on different machines.

Why monoliths are bad (for microservices advocates):

  • lack of modularity
  • hard to deploy
  • if a thing breaks, the whole process breaks
  • not scalable (this is the best one IMHO)

So microservices takes the deconstruction path to destroy everything you thought was right at the time, from database interaction to communication with the clients.

Architectural style(s)

The term "microservice" is not clear from the start. But essentially this could be defined as an "application that focuses on specific tasks that is part of a larger architecture with many microservices, communicating with them through message passing".

But new questions arise:

  • how big are microservices?
  • what is the limit of task separation?
  • how messages are sent?
  • how do I collect data about microservices working?

So new technologies arise to solve these doubts.

Various interpretations

If you talk to microservices developers and advocates, you probably will find out different code writing styles. Anecdotal recounts tell of people:

  • create a single database for sharing information
  • using separate database for each microservice
  • communicating synchronously
  • using a message broker all the time to create asynchronous communications
  • not using transactions, solving problems with reparative actions
  • using transactions in each microservice (but not shared transactions)

We can go on, every people in microservice architecture will tell his/her story about how doing microservice development "the right way".

Synchronous processing

Synchronous processing among microservices is made through message passing. A technology I've seen that is mostly used is gRPC: it is very fast of an HTTP-based technology, but it is slower than (obviously) simple method invocation and lacks transaction management, as in competing technologies like EJB using two-phase commit.

Asynchronous processing

Asynchronous processing is frequent in microservices architecture, because it is hoped that applications use the CPU better than synchronous calls (although blocking threads do not consume CPU, but this is another story). So a message broker is introduced, with producers and consumer. A typical technology used in this field is Apache Kafka, that shares messages with an impressive speed, but again it is slower than simple method invocation, lacks message send-receive transaction management (if it fails on the receive side, no rollback on the send side can be made), is not good for large messages (RabbitMQ is better in this case, but its speed is not).

Separation of data

As I said before, database can be used in the microservice architecture as a shared resources OR as distinct resources and synchronized through messages.

The latter style is what seems more frequent: each microservice use its own database. If it needs to share data with some other microservices:

  • the sender microservice sends a message to the receiver
  • some data are duplicated and managed on both sides.

This leads to several problems regarding atomicity and redundancy.

Modularization to the extreme

In "monoliths" modularization can be made through the use of language constructs. For example, the typical layered structure in the applications:

  • domain
  • data access
  • services
  • controllers

Along with other constructs (such as interfaces and implementation in Java and similar languages), this can lead to modularization at the code level.

In microservices, these modules are taken to the extreme: depending on developers, it can arrive to the point where each service has an endpoint that can receive a message (either synchronous or asynchronous), even with different processes.

Working: before and after

Obviously this takes to a different way of development and problem solving strategies. Sometimes you feel that everything you have learnt up to entering a microservice-based project was wrong. I felt this way and it felt so wrong, because it is.

Here I focus on differences between the previous style and the microservice style, so everything that was the same (such as unit testing) is not mentioned here.

Coding

This is what you do in your daily life as a coder.

Before: You create your modules, for example (in Java) service interfaces, implementations, group them in packages, expose them in HTTP controllers.

After: You create your interface, the implementation and expose it as a receiver for a message, unless you determine that a service is purely internal, but that depends on developers.

Running the application locally

Before: start the application, or the application server with the application deployed in it.

After: start 5-20 applications at the same time. And a message broker.

Root cause analysis

Before: you see a stack trace for an error, then you determine the real cause by looking at the stack trace itself. With the help of a debugger you can reach to the fix.

After: you see a stack trace. You try to understand if it is a problem of the microservices raising it, or the real cause is from the caller. Or the caller of the caller. Or the caller of the caller of the caller. To do this you have to look at the logs, for which you added correlation IDs to determine a single flow of messages. Oh and your logging level is TRACE.

Debugging running instances

Before: A ticket is created telling that there is a bug. So you start to see the log for possible problems. You try to replicate the problem locally by running on your machine. Increase the level of logging on the production machine and ask the customer to repeat the process. In the end you solve the problem.

After: A ticket is created telling that there is a bug. So you start to see the log collecting software (e.g. Kibana), hoping that you've put the correlation ID everywhere. So you try to understand the flow, correlating the message on the log with the exact line of code. Then you try to recreate the problem with the same data on your machine, running (in debug) again 5-20 applications with a message broker on your local machine. Put several breakpoints in the various applications, hoping for the best. You solve the problem (with much more time spent).

Atomicity (transactions)

Before: If you wrap your service in a transaction, you are sure that, if something fails, no write has been made.

After: For every action you code, you have to code also the counter-action, in case something goes wrong. Introduce the SAGA pattern, that will make you cry.

Suppose that microservice A does something on the DB then call the microservice B to do something else on the DB. You would like that, in case in microservice B something goes wrong, you want to rollback the actions done in microservice A.

So the flow would be:

  1. A does something in the DB
  2. A sends a message to B
  3. B tries to do something on the DB but fails
  4. B rolls back the actions
  5. B sends a message to A to tell it to rollback the action done at point 1.
  6. A receives the message and rolls back the operation.

Welcome to the reinvention of the wheel!

Misconceptions introduced by microservice advocates

Microservices advocates tell a lot of misconceptions about "monoliths" that in theory they want to solve. Not only they are false, but the solutions are even worse than the thing they want to solve. Let's take some examples.

Scalability - Monoliths do not scale well. Go tell this to cluster provider that was there since the existence of the Java EE application servers.

Speed - Monoliths are slow. So you replace method invocation with message passing, hoping that it will speed up. Spoiler alert: it won't.

Modularity - Monoliths are not modular. This essentially depends on developers: common Object Oriented Programming patterns lets you achieve modularity. Do not confuse modularity with separation of processes.

Deployment - Monoliths need to be deployed altogether. This is in fact true, but it is a good thing: DevOps operations are much simpler to code.

(Broken) promises

They told you it is will be better, they told you it's the right thing to do... No it's not. Let's see all the promises not kept.

Separate deployments: true in theory, but in fact most microservices depend on each other. Because messages must be exchanged between microservices, and if you change the send part, you have to change the receiving part. There is no escape.

(Horizontal) Scalability: abused term to tell that you can create infinite instances of a microservices. This is true, but it is far from useful.

Speed: As a side effect of using multiple instances of microservices, you can control speed of the process. This is not true because speed varies depending on workload and you can achieve potentially a better throughput. The problem is that most of the time is spent sending and receiving messages.

Parallelization: Potentially multiple instances of a microservice can treat large workload in parrallel. The problem is that microservices advocates do not do any kind of study for scalability (as in High Performance Computing) with varying workload, so it's simply an assumption.

Separation among teams: microservices are kept so small that interaction between teams is mandatory. The ideal situation in which each microservice team can work independently to the others, is simply a pipe dream.

Faster data treatment: Most of the time, in microservice architecture, NoSQL database are used. Although they are faster, they sacrifice the consistency for speed. Moreover, all the speedup that you obtain is deleted by the message passing inherent slowness.

Separate databases are better: This leads to lack of atomicity and, most importantly, a large amount of redundancy. A thing that any database teacher would yell at.

Can it be saved?

Not everything about the microservice architecture is bad, some principles are created with the best intentions.

In my opinion, the count of microservices should be reduced a lot. Instead of creating microservices for each module you create, focus on the workload, on needed speed, on possibilities of parallelization. Identify the algorithms of your project, see if they can be split and if they benefit from that split.

Much of the High Performance Computing lessons must be learnt by microservices advocates. Study scalability, with varying nodes, leaving the same amount of workload and with varying workload. Study the Amdahl's law (it's easy), remember that you cannot get a speedup more than what is possible.

And probably, most importantly, before following the microservice path, create a monolith first. In the end you will have a functioning project that can be optimized later.

Igor Melo

Software Developer

1 年

Great article! You described the pain I've suffered in projects with excessive microservices with no evidence that it brought any positive value like maintainability or performance... But the cloud provider was very happy with our costs

Davide De Rosa

Maintainer @ Passepartout

1 年

This dogmatic take on SRP (Single Responsibility Principle), which microservices is a byproduct of, has been intoxicating the field for years. No, not every piece of software can be treated as an island, and exaggerating this approach for the sake of adhering to a trend, brings up all the problems that you mention. Good analysis!

要查看或添加评论,请登录

Antonio Petrelli的更多文章

  • Netty vs Loom: the (un)final fight

    Netty vs Loom: the (un)final fight

    Introduction Project Loom has been released, although in a preview form, in the early access version of JDK 19. So it's…

  • JDK's project Loom: an unscientific evaluation

    JDK's project Loom: an unscientific evaluation

    Something interesting is happening at the JDK, there are various interesting projects that are in the works. But…

社区洞察

其他会员也浏览了