Original Sin of Microservices, Part 2
Tags: tutorial, distributedsystems, microservices, architecture
Published: 2022-03-28 04:08:59

Last time, we talked about [8 fallacies of distributed computing](https://lazypro.medium.com/original-sin-of-microservices-part-1-90461ddcefb) and why our systems end up being more complex than we ever imagined. In our last conclusion, we mentioned that two nodes already have a lot of aspects to consider, let alone a microservice architecture.

Microservice architectures have many additional dilemmas to face. Therefore, in this article, we will introduce what are the challenges of designing a microservice architecture.


## 9 challenges of distributed architecture

When designing a microservice architecture, there are 9 things that must be carefully considered, and these 9 things are the source of the complexity of microservice architecture. In order to design a microservice architecture correctly, there are many factors that must be aware, such as data consistency, architecture scalability, system availability, etc., in addition to avoiding the fallacies mentioned in the previous article.


### Service Granularity
![Macroservice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdc1rdl4q23cvvspvxi9.png)
 
![Microservice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sh39arctvbyc5koo71bi.png)
 
When we want to implement a notification service, suppose that there are three types of notifications, email, sms and mobile notifications. So should the granularity of this microservice include all three types of notifications, or should the three types of notifications be separated into a standalone microservice? How to make the right choice?

There are several points to deliberate. First, will these codes need to be changed very often? If not, then it seems feasible to put all notifications into the same notification service. On the contrary, if the content of emails needs to be changed very often, then perhaps emails can be separated out into a standalone microservice.

In addition, if sms has a large number of notifications, then sms may need to be horizontally scaled independently, i.e., sms can be a separate microservice.

How do you determine the correct granularity of microservices? It's hard, isn't it? This is the first challenge.

### Shard Functionality

> Shared service vs. Shared code

![Shared service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqeqbqgup0r9q9lj2qz7.png)
 
![Shared code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2i444myi69loztuj3gst.png)
 
When there are shared functions, how do you decide where to place them? For example, when A, B and C all have an authentication function, should there be a microservice for authentication or should the authentication code be replicated in each microservice?

There are also some aspects to think about here. If there is a shared authentication service, who will authenticate the authentication service? When the authentication service goes down, will it result in a single point of failure (SPOF)? What happens when more and more services need to be authenticated and the authentication service can't hold up?

On the other hand, if the code is copied to each service, how can we make online improvements if the logic of authentication has changed? In fact, the same problem can happen to data. When there is data needed by three microservices, should the data be shared with each other in a separate database or duplicated to each service?

Remember the ideal microservice mentioned at the beginning? No sharing of data. So here, I prefer to use the second approach, replicating the code instead of sharing the service.

### Communication Protocols

> Synchronous vs. Asynchronous

![Synchronous](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nouxsqrboq8bkg65f9ya.png)

![Asynchronous](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2b1oi52iywjlyle455sr.png)
 
Should the communication between two services be synchronous or asynchronous? There are many factors to consider.

Asynchronous has the obvious benefit of being more efficient and less coupled with each other. When there is a need to scale horizontally, each microservice can also scale according to its own needs.

But synchronization also has the advantage that `Service A` can respond more quickly when `Service B` fails. In other words, synchronization can have higher consistency.

There is no standard answer between synchronous and asynchronous, it depends on the context.

### Workflow Management

> Orchestration vs. Choreography

![Orchestration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/537e698nujhwufbs6xw0.png)
 
![Choreography](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ysrsiqfh004lfa2afa0.png)
 
Again, these are two very different types of workflow management. Similar to synchronous and asynchronous above, when orchestration is chosen, it is easier to control the entire workflow, and when any task fails, the top `Service A` has a way to know and take the correct action, i.e., orchestration makes it easier to get higher data consistency.

On the other hand, choreography can have better scalability and availability because of the low coupling between each other. Even when there are functions that need to be implemented, choreography is more productive because it does not need to be initiated by `Service A`. Each microservice communicates with each other through messages and only needs to implement new message processors.

### Monolithic Decomposition

> Component-based vs. Tactical forking

When we want to decompose a monolith to microservices, there are two different strategies that can be used. Component-based decomposition is a traditional domain-driven development that first correctly recognizes the function and size of each component, then analyzes the dependencies between the components, and finally tries to decompose the components according to a domain-based manner and generate domain-sized microservices.

In contrast, Tactical forking is a completely different process.

![Tactical forking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82oundbw15azywos88p6.png)
 
First insert a proxy between the original client and the monolith. Have you noticed? The topology is not immutable, in order to compose the microservice, the topology is changed.  Then, the original monolith is replicated exactly, and the two different functions are pointed to different targets in the proxy. Finally, according to the characteristics of the two functions, the original monolith is individually reduced to produce two different microservices.

Similarly, the two different strategies also have their own advantages and disadvantages. If component-based decomposition is used, there is a longer lead time, but the whole system can be decomposed with the right domain perspective, and in fact, the process must involve refactoring. In opposite, tactical forking does not have this refactoring process, but in return it is a much faster modification process.

### Contract Management

> Strict vs. Loose

Contract management refers to what kind of data format should be used to communicate between two services? If you use a loose contract, such as JSON or XML, the coupling between the services will be tighter, and it will not be easy to manage the versioning and achieve forward-backward compatibility. But the advantage is that it is easier to develop, especially since JSON is already the main data format for frontend and backend communication, so JSON will also work more naturally with the service.

On the other hand, using a strict contract, such as Avro or protocol buffer, then the services only need to understand each other through the schema. The old and new versions of the service can also coexist easily.

### Database Style

This is also a very popular topic. First you have to choose from PACELC, then ACID or BASE, and finally there are various options depending on the storage structure, such as K-V,  columnar, row-based or graph.

The biggest drawback of choosing a relational database in the past was its lack of horizontal scalability, but recently there has been a new breakthrough with more distributed SQL options. But again, there is no perfect solution, you still need to understand what distributed SQL can do and what it lacks.

Behind each of these choices is a lot of experience and domain knowledge, and it's really hard.

### Architecture Style

Choosing the architecture type is interesting. There are many types of architectures alone, such as service-oriented architectures, microservice architectures, event-driven architectures, and so on.

In fact, a system does not have only one architecture style, but usually a hybrid system. There are many aspects to determine when choosing event-driven and when choosing microservices for a "function".

### Distributed Transaction

The last challenge is distributed transactions. I mentioned in [my previous article](https://betterprogramming.pub/design-distributed-transaction-with-practical-examples-7b1d93fddb63) that there are 3 factors should be considered when designing a distributed transaction.

- Consistency: Atomic vs. Eventual Consistency
- Communication: Synchronous vs. Asynchronous
- Coordination: Orchestration vs. Choreography

Therefore, there are 8 combinations, each of which has a different structure also pros and cons. According to the conclusion of [distributed transaction introduction](https://medium.com/interviewnoodle/distributed-transaction-introduction-1cd105c830a2), we should choose between eventual consistency and asynchronous, but then we still have to choose between orchestration and choreography. 

![Parallel saga](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll6mnps7fzr5xsupkqop.png)
 
![Anthology saga](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdyc6pppc885folj3sdx.png)
 
In the conclusion of the last article, it was stated that if you choose choreography you can get a better decouple and better scalability, but there will be a higher complexity of implementation. This is because it is very difficult to maintain the workflow of the whole transaction in choreography.

## [Complexity is killing software developers](https://www.infoworld.com/article/3639050/complexity-is-killing-software-developers.html)

After discussing so many challenges and fallacies, I'm sure you'll agree how difficult and complex it is to design a microservice system correctly, right? So, in recent seminars, the topic has slowly shifted from microservices implementation to microservices challenges.

Complexity is the pitfall of modern software architectures. But how do we evaluate complexity? We've talked about a lot of complex stuff today, so I'll just offer a simple suggestion to measure it from my own experience.

> When you want to make a change but are very hesitant because you don't know the consequences of doing so.

Then, the complexity is pretty high.



Are there any specific evaluation criteria? Yes, there are. One of the methods is to analyze the degree of coupling. I have introduced instability in [a previous article](https://betterprogramming.pub/how-to-prepare-a-design-review-like-an-expert-85d2ab85d7f5). This is one of the formulas to evaluate the coupling degree.

![](https://i.imgur.com/4tqy4tk.png)

## Conclusion

We talked a lot about the challenges and problems of designing a microservice architecture, or a distributed system. Every architect has had a journey from believing in microservices to questioning them. In order to prove the change of architect's mindset, I list the sessions of *Worldwide Software Architecture Summit'22* I cited in these two articles, and also provide you with a reference.

- Software architecture in a non-enterprise world - a practical approach
- Correctly Built Distributed Systems
- What not to do on your next cloud-native architecture
- Software observability: Visualize Code Quality at Scale
- Designing Pragmatic Observability that Works: Avoiding Pitfalls 
- Complexity is the Problem
- Microservices anti-patterns and pitfalls