Blog

Microservices: coupling vs. autonomy

18 Mar, 2015

Microservices are the latest architectural style promising to resolve all issues we had we previous architectural styles. And just like other styles it has its own challenges. The challenge discussed in this blog is how to realise coupling between microservices while keeping the services as autonomous as possible. Four options will be described and a clear winner will be selected in the conclusion.
To me microservices are autonomous services that take full responsibility for one business capability. Full responsibility includes presentation, API, data storage and business logic. Autonomous is the keyword for me, by making the services autonomous the services can be changed with no or minimal impact on others. If services are autonomous, then operational issues in one service should have no impact on the functionality of other services. That all sounds like a good idea, but services will never be fully isolated islands. A service is virtually always dependent on data provided by another service. For example imagine a shopping cart microservice as part of a web shop, some other service must put items in the shopping cart and the shopping cart contents must be provided to yet other services to complete the order and get it shipped. The question now is how to realise these couplings while keeping maximum autonomy.  The goal of this blog post is to explain which pattern should be followed to couple microservices while retaining maximum autonomy.
rr-ps
I’m going to structure the patterns by 2 dimensions, the interaction pattern and the information exchanged using this pattern.
Interaction pattern: Request-Reply vs. Publish-Subscribe.

  • Request-Reply means that one service does a specific request for information (or to take some action). It then expects a response. The requesting service therefore needs to know what to aks and where to ask it. This could still be implemented asynchronously and of course your could put some abstraction in place such that the request service does not have to know the physical address of the other service, the point still remains that one service is explicitly asking a for specific information (or action to be taken) and functionally waiting for a response.
  • Publish-Subscribe: with this pattern a service registers itself as being interested in certain information, or being able to handle certain requests. The relevant information or requests will then be delivered to it and it can decide what to do with it. In this post we’ll assume that there is some kind of middleware in place to take care of delivery of the published messages to the subscribed services.

Information exchanged: Events vs. Queries/Commands

  • Events are facts that cannot be argued about. For example, an order with number 123 is created. Events only state what has happened. They do not describe what should happen as a consequence of such an event.
  • Queries/Commands: Both convey what should happen. Queries are a specific request for information, commands are a specific request to the receiving service to take some action.

Putting these two dimensions in a matrix results into 4 options to realise couplings between microservices. So what are the advantages and disadvantages for each option? And which one is the best for reaching maximum autonomy?
In the description below we’ll use 2 services to illustrate each pattern. The Order service which is responsible for managing orders and the Shipping service which is responsible for shipping stuff, for example the items included in an order. Services like these could be part of a webshop, which could then also contain services like a shopping cart, a product (search) service, etc.

1. Request-Reply with Events:rre

In this pattern one service asks a specific other service for events that took place (since the last time it asked). This implies strong dependency between these two services, the Shipping service must know which service to connect to for events related to orders. There is also a runtime dependency since the shipping service will only be able to ship new orders if the Order service is available.
Since the Shipping service only receives events it has to decide by itself when an order may be shipped based on information in these events. The Order service does not have to know anything about shipping, it simply provides events stating what happened to orders and leaves the responsibility to act on these events fully to the services requesting the events.

2. Request-Reply with Commands/Queries:

rrcIn this pattern the shipping Order service is going to request the Shipping service to ship an order. This implies strong coupling since the Order service is explicitly requesting a specific service to take care of the shipping and now the Order service must determine when an order is ready to be shipped. It is aware of the existence of a Shipping service and it even knows how to interact with it. If other factors not related to the order itself should be taken into account before shipping the order (e.g. credit status of the customer), then the order services should take this into account before requesting the shipping service to ship the order. Now the business process is baked into the architecture and therefore the architecture cannot be changed easily.
Again there is a runtime dependency since the Order service must ensure that the shipping request is successfully delivered to the Shipping service.

3. Publish-Subscribe with Events

pseIn Publish-Subscribe with Events the Shipping service registers itself as being interested in events related to Orders. After registering itself it will receive all events related to Orders without being aware what the source of the order events is. It is loosely coupled to the source of the Order events. The shipping service will need to retain a copy of the data received in the events such that is can conclude when an order is ready to be shipped. The Order service needs to have no knowledge about shipping. If multiple services provide order related events containing relevant data for the Shipping service then this is not recognisable by the Shipping service. If (one of) the service(s) providing order events is down, the Shipping service will not be aware, it just receives less events. The Shipping service will not be blocked by this.

4. Publish-Subscribe with Commands/Queries

pscIn Publish-Subscribe with Command/Queries the Shipping service registers itself as a service being able to ship stuff. It then receives all commands that want to get something shipped. The Shipping service does not have to be aware of the source of the Shipping commands and on the flip side the Order service is not aware of which service will take care of shipping. In that sense they are loosely coupled. However, the Order service is aware of the fact that orders must get shipped since it is sending out a ship command, this does make the coupling stronger.

