Real Life Case Study: Synchronous Inter-Service Communication in Distributed Systems

The Problem

Inter-Service Communication

 I was working on a project where I was a part of a team responsible for service B, which was one service of microservices A, B, C & D. 

And one day the business required that service B should process (using the business logic of the service) an entity that existed in the domain of service A.

Therefore, a question arose; how should we manage to do it?

Just Fire and Forget, right?

Now the services were set up to communicate using Apache Kafka. So, normally the initial thought was that service A should publish a message with the entity that needed processing and service B should listen to this topic, process the message and publish the processed response to another topic that service A is subscribed to.

But wait, this has to be a synchronous job.

Service A Can't Wait, Won't Wait

Okay so it turns out that service A isn't that relaxed and isn't into the whole we'll-get-back-to-you-soon attitude and wants the response immediately as soon as it is done processing. 

So, in a nutshell the business has decided that the response must be synchronous.

Now I know the synchronous word terrifies the zealots of microservices and a lot of these folks believe that every service should just calm down and not be in a rush all the time but sometimes business logic defies all the standards and oh boy if you don't believe that to be true just wait for my upcoming posts for some shocking real-life examples.

Plus, some microservices books have talked about blocking synchronous communication between microservices before and have not deemed it as bad practice such as Building Microservices, Microservices Design Patterns in .Net & Embracing Microservices Design.

But hey let's say that synchronous communication has its flaws and people don't want to use the S word around our project, can we somehow borrow the logic needed from that service? 

Packages Repository Is Not the Full Package

One way that was actually implemented in another project I have worked on is to use a repository or an artifact management product like Sonatype Nexus to host your packages on, then reference these packages (which were nuget packages) in any other service that needs this packaged service. 

So, we can package the needed part of service B, host it on Nexus (maybe as a part of the deployment pipeline), then finally reference it in service A and voila! Your problem is solved.

Oh, but wait, me and my team just made a new build where we deployed some enhancements (fixed a fatal issue) to service B and we have to knock on the team working on service A and ask them politely to update the service B package.

But you know what's also funny? The team working on service C liked the Nexus idea so much that they decided to borrow a service from service A, so now actually after service A updates the service B package, service A should also ask the team working on service C to update their service A package. 

And I know this may seem like science fiction or a cautionary tale to you, but this is actually a mega-project that happened in real life. 

Well, the team of service B is socially awkward and can't keep asking service A team to update their packages. So, now what? Should we go for good old HTTP requests? Is it that simple?

KISS HTTP/S Requests

If the word synchronous causes microservices lovers to be terrified, then I'm pretty sure HTTP/S is their biggest nightmare and I actually want to argue with that.

Sam Newman mentions HTTP in his book Building Microservices in Chapter 4 as one of the ways of synchronous communication between microservices. He also says "For simple microservice architectures, I don’t have a massive problem with the use of synchronous, blocking calls. Their familiarity for many people is an advantage when coming to grips with distributed systems."

Using restful APIs communication between microservices isn't really Voldemort so that everyone who works on a microservices project/s should avoid mentioning its name. You have a business that requires immediate response, thus synchronous communication, thus...HTTP/S requests. 

Sometimes you really got to K.I.S.S. the solution so that you don't spend days looking for a complex way to do something simple. When it comes to synchronous responses there is no more classical approach than HTTP/S requests and I think this approach is simple and widely supported, but it can be slower than other methods due to the overhead of HTTP.

So, is that it? HTTP? Well, if you prefer...there's HTTP/2.

HTTP Chapter Two: gRPC

Whether gRPC is "better" than HTTP depends on the specific use case. Both have their strengths and are suited to different types of applications. Here are some factors to consider:

1. Performance: gRPC uses HTTP/2 for transport, which is more efficient than HTTP/1.1 used by most RESTful APIs. gRPC also uses Protocol Buffers by default for message serialization, which is more efficient than JSON or XML in terms of both size and speed. So, for high-performance applications, gRPC can be a better choice.

2. Streaming: gRPC supports bidirectional streaming, meaning that both the client and server can send data to each other independently. This is not natively supported in HTTP/1.1, which can only send one request and receive one response at a time.

3. Contract-First API Development: With gRPC, you define your service contract first using Protocol Buffers. This contract is used to generate client and server code, which can help ensure that both sides stay in sync.

4. Language Support: gRPC supports a wide range of programming languages, including C#, Java, Python, Ruby, and more. This can be an advantage if your services are written in different languages.

However, there are also some potential downsides to gRPC:

1. Browser Support: gRPC is not natively supported in all browsers, and it doesn't work with standard HTTP/1.1 proxies. This can be a limitation if you're building a web application.

2. Ease of Debugging: Because gRPC uses binary serialization, it can be harder to debug than HTTP/1.1 with JSON or XML.

3. Learning Curve: gRPC and Protocol Buffers can be more complex to learn and use than traditional HTTP/1.1 and JSON.

In conclusion, gRPC can offer significant advantages in terms of performance and functionality for service-to-service communication, especially in a microservices architecture. However, for web applications or public APIs, traditional HTTP/1.1 and JSON might be more suitable due to their wide support and ease of use.

But wait I know some people still want to use the asynchronous base to establish synchronous requests so is this doable?

Sync-Asynchronous

There are patterns you can use to achieve synchronous-like behavior with event-driven communication, such as the Request-Reply pattern.

In this pattern, the caller publishes a request event and then waits for a reply event before continuing. However, this can add complexity and might not offer the same performance or reliability as a truly synchronous communication method.

But the book Building Microservices recommends using time-outs with this approach to avoid issues where the system gets blocked waiting for something that may never happen. Which to me seems that we're actually trying to reinvent the wheel just to avoid using asynchronous calls.

But you know what bothers me? The whole system was based on asynchronous communication and suddenly the business decides that these 2 specific services need immediate synchronous communication between them? Something's not right...

Is the System Broken?

Well sometimes it all starts like this. The business just keeps adding dependencies between some services that were initially established as independent and suddenly one day you find out that your services are more coupled than a monolith solution and believe me it's all downhill from here because you have not gained the simplicity of a monolith solutions nor the loose coupling of microservices. 

So, if service A was this dependent on service B maybe it isn't really a good idea to separate them or act like they're totally independent domains but with just a little bit of dependency. Because the business seems to believe that service A cannot function correctly without service B so are they really different domains?

If you want the black/white opinion, it's either your service is dependent or independent there is NO in between.

In the end, usually services start as monolith solution and then they branch out into microservices so if this dependency was clear from the beginning maybe this problem would've been avoided by choosing not to divide the two services. 

But that didn't happen. The dependency was not clear from the beginning. So, it really comes down to choosing one of the choices I mentioned above and maybe there's another solution that I do not know about so if you have one that is not mentioned in this post, please share it with me.

I hope this case study was useful to you and wait for more case studies in upcoming posts.

Comments

Popular posts

Google & Microsoft Token Validation Middleware in ASP.NET Applications

Publish and Consume Messages Using RabbitMQ as a Message Broker in 4 Simple Steps 🐰