The different faces of microservice communication

jeastham1993

James Eastham

Posted on December 8, 2019

The different faces of microservice communication

Microservices are the 'in' way of developing software at the moment, and have been for quite a while now. They certainly seem to be past the 'oooo new shiny thing' stage that is prevalent in software (I'm looking at you javascript frameworks).

I've spent a lot of my working life recently either migrating monoliths to microservices or building new microservices from scratch. One of the biggest challenges is always communication.

In a traditional monolith, different components of your app communicate with relative ease and simplicity. A method call here, a using statement there. Everything is nice and simple.

Take the below rather contrived example:


using MySampleApp.ProductModule;

namespace MySampleApp.OrderModule
{
    public class OrderService 
    {    
        private readonly IProductService _productService;

        public OrderService (IProductService productService)
        {
            this._productService = productService;
        }

        public void PlaceOrder(Order newOrder)
        {
            this._productService.ValidateProducts(newOrder.Products);

        }
    }
}

Enter fullscreen mode Exit fullscreen mode

A new order is placed, and the products in that order need to be validated. The app has one big shared codebase. The product service can simply be injected into the order service and used. Simple.

The complication of microservices

Imagine the same scenario, but in a world where the Order and Product services are completely separate applications. In their truest sense, microservices are completely self-contained processes that have no direct relation to each other.

Does the monolith still sound so bad?

So how can these two disparate processes, which if true to their design should have no direct link to each other, communicate. Luckily, there are quite a few options.

Good ol' REST

REST has been around for a very long time. It's a simple protocol for communication via HTTP requests.

There are some standards for the requests themselves (GET, PUT, POST etc..) but the content of a request is decided by the service itself.

The Good

REST is a well documented, well standardized and extremely common way of communication. I'm confident almost everybody reading this post will have interacted with a RESTful API at some stage in their development career.

Almost every language has built-in support, and there are 100s of 3rd party libraries that add more features to the standard HTTP calls.

It's also pretty decoupled. A URL for the location of the API is all that is required to get off the ground. Although in my personal opinion it's almost too decoupled for its own good some times. Which leads quite nicely on to

The Bad

The contents of a REST response is completely open to change by the API itself.

Let's say the order team expects a property named 'productCode' in the API response. The product team has a big refactoring/cleanup session and the property is renamed to just be 'code'. This is perfectly valid, but suddenly the order service will stop functioning.

From experience, these kinds of issues are always a pain to find and can go unnoticed for quite a long time depending on the use case.

There is also the matter of the endpoints themselves. In a large application, there could be 100's of these microservices. If all of them communicate via HTTP that is a lot of settings/endpoints to keep track of.

An API gateway could be used to give a standard base endpoint that all requests are routed through. That does then add an extra layer of complexity and a single point of failure, but offsets some of the complications of having potentially hundreds of different endpoints.

Event Bus

Event buses (or message brokers) are a pattern for mediating the communication between applications. They minimize the awareness different services have each other and are by far the most decoupled way of managing inter-service communication.

In our example scenario, the order service would raise a 'ValidateProducts' event to the event bus. This event can either be synchronous (request/response) or asynchronous (publish/subscribe).

The product service, would connect to the EventBus and listen for all events matching a set of criteria. In this instance, any ValidateProducts events. When an event is received, it is handled and marked as complete.

If a response is required, the event handler returns the content of the response to the event bus which then manages the routing back to the original requestor.

The world of event buses is magically decoupled!

There are many different event bus providers, of which I'm not going to get hung up on in this post. But there are a number of considerations

The Good

In my opinion, an event bus gives the best level of decoupling. All of your 10/100/1000 services communicate through a common medium, whilst not having any knowledge at all of each other.

It also gives an intuitive way of structuring your code. The order service creates an order and then publishes a 'NewOrderAdded' event. It doesn't need to care who/what is interested, it is solely concerned with creating the order and letting the world know about it.

The Bad

The downsides are very similar to using REST. Whilst more decoupled, the problem of a structural change still stands. The events at either side are expected to have the same object model. If one is to change, the same problem arises.

As far as common .NET implementations go, the event is serialized as JSON and passed around. If JSON property names change, then the property in question will not be parsed.

Having all events processing through an event bus, also gives a single point of failure. Most providers support clustering and failovers, but it is still a single place that can go wrong.

gRPC

gRPC is a relatively new kid on the block, and of the 3 options I'm covering here it is the one I'm least familiar with.

It is an open-source remote procedure call system originally built by Google. HTTP/2 is used for the transport and it supports protocol interfacing as standard. For any fellow .NET devs, think language-agnostic WCF.

A gRPC service generates a .proto file. These files can be consumed by services and act as an interface description between the caller and the remote server. The proto file covers method signatures, variables and response types. All in one single file.

Almost all popular programming languages now have support for gRPC implementations.

The Good

My favorite thing about gRPC is strict interfaces. To call a method on a gRPC service, the corresponding .proto file must be consumed and therefore the way in which the method is called is explicit. There is no room for changed property names or problematic serializations.

It's also lightning quick. Services are communicating directly with each other, the data is tightly packed and sent using HTTP/2 which has a number of compression based benefits.

The Bad

Time. This is probably a personal thing more so than a reflection on the protocol itself. But if I take two services written in .NET Core, one using REST and the other with gRPC, the gRPC service is considerably more cumbersome to put together.

As a .NET developer, having the forced constraint of a gRPC service only ever running in HTTPS can make deployments difficult. Especially for a service running without any external access, within clients' existing infrastructure.

Similar to REST, it also has the complexion of the explicit endpoint for each service being known. So for the order service to make a request to the product service, the order service must know the exact location of the product service. Manageable, but scale can be tricky.

In Conclusion

So there are three different methods I've used on my journey through the world of microservices.

All have pros and cons, and between the three there is enough difference to negate almost all of the cons. And that, I believe, is the solution to the inter-service communication debate. A combination of the three based on the scenario.

For something asynchronous that has already happened (NewOrderCreated) use an event bus, for speed use gRPC and for simplicity use REST.

All the opinions in this article come from my personal experience building software, but I'm always looking to learn more. If there's anything I've missed or points you'd like to add, please drop a comment below. I love a good discussion!

💖 💪 🙅 🚩
jeastham1993
James Eastham

Posted on December 8, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related