Skip to content

Commit a8bffc1

Browse files
authored
Merge branch 'master' into fix/sonar-high-issue-dirty-flag-app
2 parents 7ebf744 + b1b6e82 commit a8bffc1

File tree

21 files changed

+1400
-1
lines changed

21 files changed

+1400
-1
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3466,6 +3466,15 @@
34663466
"contributions": [
34673467
"code"
34683468
]
3469+
},
3470+
{
3471+
"login": "sanurah",
3472+
"name": "Sanura Hettiarachchi",
3473+
"avatar_url": "https://avatars.githubusercontent.com/u/16178588?v=4",
3474+
"profile": "https://github.com/sanurah",
3475+
"contributions": [
3476+
"code"
3477+
]
34693478
}
34703479
],
34713480
"contributorsPerLine": 6,

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
77
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
88
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
9-
[![All Contributors](https://img.shields.io/badge/all_contributors-380-orange.svg?style=flat-square)](#contributors-)
9+
[![All Contributors](https://img.shields.io/badge/all_contributors-381-orange.svg?style=flat-square)](#contributors-)
1010
<!-- ALL-CONTRIBUTORS-BADGE:END -->
1111

1212
<br/>
@@ -567,6 +567,7 @@ This project is licensed under the terms of the MIT license.
567567
<tr>
568568
<td align="center" valign="top" width="16.66%"><a href="https://github.com/DenizAltunkapan"><img src="https://avatars.githubusercontent.com/u/93663085?v=4?s=100" width="100px;" alt="Deniz Altunkapan"/><br /><sub><b>Deniz Altunkapan</b></sub></a><br /><a href="#translation-DenizAltunkapan" title="Translation">🌍</a></td>
569569
<td align="center" valign="top" width="16.66%"><a href="https://github.com/johnklint81"><img src="https://avatars.githubusercontent.com/u/70539458?v=4?s=100" width="100px;" alt="John Klint"/><br /><sub><b>John Klint</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=johnklint81" title="Code">💻</a></td>
570+
<td align="center" valign="top" width="16.66%"><a href="https://github.com/sanurah"><img src="https://avatars.githubusercontent.com/u/16178588?v=4?s=100" width="100px;" alt="Sanura Hettiarachchi"/><br /><sub><b>Sanura Hettiarachchi</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=sanurah" title="Code">💻</a></td>
570571
</tr>
571572
</tbody>
572573
</table>

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
<module>property</module>
189189
<module>prototype</module>
190190
<module>proxy</module>
191+
<module>publish-subscribe</module>
191192
<module>queue-based-load-leveling</module>
192193
<module>reactor</module>
193194
<module>registry</module>

publish-subscribe/README.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
---
2+
title: "Publish-Subscribe Pattern in Java: Decoupling the solution with asynchronous communication"
3+
shortTitle: Publish-Subscribe
4+
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."
5+
category: Behavioral
6+
language: en
7+
tag:
8+
- Decoupling
9+
- Event-driven
10+
- Gang Of Four
11+
- Publish/subscribe
12+
---
13+
14+
## Intent of the Publish-Subscribe Design Pattern
15+
16+
The Publish-Subscribe design pattern is widely used in software architecture to transmit data between various components in a system.
17+
It is a behavioral design pattern aimed at achieving loosely coupled communication between objects.
18+
The primary intent is to allow a one-to-many dependency relationship where one object (the Publisher) notifies multiple other objects (the Subscribers)
19+
about changes or events, without needing to know who or what the subscribers are.
20+
21+
## Detailed Explanation of Publish-Subscribe Pattern with Real-World Examples
22+
23+
### Real-world example
24+
25+
- Messaging systems like Kafka, RabbitMQ, AWS SNS, JMS
26+
- **Kafka** : publishes messages to topics and subscribers consumes them in real time for analytics, logs or other purposes.
27+
- **RabbitMQ** : Uses exchanges as publisher and queues as subscribers to route messages
28+
- **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)
29+
30+
31+
- Event driven microservices
32+
- **Publisher** : Point of Sale(PoS) system records the sale of an item and publish the event
33+
- **Subscribers** : Inventory management service updates stock, Billing service sends e-bill to customer
34+
35+
36+
- Newsletter subscriptions
37+
- **Publisher** : Writes a new blog post and publish to subscribers
38+
- **Subscribers** : All the subscribers to the newsletter receive the email
39+
40+
### In plain words
41+
42+
The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are,
43+
enabling loose coupling and asynchronous communication in a system
44+
45+
### Wikipedia says
46+
47+
In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers.
48+
This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers.
49+
50+
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.
51+
52+
Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system.
53+
Most messaging systems support both the pub/sub and message queue models in their API; e.g., Java Message Service (JMS).
54+
55+
### Architectural Diagram
56+
![pub-sub](./etc/pub-sub.png)
57+
58+
## Programmatic Example of Publish-Subscribe Pattern in Java
59+
60+
First we need to identify the Event on which we need the pub-sub methods to trigger.
61+
For example:
62+
63+
- Sending alerts based on the weather events such as earthquakes, floods and tornadoes
64+
- Sending alerts based on the temperature
65+
- Sending an email to different customer support emails when a support ticket is created.
66+
67+
The Message class below will hold the content of the message we need to pass between the publisher and the subscribers.
68+
69+
```java
70+
public record Message(Object content) {
71+
}
72+
73+
```
74+
75+
The Topic class will have the topic **name** based on the event
76+
77+
- Weather events TopicName WEATHER
78+
- Weather events TopicName TEMPERATURE
79+
- Support ticket created TopicName CUSTOMER_SUPPORT
80+
- Any other custom topic depending on use case
81+
- Also, the Topic contains a list of subscribers that will listen to that topic
82+
83+
We can add or remove subscribers from the subscription to the topic
84+
85+
```java
86+
public class Topic {
87+
88+
private final TopicName name;
89+
private final Set<Subscriber> subscribers = new CopyOnWriteArraySet<>();
90+
//...//
91+
}
92+
```
93+
94+
Then we can create the publisher. The publisher class has a set of topics.
95+
96+
- Each new topic has to be registered in the publisher.
97+
- Publish method will publish the _Message_ to the corresponding _Topic_.
98+
99+
```java
100+
public class PublisherImpl implements Publisher {
101+
102+
private static final Logger logger = LoggerFactory.getLogger(PublisherImpl.class);
103+
private final Set<Topic> topics = new HashSet<>();
104+
105+
@Override
106+
public void registerTopic(Topic topic) {
107+
topics.add(topic);
108+
}
109+
110+
@Override
111+
public void publish(Topic topic, Message message) {
112+
if (!topics.contains(topic)) {
113+
logger.error("This topic is not registered: {}", topic.getName());
114+
return;
115+
}
116+
topic.publish(message);
117+
}
118+
}
119+
```
120+
121+
Finally, we can Subscribers to the Topics we want to listen to.
122+
123+
- For WEATHER topic we will create _WeatherSubscriber_
124+
- _WeatherSubscriber_ can also subscribe to TEMPERATURE topic
125+
- For CUSTOMER_SUPPORT topic we will create _CustomerSupportSubscribe_
126+
- Also to demonstrate the async behavior we will create a _DelayedWeatherSubscriber_ who has a 0.2 sec processing deplay
127+
128+
All classes will have a _onMessage_ method which will take a Message input.
129+
130+
- On message method will verify the content of the message is as expected
131+
- After content is verified it will perform the operation based on the message
132+
- _WeatherSubscriber_ will send a weather or temperature alert based on the _Message_
133+
- _CustomerSupportSubscribe_will send an email based on the _Message_
134+
- _DelayedWeatherSubscriber_ will send a weather alert based on the _Message_ after a delay
135+
136+
```java
137+
public interface Subscriber {
138+
void onMessage(Message message);
139+
}
140+
```
141+
142+
And here is the invocation of the publisher and subscribers.
143+
144+
```java
145+
public static void main(String[] args) throws InterruptedException {
146+
147+
final String topicWeather = "WEATHER";
148+
final String topicTemperature = "TEMPERATURE";
149+
final String topicCustomerSupport = "CUSTOMER_SUPPORT";
150+
151+
// 1. create the publisher.
152+
Publisher publisher = new PublisherImpl();
153+
154+
// 2. define the topics and register on publisher
155+
Topic weatherTopic = new Topic(topicWeather);
156+
publisher.registerTopic(weatherTopic);
157+
158+
Topic temperatureTopic = new Topic(topicTemperature);
159+
publisher.registerTopic(temperatureTopic);
160+
161+
Topic supportTopic = new Topic(topicCustomerSupport);
162+
publisher.registerTopic(supportTopic);
163+
164+
// 3. Create the subscribers and subscribe to the relevant topics
165+
// weatherSub1 will subscribe to two topics WEATHER and TEMPERATURE.
166+
Subscriber weatherSub1 = new WeatherSubscriber();
167+
weatherTopic.addSubscriber(weatherSub1);
168+
temperatureTopic.addSubscriber(weatherSub1);
169+
170+
// weatherSub2 will subscribe to WEATHER topic
171+
Subscriber weatherSub2 = new WeatherSubscriber();
172+
weatherTopic.addSubscriber(weatherSub2);
173+
174+
// delayedWeatherSub will subscribe to WEATHER topic
175+
// NOTE :: DelayedWeatherSubscriber has a 0.2 sec delay of processing message.
176+
Subscriber delayedWeatherSub = new DelayedWeatherSubscriber();
177+
weatherTopic.addSubscriber(delayedWeatherSub);
178+
179+
// subscribe the customer support subscribers to the CUSTOMER_SUPPORT topic.
180+
Subscriber supportSub1 = new CustomerSupportSubscriber();
181+
supportTopic.addSubscriber(supportSub1);
182+
Subscriber supportSub2 = new CustomerSupportSubscriber();
183+
supportTopic.addSubscriber(supportSub2);
184+
185+
// 4. publish message from each topic
186+
publisher.publish(weatherTopic, new Message("earthquake"));
187+
publisher.publish(temperatureTopic, new Message("23C"));
188+
publisher.publish(supportTopic, new Message("[email protected]"));
189+
190+
// 5. unregister subscriber from TEMPERATURE topic
191+
temperatureTopic.removeSubscriber(weatherSub1);
192+
193+
// 6. publish message under TEMPERATURE topic
194+
publisher.publish(temperatureTopic, new Message("0C"));
195+
196+
/*
197+
* Finally, we wait for the subscribers to consume messages to check the output.
198+
* The output can change on each run, depending on how long the execution on each
199+
* subscriber would take
200+
* Expected behavior:
201+
* - weatherSub1 will consume earthquake and 23C
202+
* - weatherSub2 will consume earthquake
203+
* - delayedWeatherSub will take longer and consume earthquake
204+
* - supportSub1, supportSub2 will consume [email protected]
205+
* - the message 0C will not be consumed because weatherSub1 unsubscribed from TEMPERATURE topic
206+
*/
207+
TimeUnit.SECONDS.sleep(2);
208+
}
209+
```
210+
211+
Program output:
212+
213+
Note that the order of output could change everytime you run the program.
214+
The subscribers could take different time to consume the message.
215+
216+
```
217+
14:01:45.599 [ForkJoinPool.commonPool-worker-6] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1416331388 sent the email to: [email protected]
218+
14:01:45.599 [ForkJoinPool.commonPool-worker-4] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: 23C
219+
14:01:45.599 [ForkJoinPool.commonPool-worker-2] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 60629172 issued message: earthquake
220+
14:01:45.599 [ForkJoinPool.commonPool-worker-5] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1807508804 sent the email to: [email protected]
221+
14:01:45.599 [ForkJoinPool.commonPool-worker-1] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: earthquake
222+
14:01:47.600 [ForkJoinPool.commonPool-worker-3] INFO com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber -- Delayed Weather Subscriber: 2085808749 issued message: earthquake
223+
```
224+
225+
## When to Use the Publish-Subscribe Pattern
226+
227+
- Event-Driven Systems
228+
- Use Pub/Sub when your system relies on events (e.g., user registration, payment completion).
229+
- Example: After a user registers, send a welcome email and log the action simultaneously.
230+
231+
- Asynchronous Communication
232+
- When tasks can be performed without waiting for immediate responses.
233+
- Example: In an e-commerce app, notify the warehouse and the user after a successful order.
234+
235+
- Decoupling Components
236+
- Ideal for systems where producers and consumers should not depend on each other.
237+
- Example: A logging service listens for logs from multiple microservices.
238+
239+
- Scaling Systems
240+
- Useful when you need to scale services without changing the core application logic.
241+
- Example: Broadcasting messages to thousands of clients (chat applications, IoT).
242+
243+
- Broadcasting Notifications
244+
- When a message should be delivered to multiple receivers.
245+
- Example: Sending promotional offers to multiple user devices.
246+
247+
- Microservices Communication
248+
- Allow independent services to communicate without direct coupling.
249+
- Example: An order service publishes an event, and both the billing and shipping services process it.
250+
251+
## When to avoid the Publish-Subscribe Pattern
252+
253+
- Simple applications where direct calls suffice.
254+
- Strong consistency requirements (e.g., banking transactions).
255+
- Low-latency synchronous communication needed.
256+
257+
## Benefits and Trade-offs of Publish-Subscribe Pattern
258+
259+
### Benefits:
260+
261+
- Decoupling
262+
- Publishers and subscribers are independent of each other.
263+
- Publishers don’t need to know who the subscribers are, and vice versa.
264+
- Changes in one component don’t affect the other.
265+
- Scalability
266+
- New subscribers can be added without modifying publishers.
267+
- Supports distributed systems where multiple services consume the same events.
268+
- Dynamic Subscription
269+
- Subscribers can subscribe/unsubscribe at runtime.
270+
- Enables flexible event-driven architectures.
271+
- Asynchronous Communication
272+
- Publishers and subscribers operate independently, improving performance.
273+
- Useful for background processing (e.g., notifications, logging).
274+
- Broadcast Communication
275+
- A single event can be consumed by multiple subscribers.
276+
- Useful for fan-out scenarios (e.g., notifications, analytics).
277+
- Resilience & Fault Tolerance
278+
- If a subscriber fails, others can still process messages.
279+
- Message brokers (e.g., Kafka, RabbitMQ) can retry or persist undelivered messages.
280+
281+
### Trade-offs:
282+
283+
- Complexity in Debugging
284+
- Since publishers and subscribers are decoupled, tracing event flow can be difficult.
285+
- Requires proper logging and monitoring tools.
286+
- Message Ordering & Consistency
287+
- Ensuring message order across subscribers can be challenging (e.g., Kafka vs. RabbitMQ).
288+
- Some systems may process events out of order.
289+
- Potential Latency
290+
- Asynchronous processing introduces delays compared to direct calls.
291+
- Not ideal for real-time synchronous requirements.
292+
293+
## Related Java Design Patterns
294+
295+
* [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).
296+
* [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.
297+
298+
## References and Credits
299+
300+
* [Apache Kafka – Pub-Sub Model](https://kafka.apache.org/documentation/#design_pubsub)
301+
* [Microsoft – Publish-Subscribe Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber)
302+
* [Martin Fowler – Event-Driven Architecture](https://martinfowler.com/articles/201701-event-driven.html)

publish-subscribe/etc/pub-sub.png

9.5 KB
Loading

publish-subscribe/pom.xml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<project xmlns="http://maven.apache.org/POM/4.0.0"
29+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
30+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
31+
<modelVersion>4.0.0</modelVersion>
32+
<parent>
33+
<groupId>com.iluwatar</groupId>
34+
<artifactId>java-design-patterns</artifactId>
35+
<version>1.26.0-SNAPSHOT</version>
36+
</parent>
37+
38+
<artifactId>publish-subscribe</artifactId>
39+
40+
<dependencies>
41+
<dependency>
42+
<groupId>org.junit.jupiter</groupId>
43+
<artifactId>junit-jupiter-engine</artifactId>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>ch.qos.logback</groupId>
48+
<artifactId>logback-classic</artifactId>
49+
</dependency>
50+
</dependencies>
51+
52+
</project>

0 commit comments

Comments
 (0)