docs: update event aggregator docs

This commit is contained in:
Ilkka Seppälä
2024-04-14 11:52:52 +03:00
parent 4f69dea978
commit 8a3bed0e95
4 changed files with 92 additions and 84 deletions
+88 -79
View File
@@ -1,142 +1,129 @@
---
title: Event Aggregator
category: Structural
category: Messaging
language: en
tag:
- Reactive
- Decoupling
- Event-driven
- Reactive
---
## Name
## Also known as
Event Aggregator
* Message Hub
## Intent
A system with lots of objects can lead to complexities when a
client wants to subscribe to events. The client has to find and register for
each object individually, if each object has multiple events then each event
requires a separate subscription. An Event Aggregator acts as a single source
of events for many objects. It registers for all the events of the many objects
allowing clients to register with just the aggregator.
The Event Aggregator design pattern aims to reduce the direct dependencies between multiple systems and components that need to interact by introducing a single component, the Event Aggregator, that receives events from multiple sources and distributes them to multiple listeners.
## Explanation
Real-world example
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most
> of his critical information from King's Hand, the second in command. King's hand has many
> close advisors himself, feeding him with relevant information about events occurring in the
> kingdom.
> King Joffrey sits on the iron throne and rules the seven kingdoms of Westeros. He receives most of his critical information from King's Hand, the second in command. King's hand has many close advisors himself, feeding him with relevant information about events occurring in the kingdom.
In Plain Words
> Event Aggregator is an event mediator that collects events from multiple sources and delivers
> them to registered observers.
> Event Aggregator is an event mediator that collects events from multiple sources and delivers them to registered observers.
**Programmatic Example**
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of
the objects are event listeners, some are event emitters, and the event aggregator does both.
In our programmatic example, we demonstrate the implementation of an event aggregator pattern. Some of the objects are event listeners, some are event emitters, and the event aggregator does both.
```java
public interface EventObserver {
void onEvent(Event e);
void onEvent(Event e);
}
public abstract class EventEmitter {
private final Map<Event, List<EventObserver>> observerLists;
private final Map<Event, List<EventObserver>> observerLists;
public EventEmitter() {
observerLists = new HashMap<>();
}
public EventEmitter() {
observerLists = new HashMap<>();
}
public final void registerObserver(EventObserver obs, Event e) {
...
}
public final void registerObserver(EventObserver obs, Event e) {
// ...
}
protected void notifyObservers(Event e) {
...
}
protected void notifyObservers(Event e) {
// ...
}
}
```
`KingJoffrey` is listening to events from `KingsHand`.
```java
@Slf4j
public class KingJoffrey implements EventObserver {
@Override
public void onEvent(Event e) {
LOGGER.info("Received event from the King's Hand: {}", e.toString());
}
@Override
public void onEvent(Event e) {
LOGGER.info("Received event from the King's Hand: {}", e.toString());
}
}
```
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`.
Whatever he hears from them, he delivers to `KingJoffrey`.
`KingsHand` is listening to events from his subordinates `LordBaelish`, `LordVarys`, and `Scout`. Whatever he hears from them, he delivers to `KingJoffrey`.
```java
public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() {
}
public KingsHand() {
}
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
}
public KingsHand(EventObserver obs, Event e) {
super(obs, e);
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
}
```
For example, `LordVarys` finds a traitor every Sunday and notifies the `KingsHand`.
```java
@Slf4j
public class LordVarys extends EventEmitter implements EventObserver {
@Override
public void timePasses(Weekday day) {
if (day == Weekday.SATURDAY) {
notifyObservers(Event.TRAITOR_DETECTED);
@Override
public void timePasses(Weekday day) {
if (day == Weekday.SATURDAY) {
notifyObservers(Event.TRAITOR_DETECTED);
}
}
}
}
```
The following snippet demonstrates how the objects are constructed and wired together.
```java
var kingJoffrey = new KingJoffrey();
var kingJoffrey=new KingJoffrey();
var kingsHand = new KingsHand();
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
var kingsHand=new KingsHand();
kingsHand.registerObserver(kingJoffrey,Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey,Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey,Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey,Event.WHITE_WALKERS_SIGHTED);
var varys = new LordVarys();
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
var varys=new LordVarys();
varys.registerObserver(kingsHand,Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand,Event.WHITE_WALKERS_SIGHTED);
var scout = new Scout();
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
var scout=new Scout();
scout.registerObserver(kingsHand,Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys,Event.WHITE_WALKERS_SIGHTED);
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
var baelish=new LordBaelish(kingsHand,Event.STARK_SIGHTED);
var emitters = List.of(
kingsHand,
baelish,
varys,
scout
);
var emitters=List.of(kingsHand, baelish, varys, scout);
Arrays.stream(Weekday.values())
.<Consumer<? super EventEmitter>>map(day -> emitter -> emitter.timePasses(day))
.forEachOrdered(emitters::forEach);
Arrays.stream(Weekday.values()).<Consumer<? super EventEmitter>>map(day->emitter->emitter.timePasses(day)).forEachOrdered(emitters::forEach);
```
The console output after running the example.
@@ -149,21 +136,43 @@ The console output after running the example.
```
## Class diagram
![alt text](./etc/classes.png "Event Aggregator")
![Event Aggregator](./etc/classes.png "Event Aggregator")
## Applicability
Use the Event Aggregator pattern when
* Event Aggregator is a good choice when you have lots of objects that are
potential event sources. Rather than have the observer deal with registering
with them all, you can centralize the registration logic to the Event
Aggregator. As well as simplifying registration, an Event Aggregator also
simplifies the memory management issues in using observers.
* Use the Event Aggregator pattern in systems where multiple components generate events and multiple components need to receive those events, but direct coupling between these components leads to complex dependencies and hard-to-manage code.
* Suitable in applications where a reduction in the number of explicit references between decoupled systems is desired, such as in microservices architectures or complex user interface systems.
## Related patterns
## Known Uses
* [Observer](https://java-design-patterns.com/patterns/observer/)
* Enterprise application integrations where systems need a central point to handle events generated by various subsystems.
* Complex GUI applications where user actions in one part of the interface need to affect other parts without tight coupling between the components.
## Consequences
Benefits:
* Reduces Coupling: By centralizing event handling, the Event Aggregator minimizes direct interaction between components, leading to a more modular and easier-to-manage system.
* Improves Flexibility and Scalability: Adding new publishers or subscribers involves less effort since the central aggregator handles all routing.
* Simplifies Component Interface: Components need to know only about the Event Aggregator, not about other components.
Trade-offs:
* Complexity of the Aggregator: The Event Aggregator itself can become a complex and high-maintenance component if not properly designed.
* Potential Performance Bottleneck: If not scaled properly, the central event handling mechanism can become a bottleneck in the system.
## Related Patterns
* [Mediator](https://java-design-patterns.com/patterns/mediator/): Similar to Mediator in that it abstracts direct communications between components, but focused specifically on event messages.
* [Observer](https://java-design-patterns.com/patterns/observer/): The Event Aggregator pattern is often implemented using the Observer pattern, where the aggregator observes events and notifies subscribers.
* Publish-Subscribe: The Event Aggregator can be seen as a special case of the Publish-Subscribe pattern, with the aggregator acting as the broker.
## Credits
* [Martin Fowler - Event Aggregator](http://martinfowler.com/eaaDev/EventAggregator.html)
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/44eWKXv)
* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/440b0CZ)
* [Java Design Pattern Essentials](https://amzn.to/43XHCgM)
@@ -35,7 +35,6 @@ class AppTest {
/**
* Issue: Add at least one assertion to this test case.
*
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@@ -67,14 +67,14 @@ class KingJoffreyTest {
assertEquals(i, appender.getLogSize());
var event = Event.values()[i];
kingJoffrey.onEvent(event);
final var expectedMessage = "Received event from the King's Hand: " + event.toString();
final var expectedMessage = "Received event from the King's Hand: " + event;
assertEquals(expectedMessage, appender.getLastMessage());
assertEquals(i + 1, appender.getLogSize());
});
}
private class InMemoryAppender extends AppenderBase<ILoggingEvent> {
private static class InMemoryAppender extends AppenderBase<ILoggingEvent> {
private final List<ILoggingEvent> log = new LinkedList<>();
public InMemoryAppender(Class<?> clazz) {
@@ -49,11 +49,11 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
/**
* The {@link KingsHand} is both an {@link EventEmitter} as an {@link EventObserver} so verify if
* every event received is passed up to it's superior, in most cases {@link KingJoffrey} but now
* every event received is passed up to its superior, in most cases {@link KingJoffrey} but now
* just a mocked observer.
*/
@Test
void testPassThrough() throws Exception {
void testPassThrough() {
final var observer = mock(EventObserver.class);
final var kingsHand = new KingsHand();
kingsHand.registerObserver(observer, Event.STARK_SIGHTED);