One of my pet bug-bears is inter-service communication policies adopted by companies when it comes to microservice architecture. I don’t think I am ever going to understand why a company might mandate a one size fits all approach for their architecture.
Communication between services has FOUR possible approaches according to Mark Richards, author of the book Software Architecture Patterns.
Each approach has strengths and weaknesses and as such they should be used when appropriate for the given use case. And even when an approach has been decided upon, and, again, mandating one technology for all times a given approach is used is as far from ideal.
One more thing to mention, inter-service communication can take place in the same service, the same company, or across the internet between two completely unknown entities.
Sharing datastores
The very first approach that I am going to mention is the approach almost everyone is familiar with, is the easiest to implement, and is the worst possible approach.
It’s super easy for two services to share data via some datastore (SQL/NoSQL/whatever), and tempting, the datastore typically handles the concurrency issues for free, the data is stored and retrieved in a well understood manner (the data types), and most programming languages have strong support for a lot of the popular databases.
It’s a no-brainer you might think. But, there are important issues that negate the value of shared datastores. First and foremost, ownership. If a producing service wants to change the data that is inserted into the table or the schema, or even the nature of the datastore, there needs to be a discussion with every reading service on that change. This is a trap! The more the service grows, the higher the coupling, the worse the performance, the harder to re-architect.
File transfer
This next approach isn’t intuitive, but is still prevalent, especially between financial institutions, and software providers.
Data is collated and written to a single file, that is made available for other services to consume. It’s fairly easy to share the file, with a number of possible protocols, all supported by most programming languages, available to do the task (eg. (S)FTP, HTTP(S), SCP, and so on).
The advantage of this approach is that it allows a large amount of data to be transferred between services easily, and in some cases is the only way (think images for example, or various Linux distribution iso). It’s very common amongst financial institutions to provide files that describe daily reconciliation information (Daily activity and Daily Dispute files), but this is a legacy issue rather than an artefact of the data to be shared.
The biggest disadvantage here is communication, how does the producing service indicate that the file is ready for transfer, or has been finished being written to. Further, how do services communicate errors (eg. permissions issues, network issues, or even that the wrong file has been provided). Corruption and transfer issues exist, which require mitigation strategies (checksums usually).
Also there still needs to be agreement on the layout of the file and the filetype, but it’s perfectly acceptable for the producer to change those, as long as they are clearly communicated.
You’re going to use this approach when you have a large piece of data that cannot be cut into discrete meaningful pieces of information that can stand alone.
Remote Procedure Invocation (RPI)
Again, a very common approach, but not without its problems.
When you expose a RESTful API, or a gRPC endpoint, you are providing a method for services to invoke a procedure within your service.
The interface is usually supported by most languages (especially if you expose HTTP endpoints), and is faster than file transfer, in that you are only requesting a subset of the possible information.
The biggest disadvantage of this approach is coupling. The information expected from the caller and provided by the callee is set such that when the callee wishes to change that contract it needs to provide a way for the callers to know this, or attempt to make changes backward compatible.
Further, if the call is dropped, for whatever reason, neither end is any the wiser where the problem is. The callee won’t know that its response wasn’t received by the caller, the caller won’t know that a response was even sent.
Finally, when a call is made, the caller and the callee are blocked by that call. That is, the caller has to wait for a response from the callee, and the caller is busy satisfying the call. This is, of course, mitigated by concurrent processing, but that’s only a partial fix, in that something is waiting whilst something else is busy.
Messaging
This is, by far and away, the best approach for inter service communication BUT it’s not without its drawbacks and there is no way that some carte blanche policy of “all services must use messaging to communicate” is appropriate. Even if it were, the discussion on which messaging system to use is another one where “one size fits all” is not going to work.
Message Queues create an asynchronous communication between the services. A service sends some information to a message queue, which the queue then passes onto a destination service.
The service that sends the initial piece of information to the other service, via the message queue does not know when the call will even be heard, let alone fulfilled. But, it does know that its call will be heard (as long as the messaging service doesn’t lose it…).
Generally speaking Message queue systems provide APIs that are accessible from several languages, but it’s not given that your choice of programming language will be supported by the message queue system you desire.
After reading this, it should be screamingly obvious that if you mandate one approach over all others, or one technology for a given strategy, you’re doing it wrong (quite frankly). The communication between services needs to be tailored to the use cases. There is no one size fits all. There is, instead, cases of strengths and weaknesses that need to be carefully weighed against the requirements. The same goes for the individual technologies, gRPC vs REST vs GraphQL.