A recent post extolled the virtues of exploring reactive databases for decoupled applications. Why is loose coupling so desirable?
If you've been reading this blog for a while, you know the answer.
Decoupled applications allow us to embrace change. When we design for change every architectural decision is less consequential. It means we can reduce the switching costs associated with any given approach.
We also get the gift of freedom–to experiment, to take measured risks and to innovate.
We know we want modifiable applications. We also know we want to decouple the implementation details of communication between parts of our application. That is to say we are less concerned with whether, for example, we communicate via RESTful API or GraphQL or message brokers.
Instead we concentrate on the meaning of the messages we exchange among our services.
This week’s pattern builds on using the Publish/Subscribe capabilities of reactive databases to create a message bus. We'll see how this approach allows seamless communication between services and affords easy extensibility of the application when it's time to introduce new features.
Read on to learn more about the promise and perils of taking the bus.
The Bus? Seriously…?
At its base, a message bus is a central communication link that services can use to listen for and dispatch messages of interest to anyone or anything connected to the bus. Adding or removing capabilities is simple: either case requires only attaching or detaching from the bus. Take a look.
Set it and forget it. Application services can connect and disconnect whenever they like. Communicating with event messages offers unparalleled flexibility.
Services are unaware of what peer services are connected to the bus. They are only able to post messages of interest to the bus.
This pattern shares similarities with the classic Publish/Subscribe pattern, where each service registers its own listeners. A challenge with this approach is that it's difficult to manage all publishers and subscribers as the system grows.
A message bus, similar to a message broker, can centralize operational logic like retries, routing policies, error handling and other concerns that would be duplicated in an architecture with a host of point-to-point connections between publishers and subscribers.
A Ride Down Memory Lane…
If this sounds reminiscent of the centralized enterprise service buses of the 90s, you've got a good memory. This architectural approach was the foundation of what became known as service-oriented architecture or SOA.
This paradigm fell out of favor as more logic concentrated in the message bus, creating challenges as the system scaled in terms of connected services. In fact, it created exactly the type of obstacles such a decoupled architecture is intended to avoid with the message bus as a single point of failure.
Again, self-describing messages offer an innovative solution. The core problem with the Enterprise Service Bus is the accumulation of message processing logic within the bus itself, as well as the coordination of message schemas among connected services.
By removing the logic for managing messages from the bus and embedding data required for message processing within the messages themselves, our bus is solely concerned with carrying messages.
When we eliminate logic from the bus and store all application state and ancillary processing data inside the messages, the result is a near-stateless application.
In the example application below, notice that there is no persistence layer the application uses to do its work aside from publishing events and responding to them.
Taking the Bus for Ice Cream
Our demo app is an ice cream service. It receives orders for customers, charges the customer card and fulfills the order provided inventory is available.
We use Supabase as our database of choice because...duh; this spares us having to create an API surface just to test our app. Since our FOHService
(which could be anything including a UI app) is listening for inserts into the events
table, it means we have a full stack app with a REST API in seconds.
The rest of the application is driven by the flow of events.
Stackblitz is our backend in this case. Here we define our services and their subscriptions and we publish relevant events. The implementation of this pattern (the how) is less important than what is accomplished:
- the app completes tasks with refreshingly little state to manage
- decoupled services are easy to replace, rollback or trial
- if we know how to create self-describing messages, message structure can evolve without disrupting peer services
Toward Stateless Services
This approach of putting all stateful information into streams of successive messages is known as Event-Carried State Transfer. By carrying the relevant state in the message itself, downstream services don't need to query other systems for additional context.
As in our previous exploration of self-describing messages, they once more prove an invaluable contribution to system adaptability.
As in earlier examples with self-describing messages, our connected services can evolve their message schemas without disrupting the message processing of their peer services.
Finally, as in previous posts, we see another pattern that demonstrates the possibilities of architectures that reduce the cost of experimentation, increase the freedom to innovate, and above all, allow us to author systems that can move at the speed of change.
Top comments (0)