According to the report, we can see the event-driven model is a hot topic for the system design. However, designing a good event-driven architecture is quite challenging. We are familiar with the synchronization model, which request and response directly, but the stories are totally different in the asynchronization scheme. Nevertheless, here are some architecture patterns for reference.
The blueprint comes from the video and shows the whole vision on a well-defined event-driven architecture.
I will briefly introduce those terminologies in the later sections.
Channel monitoring pattern
The channel monitoring pattern define a role called channel monitor to watch the usage and utilization of the main queue. It monitors some metrics like the amount of pending messages, the amount of consumers and the consuming rate.
Having those metrics helps to understand the workload and the healthy of the system. Moreover, the information can let the system be able to self-healing by adjusting the amount of consumers and throttle the producer.
Consumer supervisor pattern
This supervisor is responsible for adjusting the amount of consumers. It listens to the channel monitor and determines if the current consumers can handle the events. If the supervisor thinks the throughput of consumers is not enough, it will call more consumers to help, and vise versa.
Producer control flow pattern
In the previous section, the supervisor improves the throughput by making more consumers. However, if the cost is a concern, using more consumers obviously is infeasible.
Therefore, there is another approach, producer control flow pattern. When the consuming rate cannot align to the producing rate, a controller will send a signal to the producer to slow down the event generation. After the pending events are processed, the producer can disable the throttling to restore the normal state.
Thread delegate pattern
The thread delegate pattern is a useful pattern not only in the event-driven architecture but the other types of the system design.
The pattern has two purposes:
- Preserve the message order
- Track the metrics of event processing, ex. response time.
Let's begin from the first purpose. When a huge amounts of events enter the queue, we are used to bring up more consumers to handle them, aka scale-out. This method is common in the stateless scenarios. But, some kind of events are stateful, the order of events must be addressed. For instance,
- Put some items in the cart
- Clean up the cart
- Put other items in the same cart
- Charge it
The event order must be kept; otherwise, you may buy the wrong items. Thus, how to handle the events on multiple consumers? In thread delegate pattern, it introduces a new role, event dispatcher, and the dispatcher dispatch an event to the corresponding consumer based on the event type. In other words, all events with the same type will be in the same place, and the order can be ensured.
So, the dispatcher maintains a mapping table to record which type of event belongs to who. After the consumer finishes an event, it callback to the dispatcher. The dispatcher knows all processing results of consumers, i.e., it can track the response time as well.
Workflow event pattern
When event consumer encounter an error while processing, it may not fix the error due to many reasons like the malformed event, transaction conflicts, etc. Hence, the consumer emit this event to another queue handled by workflow processor. The processor will either fix the event or store the event in the storage. In addition, the processor can display the event on the dashboard and inform the human to take over the case. The human can fix the situation manually and resend this event to the queue.
It is worth to mention that if the event is resent to the queue, the order cannot be guaranteed.
Request-response Model
Wait, we are designing an event-driven architecture which is asynchronized definitely. Why we are talking about the synchronized model here? In my opinion, the request-response models is very classic way to start up a project. We used to request a response through the restful APIs. This implementation can reduce the development effort and give a straightforward view. However, we don’t want to lose the flexibility of event-driven architectures. The solution is we implement a request-response models upon an event system.
The sequential diagram is shown as the following figure.
The server is a traditional HTTP server with some synchronized Restful APIs, and the client requests the server as usual. Nevertheless, the server doesn't handle this request directly; instead, he delegate this job to the event worker and wait for the response at the agreed place which is another queue. After using this pattern, we can enjoy not only the benefits of the event-driven architecture but also the simplicity between clients and servers.
Ambulance Pattern
The ambulance pattern is used to handle the message priority correctly. The simple design for an emergency event is always put this event to the head of a queue, so that workers can process those high-priority events Immediately. Well, this implementation has some drawbacks, especially, the starvation of the low-priority events. Those normal events might not be processed at all, because there are always emergency events come in.
In order to handle events evenly, we can separate emergency events from ordinary events and submit to a new queue like the follows:
The workers can pick events from those queues to avoid the starvation. There can be a weight to determine the ratio of the two sides, or simply use round-robin to handle those events sequentially.
Furthermore, you can dedicate a worker focus on the emergency events.
Summary
We walk through several design patterns of an event-driven architecture. We can design a self-monitoring and self-healing system leverage those patterns. The system is scalable, robust, efficient, and fault-tolerant.
In order to make the client more easily, we can leverage the request-response model. If we encounter a scenario is to distinguish the event priority, we can use the ambulance pattern.
The trend of using event-driven architecture has become apparent. However, until now, there is no one-size-fits-all solution can design a well architecture for events. We have to find the corresponding solution according to various situations. Hope this article are helpful to you.