This idea came to me because I realized that keeping a request-response chain throughout your microservices is terrible practice. I think it’s because it means you’re keeping your old ways of thinking inside the monolith and made them distributed. Synchronous interactions make tight coupling between microservices more obvious. The more coupled the microservices are, the more difficult you make it for yourself (or your org) because you have to make each microservice implement more and more resiliency methods. Microservices should have resiliency methods, but also be independent enough to work without any other. Keeping the request-response starts making timeouts grow out of control, and you end up with timeouts of 10s, and nobody cares, or understands why. It’s just another second here, and there, it will slowly but surely keep growing. You have to rethink your interactions and make them asynchronous when possible or go back to the drawing board on how your system is behaving. Keeping the synchronicity to a minimum, only or mostly for customer-facing interactions. If you’re doing request-response you should not do layers of services calling them one after the other; instead, the client should consume each independently. For example, think how adding a product to the cart work on Amazon. When you click ‘add to cart’ the response is synchronous, but after a few milliseconds (or sometimes “instantly”) the cart shows a number 1 (if you had not previously added any items). So that interaction is synchronous for the user, but it also triggers an asynchronous operation to check stock, seller, etc. probably.
Perhaps don’t leave the monolith just yet
You should keep the monolith, and work better into modularizing the domains inside the monoliths. Reducing coupling as much as possible, and by understanding how every domain interacts. You can start to rethink them to avoid a linear way of calling all of them to perform actions within your system. You could also, when the modules, libraries, etc. are ready. You could publish them but keep a monorepo, a la google. Having an enormous test suite that guarantees that contracts are being fulfilled and just using the monolith as an integration layer for every domain available.
I still have no answer on how to correctly solve this, but only think about the preference of choreography over orchestration. And don’t create a tightly coupled system where an action by the user ends up doing a chain of 10 requests (which all are synchronous calls). Have you faced these problems? Have you done mostly asynchronous interactions? With Kafka, RabbitMQ, etc? You can also watch this excellent talk by Oliver Gierke at SpringOne last year. Or this talk by Ben Christensen.