Conclusion

Now that we have described the four options we go back to the original question, which pattern of the above 4 provides maximum autonomy?
Both Request-Reply patterns imply a runtime coupling between two services and that implies strong coupling. Both Command/Queries patterns imply that one service is aware of what another service should do (in the examples above the order service is aware that another service takes care of shipping) and that also implies strong coupling, but this time on functional level. That leaves one option: 3. Publish-Subscribe with Events. In this case both services are not aware of each others existence from both runtime and functional perspective. To me this is the clear winner for achieving maximum autonomy between services.
The next question pops up immediately, should you always couple services using Publish-Subscribe with events? If your only concern is maximum autonomy of services the answer would be yes, but, there are more factors that should be taken into the account. Always coupling using this pattern comes at a price, data is replicated, measures must be taken to deal with lost events, events driven architectures do add extra requirements on infrastructure, their might be extra latency, and more. In a next post I’ll dive into these trade-offs and put things into perspective. For now remember that Publish-Subscribe with Events is a good bases for achieving autonomy of services.

guest
7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
vpartington@xebialabs.com
vpartington@xebialabs.com
7 years ago

Hi Gero,
Thanks for the interesting article!
But… 😉
In your article you present the two axes as being independent of one another. But is that really the case? To me request-reply seems most suited to request-reply, while pub-sub seems most suited to events. Scenarios 1 and 4 seem a little contrived to me.
However, I cannot argue with your conclusion that events over pub-sub provide for the least coupling. I think that’s a lesson we’ve already learned with other architectural styles, but keep failing to apply consistently. I guess request-reply/command-query is just so easy to understand and easy to write and that makes it hard to stomp it out.
BTW, there is one thing in your blog that scares me a little bit and that’s the mention of “middleware” when you’re talking about pub-sub. Don’t let the middleware vendors hear it! They might give us some microservicey equivalent of an ESB. 😛
Regards, Vincent.

Jan Toebes
7 years ago

@ Vincent. Correct there is nothing wrong with a message broker. Middleware / ESB’s are evil when they will do message translation etc. We should strive for dumb pipes and smart endpoints

Tom
Tom
7 years ago

Nicely written, I’m always very interested in the next step: so now the shipping service knows that an order is completed, how does it know what to send? Will it query the order service for the order details? AKA will there be a strong couple because the shipping service need to know the exact structure of the API of the order service. Or will all the required information be part of the event? Then we need some kind of super model.

Eddy
Eddy
5 years ago
Reply to  Tom

+1, all the articles we can find on the internet always stop before the next step.
Does the “OrderCreated” event contain all the data of the Order aggregate ?
– If yes: We expose the entire model to potentially multiple services, how can we make the model evolve ? We don’t even know what other services are doing with it, we could break something. It’s a strong coupling to expose your aggregate.
– If no, then the service receiving the Event needs to do an extra call to fetch data regarding the order: We fall back on a strong coupling

William Louth
William Louth
7 years ago

There is another option which not only blurs the lines between call and event but decouples space and time allowing repeated post-execution augmentation of experienced behavior.
Software Regulators! Mirror Outwards, Simulate Inwards.
http://www.autoletics.com/posts/software-regulators-mirror-outwards-simulate-inwards

Rickard
Rickard
6 years ago

Wow, food for thought.
To me it spontaneously feels like choosing an event-based only pattern will create complexity in terms of having chatty services and a challenge of deciding where to block (where to wait until all conditions are met until a order-created response can be returned). However you choose to implement, there seems to me arise some problems
* Somewhere you need to block before returning an “OK” to the original consumer/caller. You can’t assume the consumers all to be smart async clients.
* Using events only: evolves into difficult dependency chains and ending up with chatty services, somewhere you need to establish a work flow or conditions that must be met.
* Using commands and queries: breaking bounded context (or even domain) knowledge since a service now knows about more stuff in other domains.
So I am thinking about a layer above the micro services, ie: ApiGateway that has several responsibilites:
* It exists on top, exposes its API to consumers, for example web applications.
* It might or might not have knowledge about the microservices (service registry and discovery)
* It might or might not provide load balancing
* It knows about all the domains and can tie & aggregate stuff together, and it know the conditions that must be met before satisfying a request from the consumer.
The call chain in this example would be like this: WebClient -> APIGateWay -> OrderMicroService & ShippingMicroService.
Following this hierarchy, it makes sense that the microservices only raises events in it’s own domain, listens to commands and queries in it’s own domain, and never goes out of context or tell other services to act.

Sanjoy Ghosh
5 years ago

Question, something that I have not been able to find an answer.
Assume there is a CartService, now should the price of the cart items be stored when a command to addItem to cart is received? Ideally the price is something that resides in the catalog service. Also, discounts the Promotion service for example is responsible for computing/applying the discounts. Once applied should the discounts stay in the Cart Service along with lineitems or with the Discount service?

Explore related posts