Today, REST is most commonly used to get applications to interact with each other, and it works fine for some situations, but as discussed in my previous blog post there are many advantages to using messaging instead, not the least of which is the ability to use a wide range of synchronous and asynchronous messaging patterns for event-driven microservices.
Whether organizations turn to microservices to solve issues with existing applications or build greenfield applications, creating applications that consist of many single purpose microservices introduces the need for those individual services to interact in real-time to provide value to the end user.
Inherently distributed messaging patterns are ideal for microservices. Examples of such - which have been a staple of enterprise architecture for many years - include:
- One-way notification, and
To prove this point, lets look at a real-world problem as an example.
A Real-World Example: Creating a Microservices Architecture to Solve the Problem of a Dwindling Beer Supply
The problem I have chosen to solve is near and dear to my heart (as I hope it is to yours!). Imagine you're at a party where the supply of beer has begun to dwindle? Wouldn't it be awesome if someone would magically appear and restock the supply? Well, let me present to you the Solace Beer Online Store, which I'll call Sol-Beer. It makes this dream come true by combining the convenience of an e-commerce platform with a rapid delivery platform.
Not only does this use case solve a real-world problem, it proves out many of the common challenges developers face when creating a microservices architecture. Without further ado, lets jump in!
This problem can be broken down into a set of discrete components:
- Web App - An e-commerce site you can use to order beer.
- Inventory Manager - Component which keeps track of our e-store inventory.
- Billing - While free beer would be amazing, this is the real world and collecting money is a requirement. This component can reach out to a credit card processor and collect funds.
- Logistics - Component which ensures product gets delivered to the customer
- Delivery Vehicle - The car which transports the liquid gold.
The components must then interact to provide value (i.e. beer). With the components defined above, now we need to orchestrate these services so you can be the hero who solves the party's beer problem.
The RESTful/Synchronous Approach to Microservices
So let's say you've read that microservices are synonymous with REST, or assume that the use of a "technology agnostic protocol" means REST. Because REST over HTTP is inherently synchronous and only natively supports request/reply, the orchestration of services would look like this:
The customer initiates the order using the Sol-Beer Web App, which acts as the orchestrator by synchronously invoking the Inventory Manager, Billing, and Logistics services. It then returns a success or failure response to the customer. If we further decompose these interactions into a sequence diagram, it reveals some serious flaws in the architecture:
Here's what's wrong with this architecture:
- Lack of Flexibility: The Web App must be programmed to invoke the Inventory Manager, then the Billing Service. If you want to add a new service (such as fraud detection) to the system, it would mean editing either the WebApp or Billing service - defeating the purpose of single purpose services!
- Slow/Unreliable Response Time: The use must always wait as long as it takes for the Inventory Manager, Billing, and Logistics services to return. A hiccup with any of these services means the user doesn't get a response, and we all know that user-experience is critical for e-commerce interactions.!
- Excessive Coupling: Should the WebApp know about these services? Should the inventory manager be responsible for finding a car? This kind of overlapping functionality is a direct violation of the idea that microservices should be small and single in purpose.
- Difficult Service Discovery: Service discovery is a challenge when you have many delivery vehicles as part of the system, and it's further complicated by the fact that they're moving and coming/going online.
- Complicated Scaling: Let's take a case where the logistics component has become a bottleneck. Naturally, we would want to horizontally scale that service since this is a microservices architecture. But with this architecture we'd also have to scale the WebApp and the Inventory Manager since they are all blocking on the Logistics service - a waste of resources since they are just in a blocked/wait state anyway.
It is apparent that if we were only to use REST as the microservices communication protocol, we would have a mess and not realize many of the benefits of microservices architecture.
Realizing the Benefits of Microservices with Messaging Patterns and Event-Driven Thinking
To leverage the power of event-driven microservices you need to shift your thinking from "invoking services" to "initiating and capturing events." Think about systems publishing events that can be consumed by zero or more downstream services and interpreted within the context of each service. The amount of flexibility and decoupling this provides is astounding. Let's take another look at the architecture behind Sol-Beer with that in mind.
Look what happens when we add messaging to our microservices architecture - the services are fully decoupled and no longer know about each other. Also, the responsiveness of the Sol-Beer WebApp is determined solely by the Inventory Manager because all other activities are only called later.
Our site is now able to handle large demand spikes because the only services that need to be scaled are the WebApp and the Inventory Manager. The other services can be scaled to average load as they no longer impact the customer's order experience. Let's look at the messaging patterns that make up our new event-driven microservices architecture.
Request/Reply can easily be implemented by using messaging, but why would you use messaging vs REST for microservices?
Remember when I said you need to start thinking think about events instead of services? Let's use that concept for when the client places his order on our web app. Our business processes dictate that the inventory needs to be reserved in order to return a message of "your order is being processed." The web app should only know that an order event has occurred and been confirmed. It doesn't need to know if that confirmation comes from the Inventory Manager, Billing, or both.
Here you see how the generated event can be produced and consumed by the Inventory Manager, and a response event can be sent back to the customer once it's confirmed. There are a few key concepts here:
- First that events are sent out with topics. These topic names are strings which can contain a hierarchy; thus supporting interest-based subscriptions.
- Secondly, services will bind to a queue to consume events. These events will be attracted to the queue based on the queue's subscription.
- Lastly, the queue will be non-exclusive, meaning that multiple consumers can receive messages from the queue in a round-robin fashion to support service scaling. Essentially the messaging component replaces the need for a load balancer!
Since the Sol-Beer store produced an event, this event can be consumed by zero or more recipients. In our case, it is consumed by the Inventory Manager, but what if we wanted to have those events consumed by an analytics engine as well? This is where the big benefit lies as you can add these kinds of capabilities without touching the Web App or Inventory Manager. After all, microservices are all about the ability to create new capabilities faster!
The combination of messaging and event-driven architecture really begins to shine when events can be sent in a "fire and forget" way. The one-way notification pattern is a great example of this mode. It was performed in the previous step when the Inventory Manager sent a message indicating that an order's set of products were reserved. From a process perspective, one service that would be interested in those events would be billing. The below diagram shows the billing service consuming those events and acquiring funds from the customer.
A common challenge with microservices architecture is the need to avoid XA transactions which aren't supported. From the business perspective, if the credit card bounces, we need to cancel the order and remove the reservation on the product so others can buy it. By making use of the concept of "eventual consistency" we can avoid XA transactions entirely. The key to eventual consistency is that the events must never be lost, or the system will be in a perpetual state of inconsistency. Guaranteed messaging ensures that the information will be delivered to another application, even in the event of component or network problems. REST cannot provide this guarantee.
In our case, if there were no funds, the Inventory Manager needs to subscribe to these events so it can undo the product reservation and let the customer know their order has been cancelled when a payment problem occurs. The key is that the Billing service does not know what steps need to be taken to roll back the order; it simply produces events indicating "paid" or "not paid." Clean and elegant!
Single Request-Multi Response
If you have ever used Uber or Lyft, you will have seen that the drivers react immediately if their phone dings with another customer in the area needing a ride. Those notifications go to all drivers in the area, and it's the first to respond that wins the ride/business. This is exactly how the Sol-Beer store delivery mechanism is intended to work. The ability to send an event to multiple clients and receive multiple responses is key to this use case and messaging is a perfect enabler. This diagram illustrates how this can be implemented:
Once the Inventory Manager has fulfilled the order, it sends an event to the topic "order/ship//". If you're wondering why we would include delivery zip in a topic name, that's because if each car subscribes to events for a certain geography, we have a clean routing mechanism so drivers won't be overwhelmed by events outside of their area. In this case, the Logistics Manager receives ship notifications and sends out an event indicating that it's looking to locate a delivery vehicle.
Until now we've been using the JMS API as that is commonly used for communication between applications within an enterprise, but for a car or mobile device MQTT or WebSocket is more appropriate. Open APIs and standard wireline protocols make it easier to use the right tools for each job.
A delivery vehicle (or the driver's mobile device) is presented with a notification that an order is ready for delivery. They choose to accept or deny the opportunity. As stated before, the Logistics service only needs a single accept, and for fairness, selects the driver who responded first.
The use of messaging is most commonly associated with the publish/subscribe messaging pattern. Publish/subscribe is an element of some of the messaging patterns we have explored, but it can also be used directly to enhance the user's experience. In our case, wouldn't it be great if the customer could track the delivery vehicle, in real-time, that contains our order? As far-fetched as it sounds, many believe that these types of capabilities will be the future of e-commerce. For example, what if you opted into a FedEx or UPS service where they could deliver a package to your parked car while you are at work? In our case, the user will navigate to our web app where a mapping service (such as Google Maps) will be tracking his beer in real-time!
Because we used publish/subscribe, these events could be consumed by multiple components based on their interest. There is no reason for the Web App to consume these events unless a user navigates to that screen. Logistics, on the other hand, may wish to always receive these events to ensure the driver follows the predefined route and that they do not speed excessively.
Conclusion - Unlocking the Value of Messaging Patterns for Event-Driven Microservices
Hopefully the Sol-Beer store will be implemented one day and fulfill my dreams! In the meantime, I hope that you found it an interesting way to look at the various ways messaging patterns can unlock the full benefits and value of event-driven microservices. Consider adding these patterns to your toolbox and experience the real benefits provided by this architecture.
Author: Jonathan Schabowsky