From 8a3bed0e950cbc5d9030aa51caf070ecd70cba94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 14 Apr 2024 11:52:52 +0300 Subject: [PATCH] docs: update event aggregator docs --- event-aggregator/README.md | 167 +++++++++--------- .../iluwatar/event/aggregator/AppTest.java | 1 - .../event/aggregator/KingJoffreyTest.java | 4 +- .../event/aggregator/KingsHandTest.java | 4 +- 4 files changed, 92 insertions(+), 84 deletions(-) diff --git a/event-aggregator/README.md b/event-aggregator/README.md index 9e4c7acd4..6e1928cba 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -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> observerLists; + private final Map> 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()) - .>map(day -> emitter -> emitter.timePasses(day)) - .forEachOrdered(emitters::forEach); + Arrays.stream(Weekday.values()).>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) diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java index 7aa83bf64..3b0a66027 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java @@ -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. */ diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java index d7bebbb8e..f0f42516b 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -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 { + private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); public InMemoryAppender(Class clazz) { diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java index 649f5bb2c..cca815cd3 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -49,11 +49,11 @@ class KingsHandTest extends EventEmitterTest { /** * 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);