From 9f7ce4e732fe9b6a70b607f1882479177bd86182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Tue, 15 Apr 2025 20:36:32 +0300 Subject: [PATCH] docs: update publish-subscribe --- publish-subscribe/README.md | 238 ++++++++---------- .../publish-subscribe-sequence-diagram.png | Bin 0 -> 39717 bytes .../iluwatar/publish/subscribe/AppTest.java | 2 +- .../publish/subscribe/LoggerExtension.java | 7 +- .../publish/subscribe/model/MessageTest.java | 4 +- .../publish/subscribe/model/TopicTest.java | 2 +- .../subscribe/publisher/PublisherTest.java | 2 +- 7 files changed, 114 insertions(+), 141 deletions(-) create mode 100644 publish-subscribe/etc/publish-subscribe-sequence-diagram.png diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md index 8ff29f2c6..86f6c5316 100644 --- a/publish-subscribe/README.md +++ b/publish-subscribe/README.md @@ -1,97 +1,101 @@ --- -title: "Publish-Subscribe Pattern in Java: Decoupling the solution with asynchronous communication" +title: "Publish-Subscribe Pattern in Java: Decoupling components with asynchronous communication" shortTitle: Publish-Subscribe description: "Explore the Publish-Subscribe design pattern in Java with detailed examples. Learn how it helps to create loosely coupled, scalable, and flexible systems by allowing components to communicate asynchronously without knowing each other directly." -category: Behavioral +category: Messaging language: en tag: + - Architecture + - Asynchronous - Decoupling + - Event-driven + - Messaging + - Microservices + - Publish/subscribe + - Scalability --- ## Intent of the Publish-Subscribe Design Pattern -The Publish-Subscribe design pattern is widely used in software architecture to transmit data between various components in a system. -It is a behavioral design pattern aimed at achieving loosely coupled communication between objects. -The primary intent is to allow a one-to-many dependency relationship where one object (the Publisher) notifies multiple other objects (the Subscribers) -about changes or events, without needing to know who or what the subscribers are. +Defines a one-to-many dependency between objects, enabling automatic notification of multiple subscribers when a publisher's state changes or an event occurs. ## Detailed Explanation of Publish-Subscribe Pattern with Real-World Examples -### Real-world example +Real-world example -- Messaging systems like Kafka, RabbitMQ, AWS SNS, JMS - - **Kafka** : publishes messages to topics and subscribers consumes them in real time for analytics, logs or other purposes. - - **RabbitMQ** : Uses exchanges as publisher and queues as subscribers to route messages - - **AWS SNS** : Simple Notification Service (SNS) received the messages from publishers with topic and the subscribers on that topic will receive the messages. (SQS, Lambda functions, emails, SMS) +> An analogous real-world example of the Publish-Subscribe pattern is a news broadcasting system. A news agency (publisher) broadcasts breaking news stories without knowing who specifically receives them. Subscribers, such as television stations, news websites, or mobile news apps, independently decide which types of news they want to receive (e.g., sports, politics, weather) and are automatically notified whenever relevant events occur. This approach keeps the news agency unaware of subscribers' specifics, allowing flexible and scalable distribution of information. +In plain words -- Event driven microservices - - **Publisher** : Point of Sale(PoS) system records the sale of an item and publish the event - - **Subscribers** : Inventory management service updates stock, Billing service sends e-bill to customer +> The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, enabling loose coupling and asynchronous communication in a system. +Wikipedia says -- Newsletter subscriptions - - **Publisher** : Writes a new blog post and publish to subscribers - - **Subscribers** : All the subscribers to the newsletter receive the email - -### In plain words - -The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, -enabling loose coupling and asynchronous communication in a system - -### Wikipedia says - -In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. -This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. - -Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. - -Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. +> In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. +This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. Most messaging systems support both the pub/sub and message queue models in their API; e.g., Java Message Service (JMS). -### Architectural Diagram -![pub-sub](./etc/pub-sub.png) +Sequence diagram + +![Publish-Subscribe sequence diagram](./etc/publish-subscribe-sequence-diagram.png) ## Programmatic Example of Publish-Subscribe Pattern in Java -First we need to identify the Event on which we need the pub-sub methods to trigger. -For example: +First, we identify events that trigger the publisher-subscriber interactions. Common examples include: -- Sending alerts based on the weather events such as earthquakes, floods and tornadoes -- Sending alerts based on the temperature -- Sending an email to different customer support emails when a support ticket is created. +* Sending alerts based on weather events, like earthquakes, floods, and tornadoes. +* Sending notifications based on temperature changes. +* Sending emails to customer support when support tickets are created. -The Message class below will hold the content of the message we need to pass between the publisher and the subscribers. +### Defining the Message + +We start with a simple message class encapsulating the information sent from publishers to subscribers. ```java public record Message(Object content) { } - ``` -The Topic class will have the topic **name** based on the event +### Defining Topics -- Weather events TopicName WEATHER -- Weather events TopicName TEMPERATURE -- Support ticket created TopicName CUSTOMER_SUPPORT -- Any other custom topic depending on use case -- Also, the Topic contains a list of subscribers that will listen to that topic +A Topic represents an event category that subscribers can register to and publishers can publish messages to. Each topic has: -We can add or remove subscribers from the subscription to the topic +* A unique identifier or name (e.g., WEATHER, TEMPERATURE, CUSTOMER_SUPPORT). +* A collection of subscribers listening to this topic. + +Subscribers can dynamically subscribe or unsubscribe. ```java +@Getter +@Setter +@RequiredArgsConstructor public class Topic { - private final TopicName name; - private final Set subscribers = new CopyOnWriteArraySet<>(); - //...// + private final String topicName; + private final Set subscribers = new CopyOnWriteArraySet<>(); + + public void addSubscriber(Subscriber subscriber) { + subscribers.add(subscriber); + } + + public void removeSubscriber(Subscriber subscriber) { + subscribers.remove(subscriber); + } + + public void publish(Message message) { + for (Subscriber subscriber : subscribers) { + CompletableFuture.runAsync(() -> subscriber.onMessage(message)); + } + } } ``` -Then we can create the publisher. The publisher class has a set of topics. +### Publisher Implementation -- Each new topic has to be registered in the publisher. -- Publish method will publish the _Message_ to the corresponding _Topic_. +The Publisher maintains a collection of topics it can publish to. + +* Before publishing, a topic must be registered. +* Upon publishing, it forwards messages to subscribers of the corresponding topic. ```java public class PublisherImpl implements Publisher { @@ -115,20 +119,12 @@ public class PublisherImpl implements Publisher { } ``` -Finally, we can Subscribers to the Topics we want to listen to. +### Defining Subscribers -- For WEATHER topic we will create _WeatherSubscriber_ -- _WeatherSubscriber_ can also subscribe to TEMPERATURE topic -- For CUSTOMER_SUPPORT topic we will create _CustomerSupportSubscribe_ -- Also to demonstrate the async behavior we will create a _DelayedWeatherSubscriber_ who has a 0.2 sec processing deplay +Subscribers implement an interface that handles incoming messages. -All classes will have a _onMessage_ method which will take a Message input. - -- On message method will verify the content of the message is as expected -- After content is verified it will perform the operation based on the message - - _WeatherSubscriber_ will send a weather or temperature alert based on the _Message_ - - _CustomerSupportSubscribe_will send an email based on the _Message_ - - _DelayedWeatherSubscriber_ will send a weather alert based on the _Message_ after a delay +* Each subscriber processes messages according to specific logic. +* Subscribers can be registered to multiple topics. ```java public interface Subscriber { @@ -136,7 +132,21 @@ public interface Subscriber { } ``` -And here is the invocation of the publisher and subscribers. +Subscriber examples: + +* WeatherSubscriber: handles alerts for weather events or temperature changes. +* CustomerSupportSubscriber: handles support tickets by sending emails. +* DelayedWeatherSubscriber: simulates delayed processing for demonstrating asynchronous behavior. + +### Example Usage (Invocation) + +Here's how all components connect: + +1. Create Publisher +2. Register Topics with Publisher +3. Create Subscribers and Subscribe to Relevant Topics +4. Publish Messages +5. Manage Subscriptions Dynamically ```java public static void main(String[] args) throws InterruptedException { @@ -205,10 +215,9 @@ public static void main(String[] args) throws InterruptedException { } ``` -Program output: +### Program output -Note that the order of output could change everytime you run the program. -The subscribers could take different time to consume the message. +Output may vary due to asynchronous subscriber processing: ``` 14:01:45.599 [ForkJoinPool.commonPool-worker-6] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1416331388 sent the email to: support@test.de @@ -219,81 +228,48 @@ The subscribers could take different time to consume the message. 14:01:47.600 [ForkJoinPool.commonPool-worker-3] INFO com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber -- Delayed Weather Subscriber: 2085808749 issued message: earthquake ``` +This demonstrates: + +* Subscribers reacting independently to messages published to subscribed topics. +* Dynamic subscription management allows changing which subscribers listen to specific topics. +* The asynchronous and loosely coupled nature of the publish-subscribe pattern in Java applications. + ## When to Use the Publish-Subscribe Pattern -- Event-Driven Systems - - Use Pub/Sub when your system relies on events (e.g., user registration, payment completion). - - Example: After a user registers, send a welcome email and log the action simultaneously. +* When an application requires loose coupling between event producers and consumers. +* In scenarios where multiple subscribers independently react to the same event. +* When developing scalable, asynchronous messaging systems, particularly within microservices architectures. -- Asynchronous Communication - - When tasks can be performed without waiting for immediate responses. - - Example: In an e-commerce app, notify the warehouse and the user after a successful order. +## Real-World Applications of Publish-Subscribe Pattern in Java -- Decoupling Components - - Ideal for systems where producers and consumers should not depend on each other. - - Example: A logging service listens for logs from multiple microservices. - -- Scaling Systems - - Useful when you need to scale services without changing the core application logic. - - Example: Broadcasting messages to thousands of clients (chat applications, IoT). - -- Broadcasting Notifications - - When a message should be delivered to multiple receivers. - - Example: Sending promotional offers to multiple user devices. - -- Microservices Communication - - Allow independent services to communicate without direct coupling. - - Example: An order service publishes an event, and both the billing and shipping services process it. - -## When to avoid the Publish-Subscribe Pattern - -- Simple applications where direct calls suffice. -- Strong consistency requirements (e.g., banking transactions). -- Low-latency synchronous communication needed. +* Java Message Service (JMS) implementations (ActiveMQ, RabbitMQ) +* Apache Kafka (used extensively in Java-based microservices) +* Spring Framework's event publishing and listening mechanisms +* Google Cloud Pub/Sub in Java applications +* AWS Simple Notification Service (SNS) with Java SDK ## Benefits and Trade-offs of Publish-Subscribe Pattern -### Benefits: +Benefits: -- Decoupling - - Publishers and subscribers are independent of each other. - - Publishers don’t need to know who the subscribers are, and vice versa. - - Changes in one component don’t affect the other. -- Scalability - - New subscribers can be added without modifying publishers. - - Supports distributed systems where multiple services consume the same events. -- Dynamic Subscription - - Subscribers can subscribe/unsubscribe at runtime. - - Enables flexible event-driven architectures. -- Asynchronous Communication - - Publishers and subscribers operate independently, improving performance. - - Useful for background processing (e.g., notifications, logging). -- Broadcast Communication - - A single event can be consumed by multiple subscribers. - - Useful for fan-out scenarios (e.g., notifications, analytics). -- Resilience & Fault Tolerance - - If a subscriber fails, others can still process messages. - - Message brokers (e.g., Kafka, RabbitMQ) can retry or persist undelivered messages. +* Loose coupling between publishers and subscribers promotes flexibility. +* Improved scalability and maintainability as new subscribers can be easily added. +* Supports asynchronous communication, enhancing system responsiveness. -### Trade-offs: +Trade-offs: -- Complexity in Debugging - - Since publishers and subscribers are decoupled, tracing event flow can be difficult. - - Requires proper logging and monitoring tools. -- Message Ordering & Consistency - - Ensuring message order across subscribers can be challenging (e.g., Kafka vs. RabbitMQ). - - Some systems may process events out of order. -- Potential Latency - - Asynchronous processing introduces delays compared to direct calls. - - Not ideal for real-time synchronous requirements. +* Increased complexity due to asynchronous message handling and debugging difficulties. +* Potential message delivery delays and inconsistency if the infrastructure isn't reliable. +* Risk of message flooding, requiring proper infrastructure and consumer management. ## Related Java Design Patterns -* [Observer Pattern](https://github.com/sanurah/java-design-patterns/blob/master/observer/): Both involve a producer (subject/publisher) notifying consumers (observers/subscribers). Observer is synchronous & tightly coupled (observers know the subject). Pub-Sub is asynchronous & decoupled (via a message broker). -* [Mediator Pattern](https://github.com/sanurah/java-design-patterns/blob/master/mediator/): A mediator centralizes communication between components (like a message broker in Pub-Sub). Mediator focuses on reducing direct dependencies between objects. Pub-Sub focuses on broadcasting events to unknown subscribers. +* [Observer Pattern](https://java-design-patterns.com/patterns/observer/): Both patterns establish a publisher-subscriber relationship; however, Observer typically works within a single application boundary synchronously, whereas Publish-Subscribe is often distributed and asynchronous. +* [Mediator Pattern](https://java-design-patterns.com/patterns/mediator/): Mediator encapsulates interactions between objects in a centralized manner, whereas Publish-Subscribe provides decentralized, loosely-coupled interactions. ## References and Credits -* [Apache Kafka – Pub-Sub Model](https://kafka.apache.org/documentation/#design_pubsub) -* [Microsoft – Publish-Subscribe Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) -* [Martin Fowler – Event-Driven Architecture](https://martinfowler.com/articles/201701-event-driven.html) +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) +* [Publisher-Subscriber Pattern (Microsoft)](https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) diff --git a/publish-subscribe/etc/publish-subscribe-sequence-diagram.png b/publish-subscribe/etc/publish-subscribe-sequence-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb1213417b5213eab4e9e34c2b22611a3eed75c GIT binary patch literal 39717 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 literal 0 HcmV?d00001 diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java index 808055307..50c780cb6 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; -public class AppTest { +class AppTest { @Test void shouldExecuteApplicationWithoutException() { diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java index 05e16e5a1..ecf015752 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java @@ -28,7 +28,6 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import java.util.List; -import java.util.stream.Collectors; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -53,12 +52,10 @@ public class LoggerExtension implements BeforeEachCallback, AfterEachCallback { } public List getMessages() { - return listAppender.list.stream().map(e -> e.getMessage()).collect(Collectors.toList()); + return listAppender.list.stream().map(e -> e.getMessage()).toList(); } public List getFormattedMessages() { - return listAppender.list.stream() - .map(e -> e.getFormattedMessage()) - .collect(Collectors.toList()); + return listAppender.list.stream().map(e -> e.getFormattedMessage()).toList(); } } diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java index a08624a3f..636e1ed66 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java @@ -29,10 +29,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import org.junit.jupiter.api.Test; -public class MessageTest { +class MessageTest { @Test - public void testMessage() { + void testMessage() { final String content = "some content"; Message message = new Message(content); assertInstanceOf(String.class, message.content()); diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java index eb2d87c8c..cbb5a9882 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java @@ -33,7 +33,7 @@ import java.lang.reflect.Field; import java.util.Set; import org.junit.jupiter.api.Test; -public class TopicTest { +class TopicTest { private static final String TOPIC_WEATHER = "WEATHER"; diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java index d3db88c42..7105db20f 100644 --- a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java @@ -36,7 +36,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public class PublisherTest { +class PublisherTest { @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension();