actorRegister = new ConcurrentHashMap<>();
+ private final AtomicInteger idCounter = new AtomicInteger(0);
+
+ public void startActor(Actor actor) {
+ String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID
+ actor.setActorId(actorId); // assign the actor it's ID
+ actorRegister.put(actorId, actor); // Register and save the actor with it's ID
+ executor.submit(actor); // Run the actor in a thread
+ }
+
+ public Actor getActorById(String actorId) {
+ return actorRegister.get(actorId); // Find by Id
+ }
+
+ public void shutdown() {
+ executor.shutdownNow(); // Stop all threads
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
new file mode 100644
index 000000000000..79fe79e48a6f
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
@@ -0,0 +1,64 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * The Actor Model is a design pattern used to handle concurrency in a safe, scalable, and
+ * message-driven way.
+ *
+ * In the Actor Model: - An **Actor** is an independent unit that has its own state and behavior.
+ * - Actors **communicate only through messages** — they do not share memory. - An **ActorSystem**
+ * is responsible for creating, starting, and managing the lifecycle of actors. - Messages are
+ * delivered asynchronously, and each actor processes them one at a time.
+ *
+ *
đź’ˇ Key benefits: - No shared memory = no need for complex thread-safety - Easy to scale with
+ * many actors - Suitable for highly concurrent or distributed systems
+ *
+ *
🔍 This example demonstrates the Actor Model: - `ActorSystem` starts two actors: `srijan` and
+ * `ansh`. - `ExampleActor` and `ExampleActor2` extend the `Actor` class and override the
+ * `onReceive()` method to handle messages. - Actors communicate using `send()` to pass `Message`
+ * objects that include the message content and sender's ID. - The actors process messages
+ * **asynchronously in separate threads**, and we allow a short delay (`Thread.sleep`) to let them
+ * run. - The system is shut down gracefully at the end.
+ */
+package com.iluwatar.actormodel;
+
+public class App {
+ public static void main(String[] args) throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+ Actor srijan = new ExampleActor(system);
+ Actor ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+ srijan.send(new Message("Hello srijan!", ansh.getActorId()));
+
+ Thread.sleep(1000); // Give time for messages to process
+
+ srijan.stop(); // Stop the actor gracefully
+ ansh.stop();
+ system.shutdown(); // Stop the actor system
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
new file mode 100644
index 000000000000..fd49325f44bd
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ // Logger log = Logger.getLogger(getClass().getName());
+
+ @Override
+ protected void onReceive(Message message) {
+ LOGGER.info(
+ "[{}]Received : {} from : [{}]", getActorId(), message.getContent(), message.getSenderId());
+ Actor sender = actorSystem.getActorById(message.getSenderId()); // sender actor id
+ // Reply of the message
+ if (sender != null && !message.getSenderId().equals(getActorId())) {
+ sender.send(new Message("I got your message ", getActorId()));
+ }
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
new file mode 100644
index 000000000000..037f96716558
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
@@ -0,0 +1,46 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor2 extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor2(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ @Override
+ protected void onReceive(Message message) {
+ receivedMessages.add(message.getContent());
+ LOGGER.info("[{}]Received : {}", getActorId(), message.getContent());
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
new file mode 100644
index 000000000000..03ca6e02cac0
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class Message {
+ private final String content;
+ private final String senderId;
+}
diff --git a/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
new file mode 100644
index 000000000000..a4a0dee569ab
--- /dev/null
+++ b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
@@ -0,0 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actor;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.iluwatar.actormodel.ActorSystem;
+import com.iluwatar.actormodel.App;
+import com.iluwatar.actormodel.ExampleActor;
+import com.iluwatar.actormodel.ExampleActor2;
+import com.iluwatar.actormodel.Message;
+import org.junit.jupiter.api.Test;
+
+public class ActorModelTest {
+ @Test
+ void testMainMethod() throws InterruptedException {
+ App.main(new String[] {});
+ }
+
+ @Test
+ public void testMessagePassing() throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+
+ ExampleActor srijan = new ExampleActor(system);
+ ExampleActor2 ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+
+ // Ansh recieves a message from Srijan
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+
+ // Wait briefly to allow async processing
+ Thread.sleep(200);
+
+ // Check that Srijan received the message
+ assertTrue(
+ ansh.getReceivedMessages().contains("Hello ansh"),
+ "ansh should receive the message from Srijan");
+ }
+}
diff --git a/data-access-object/README.md b/data-access-object/README.md
index bd020252d253..7e84299e23e1 100644
--- a/data-access-object/README.md
+++ b/data-access-object/README.md
@@ -199,10 +199,6 @@ The program output:
10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1
```
-## Detailed Explanation of Data Access Object Pattern with Real-World Examples
-
-
-
## When to Use the Data Access Object Pattern in Java
Use the Data Access Object in any of the following situations:
diff --git a/data-locality/README.md b/data-locality/README.md
index e9f556b8ad5c..a59103b3f89b 100644
--- a/data-locality/README.md
+++ b/data-locality/README.md
@@ -128,10 +128,6 @@ The console output:
In this way, the data-locality module demonstrates the Data Locality pattern. By updating all components of the same type together, it increases the likelihood that the data needed for the update is already in the cache, thereby improving performance.
-## Detailed Explanation of Data Locality Pattern with Real-World Examples
-
-
-
## When to Use the Data Locality Pattern in Java
This pattern is applicable in scenarios where large datasets are processed and performance is critical. It's particularly useful in:
diff --git a/microservices-distributed-tracing/README.md b/microservices-distributed-tracing/README.md
index 1adf6db32277..4fc271a68824 100644
--- a/microservices-distributed-tracing/README.md
+++ b/microservices-distributed-tracing/README.md
@@ -1,6 +1,6 @@
---
-title: "Microservices Distributed Tracing Pattern: Enhancing Visibility in Service Communication"
-shortTitle: Distributed Tracing in Microservices
+title: "Microservices Distributed Tracing Pattern In Java: Enhancing Visibility in Service Communication"
+shortTitle: Microservices Distributed Tracing
description: "Learn how the Distributed Tracing pattern enhances visibility into service communication across microservices. Discover its benefits, implementation examples, and best practices."
category: Architectural
language: en
@@ -8,8 +8,8 @@ tag:
- Cloud distributed
- Microservices
- Resilience
- - Scalability
- Observability
+ - Scalability
- System health
---
diff --git a/microservices-distributed-tracing/pom.xml b/microservices-distributed-tracing/pom.xml
index 1957766d694d..c7dddf0fa0b6 100644
--- a/microservices-distributed-tracing/pom.xml
+++ b/microservices-distributed-tracing/pom.xml
@@ -46,7 +46,7 @@
io.micrometer
micrometer-tracing-bridge-brave
- 1.4.4
+ 1.4.5
compile
diff --git a/microservices-idempotent-consumer/README.md b/microservices-idempotent-consumer/README.md
index a3065861a4b1..738bbcda5a3d 100644
--- a/microservices-idempotent-consumer/README.md
+++ b/microservices-idempotent-consumer/README.md
@@ -1,53 +1,56 @@
---
-title: "Idempotent Consumer Pattern in Java: Ensuring Reliable Message Processing"
-shortTitle: Idempotent Consumer
+title: "Microservices Idempotent Consumer Pattern in Java: Ensuring Reliable Message Processing"
+shortTitle: Microservices Idempotent Consumer
description: "Learn about the Idempotent Consumer pattern in Java. Discover how it ensures reliable and consistent message processing, even in cases of duplicate messages."
-category: Structural
+category: Messaging
language: en
tag:
+ - Asynchronous
+ - Decoupling
- Event-driven
+ - Messaging
+ - Microservices
+ - Resilience
+ - Retry
---
## Also known as
-* Idempotency Pattern
+* Idempotent Subscriber
+* Repeatable Message Consumer
+* Safe Consumer
## Intent of Idempotent Consumer Pattern
-The Idempotent Consumer pattern is used to handle duplicate messages in distributed systems, ensuring that multiple processing of the same message does not cause undesired side effects. This pattern guarantees that the same message can be processed repeatedly with the same outcome, which is critical in ensuring reliable communication and data consistency in systems where message duplicates are possible.
+Ensure that consuming the same message multiple times does not cause unintended side effects in a microservices-based architecture.
## Detailed Explanation of Idempotent Consumer Pattern with Real-World Examples
-### Real-world Example
+Real-world example
> In a payment processing system, ensuring that payment messages are idempotent prevents duplicate transactions. For example, if a user’s payment message is accidentally processed twice, the system should recognize the second message as a duplicate and prevent it from executing a second time. By storing unique identifiers for each processed message, such as a transaction ID, the system can skip any duplicate messages. This ensures that a user is not charged twice for the same transaction, maintaining system integrity and customer satisfaction.
-### In Plain Words
+In plain words
> The Idempotent Consumer pattern prevents duplicate messages from causing unintended side effects by ensuring that processing the same message multiple times results in the same outcome. This makes message processing safe in distributed systems where duplicates may occur.
-### Wikipedia says
-> In computing, idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.
+Wikipedia says
-## When to Use the Idempotent Consumer Pattern
+> In computing, idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.
-The Idempotent Consumer pattern is particularly useful in scenarios:
+Flowchart
-* When messages can be duplicated due to network retries or communication issues.
-* In distributed systems where message ordering is not guaranteed, making deduplication necessary to avoid repeated processing.
-* In financial or critical systems, where duplicate processing would have significant side effects.
+
-## Real-World Applications of Idempotent Consumer Pattern
+## Programmatic example of Idempotent Consumer Pattern
-* Payment processing systems that avoid duplicate transactions.
-* E-commerce systems to prevent multiple entries of the same order.
-* Inventory management systems to prevent multiple entries when updating stock levels.
+In this Java example, we have an idempotent service that creates and updates orders. The `create` method is idempotent, meaning multiple calls with the same order ID return the same result without duplicates. For state changes (like starting or completing an order), the service checks whether the transition is valid and throws an exception if it’s not allowed. The `RequestStateMachine` ensures that order statuses move forward in a valid sequence (e.g., PENDING → STARTED → COMPLETED).
-## Programmatic example of Idempotent Consumer Pattern
-In this Java example, we have an idempotent service that offers functionality to create and update (start, complete, etc.) orders. The service ensures that the **create order** operation is idempotent, meaning that performing it multiple times with the same order ID will lead to the same result without creating duplicates. For state transitions (such as starting or completing an order), the service enforces valid state changes and throws exceptions if an invalid transition is attempted. The state machine governs the valid order status transitions, ensuring that statuses progress in a defined and consistent sequence.
### RequestService - Managing Idempotent Order Operations
-The `RequestService` class is responsible for handling the creation and state transitions of orders. The `create` method is designed to be idempotent, ensuring that it either returns an existing order or creates a new one without any side effects if invoked multiple times with the same order ID.
+
+The `RequestService` class provides methods to create and transition an order. The `create` method returns an existing order if it already exists, making it idempotent.
+
```java
public class RequestService {
// Idempotent: ensures that the same request is returned if it already exists
@@ -76,8 +79,11 @@ public class RequestService {
}
}
```
+
### RequestStateMachine - Managing Order Transitions
-The `RequestStateMachine` ensures that state transitions occur in a valid order. It handles the progression of an order's status, ensuring the correct sequence (e.g., from `PENDING` to `STARTED` to `COMPLETED`).
+
+The `RequestStateMachine` enforces valid state changes. If a requested transition is not allowed based on the current status, an exception is thrown.
+
```java
public class RequestStateMachine {
@@ -102,9 +108,10 @@ public class RequestStateMachine {
}
}
```
+
### Main Application - Running the Idempotent Consumer Example
-In the main application, we demonstrate how the `RequestService` can be used to perform idempotent operations. Whether the order creation or state transition is invoked once or multiple times, the result is consistent and does not produce unexpected side effects.
+Here, we demonstrate how `RequestService` can be called multiple times without creating duplicate orders. We also show how invalid transitions (like trying to start an order twice) result in exceptions, while valid transitions proceed normally.
```java
Request req = requestService.create(UUID.randomUUID());
@@ -127,32 +134,49 @@ req = requestService.complete(req.getUuid());
// Log the final status of the Request to confirm it's been completed
LOGGER.info("Request: {}", req);
```
+
Program output:
+
```
19:01:54.382 INFO [main] com.iluwatar.idempotentconsumer.App : Nb of requests : 1
19:01:54.395 ERROR [main] com.iluwatar.idempotentconsumer.App : Cannot start request twice!
19:01:54.399 INFO [main] com.iluwatar.idempotentconsumer.App : Request: Request(uuid=2d5521ef-6b6b-4003-9ade-81e381fe9a63, status=COMPLETED)
```
+
+## When to Use the Idempotent Consumer Pattern
+
+* When messages can arrive more than once due to network glitches or retries
+* When microservices must guarantee consistent state changes regardless of duplicates
+* When fault-tolerant event-driven communication is critical to system reliability
+* When horizontal scaling requires stateless consumer operations
+
+## Real-World Applications of Idempotent Consumer Pattern
+
+* Payment processing systems that receive duplicate charge events
+* E-commerce order services that handle duplicate purchase requests
+* Notification services that retry failed message deliveries
+* Distributed transaction systems where duplicated events are common
+
## Benefits and Trade-offs of the Idempotent Consumer Pattern
-### Benefits
+Benefits
-* **Reliability**: Ensures that messages can be processed without unwanted side effects from duplicates.
-* **Consistency**: Maintains data integrity by ensuring that duplicate messages do not cause redundant updates or actions.
-* **Fault Tolerance**: Handles message retries gracefully, preventing them from causing errors.
+* Prevents duplicate side effects
+* Increases reliability under repeated or delayed messages
+* Simplifies error handling and retry logic
-### Trade-offs
+Trade-offs
-* **State Management**: Requires storing processed message IDs, which can add memory overhead.
-* **Complexity**: Implementing deduplication mechanisms can increase the complexity of the system.
-* **Scalability**: In high-throughput systems, maintaining a large set of processed messages can impact performance and resource usage.
+* Requires careful design to track processed messages
+* Can add overhead for maintaining idempotency tokens or state
+* May require additional storage or database transactions
## Related Patterns in Java
-* [Retry Pattern](https://java-design-patterns.com/patterns/retry/): Works well with the Idempotent Consumer pattern to handle failed messages.
-* [Circuit Breaker Pattern](https://java-design-patterns.com/patterns/circuitbreaker/): Often used alongside idempotent consumers to prevent repeated failures from causing overload.
+* Outbox Pattern: Uses a dedicated table or storage to reliably publish events and handle deduplication at the source.
## References and Credits
+* [Building Microservices](https://amzn.to/3UACtrU)
* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4dznP2Y)
-* [Designing Data-Intensive Applications](https://amzn.to/3UADv7Q)
+* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O)
diff --git a/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png
new file mode 100644
index 000000000000..e5bd5d83ac8b
Binary files /dev/null and b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png differ
diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java
index 7960533301e6..39d496120cc7 100644
--- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java
+++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java
@@ -44,7 +44,7 @@ public RequestService(
}
/**
- * Creates a new Request or returns an existing one by it's UUID. This operation is idempotent:
+ * Creates a new Request or returns an existing one by its UUID. This operation is idempotent:
* performing it once or several times successively leads to an equivalent result.
*
* @param uuid The unique identifier for the Request.
@@ -52,10 +52,7 @@ public RequestService(
*/
public Request create(UUID uuid) {
Optional optReq = requestRepository.findById(uuid);
- if (!optReq.isEmpty()) {
- return optReq.get();
- }
- return requestRepository.save(new Request(uuid));
+ return optReq.orElseGet(() -> requestRepository.save(new Request(uuid)));
}
/**
diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java
index 457febedadd7..f6cd88e0f505 100644
--- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java
+++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java
@@ -39,12 +39,12 @@
class AppTest {
@Test
- void main() {
+ void testMain() {
assertDoesNotThrow(() -> App.main(new String[] {}));
}
@Test
- void run() throws Exception {
+ void testRun() throws Exception {
RequestService requestService = Mockito.mock(RequestService.class);
RequestRepository requestRepository = Mockito.mock(RequestRepository.class);
UUID uuid = UUID.randomUUID();
diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java
index f883709a92be..20470d5151c7 100644
--- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java
+++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java
@@ -43,11 +43,10 @@
class RequestServiceTests {
private RequestService requestService;
@Mock private RequestRepository requestRepository;
- private RequestStateMachine requestStateMachine;
@BeforeEach
void setUp() {
- requestStateMachine = new RequestStateMachine();
+ RequestStateMachine requestStateMachine = new RequestStateMachine();
requestService = new RequestService(requestRepository, requestStateMachine);
}
diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java
index 62837cbf5dec..d160814a00ed 100644
--- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java
+++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java
@@ -35,7 +35,7 @@ class RequestStateMachineTests {
private RequestStateMachine requestStateMachine;
@BeforeEach
- public void setUp() {
+ void setUp() {
requestStateMachine = new RequestStateMachine();
}
diff --git a/money/README.md b/money/README.md
index 55e6c2f69214..23535688f851 100644
--- a/money/README.md
+++ b/money/README.md
@@ -2,10 +2,13 @@
title: "Money Pattern in Java: Encapsulating Monetary Values with Currency Consistency"
shortTitle: Money
description: "Learn how the Money design pattern in Java ensures currency safety, precision handling, and maintainable financial operations. Explore examples, applicability, and benefits of the pattern."
-category: Behavioral
+category: Structural
language: en
tag:
- - Encapsulation
+ - Business
+ - Domain
+ - Encapsulation
+ - Immutable
---
## Also known as
@@ -14,147 +17,115 @@ tag:
## Intent of Money Design Pattern
-The Money design pattern provides a robust way to encapsulate monetary values and their associated currencies. It ensures precise calculations, currency consistency, and maintainability of financial logic in Java applications.
+Encapsulate monetary values and their associated currency in a domain-specific object.
## Detailed Explanation of Money Pattern with Real-World Examples
-### Real-world example
+Real-world example
-> Imagine an e-commerce platform where customers shop in their local currencies. The platform needs to calculate order totals, taxes, and discounts accurately while handling multiple currencies seamlessly.
+> Imagine an online gift card system, where each gift card holds a specific balance in a particular currency. Instead of just using a floating-point value for the balance, the system uses a Money object to precisely track the amount and currency. Whenever someone uses the gift card, it updates the balance with accurate calculations that avoid floating-point rounding errors, ensuring the domain logic stays consistent and accurate.
-In this example:
-- Each monetary value (like a product price or tax amount) is encapsulated in a `Money` object.
-- The `Money` class ensures that only values in the same currency are combined and supports safe currency conversion for global operations.
-
-### In plain words
+In plain words
> The Money pattern encapsulates both an amount and its currency, ensuring financial operations are precise, consistent, and maintainable.
-### Wikipedia says
+Wikipedia says
+
+> The Money design pattern encapsulates a monetary value and its currency, allowing for safe arithmetic operations and conversions while preserving accuracy and consistency in financial calculations.
+
+Mind map
-> "The Money design pattern encapsulates a monetary value and its currency, allowing for safe arithmetic operations and conversions while preserving accuracy and consistency in financial calculations."
+
+
+Flowchart
+
+
## Programmatic Example of Money Pattern in Java
-### Money Class
+In this example, we're creating a `Money` class to demonstrate how monetary values can be encapsulated along with their currency. This approach helps avoid floating-point inaccuracies, ensures arithmetic operations are handled consistently, and provides a clear domain-centric way of working with money.
```java
-
-/**
- * Represents a monetary value with an associated currency.
- * Provides operations for basic arithmetic (addition, subtraction, multiplication),
- * as well as currency conversion while ensuring proper rounding.
- */
+@AllArgsConstructor
@Getter
public class Money {
- private @Getter double amount;
- private @Getter String currency;
+ private double amount;
+ private String currency;
- public Money(double amnt, String curr) {
- this.amount = amnt;
- this.currency = curr;
- }
+ public Money(double amnt, String curr) {
+ this.amount = amnt;
+ this.currency = curr;
+ }
- private double roundToTwoDecimals(double value) {
- return Math.round(value * 100.0) / 100.0;
- }
+ private double roundToTwoDecimals(double value) {
+ return Math.round(value * 100.0) / 100.0;
+ }
- public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException {
- if (!moneyToBeAdded.getCurrency().equals(this.currency)) {
- throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies");
+ public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException {
+ if (!moneyToBeAdded.getCurrency().equals(this.currency)) {
+ throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies");
+ }
+ this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount());
}
- this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount());
- }
-
- public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException {
- if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) {
- throw new CannotSubtractException("You are trying to subtract two different currencies");
- } else if (moneyToBeSubtracted.getAmount() > this.amount) {
- throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have");
+
+ public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException {
+ if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) {
+ throw new CannotSubtractException("You are trying to subtract two different currencies");
+ } else if (moneyToBeSubtracted.getAmount() > this.amount) {
+ throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have");
+ }
+ this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount());
}
- this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount());
- }
- public void multiply(int factor) {
- if (factor < 0) {
- throw new IllegalArgumentException("Factor must be non-negative");
+ public void multiply(int factor) {
+ if (factor < 0) {
+ throw new IllegalArgumentException("Factor must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * factor);
}
- this.amount = roundToTwoDecimals(this.amount * factor);
- }
- public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) {
- if (exchangeRate < 0) {
- throw new IllegalArgumentException("Exchange rate must be non-negative");
+ public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) {
+ if (exchangeRate < 0) {
+ throw new IllegalArgumentException("Exchange rate must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * exchangeRate);
+ this.currency = currencyToChangeTo;
}
- this.amount = roundToTwoDecimals(this.amount * exchangeRate);
- this.currency = currencyToChangeTo;
- }
}
+```
-## When to Use the Money Pattern
-
-The Money pattern should be used in scenarios where:
+By encapsulating all money-related logic in a single class, we reduce the risk of mixing different currencies, improve clarity of the codebase, and facilitate future modifications such as adding new currencies or refining rounding rules. This pattern ultimately strengthens the domain model by treating money as a distinct concept rather than just another numeric value.
-1. **Currency-safe arithmetic operations**
- To ensure that arithmetic operations like addition, subtraction, and multiplication are performed only between amounts in the same currency, preventing inconsistencies or errors in calculations.
-
-2. **Accurate rounding for financial calculations**
- Precise rounding to two decimal places is critical to maintain accuracy and consistency in financial systems.
-
-3. **Consistent currency conversion**
- When handling international transactions or displaying monetary values in different currencies, the Money pattern facilitates easy and reliable conversion using exchange rates.
+## When to Use the Money Pattern
-4. **Encapsulation of monetary logic**
- By encapsulating all monetary operations within a dedicated class, the Money pattern improves maintainability and reduces the likelihood of errors.
+* When financial calculations or money manipulations are part of the business logic
+* When precise handling of currency amounts is required to avoid floating-point inaccuracies
+* When domain-driven design principles and strong typing are desired
-5. **Preventing errors in financial operations**
- Strict validation ensures that operations like subtraction or multiplication are only performed when conditions are met, safeguarding against misuse or logical errors.
+## Real-World Applications of Monad Pattern in Java
-6. **Handling diverse scenarios in financial systems**
- Useful in complex systems like e-commerce, banking, and payroll applications where precise and consistent monetary value handling is crucial.
+* JSR 354 (Java Money and Currency) library in Java
+* Custom domain models in e-commerce and accounting systems
----
## Benefits and Trade-offs of Money Pattern
-### Benefits
-1. **Precision and Accuracy**
- The Money pattern ensures precise handling of monetary values, reducing the risk of rounding errors.
-
-2. **Encapsulation of Business Logic**
- By encapsulating monetary operations, the pattern enhances maintainability and reduces redundancy in financial systems.
-
-3. **Currency Safety**
- It ensures operations are performed only between amounts of the same currency, avoiding logical errors.
+Benefits
-4. **Improved Readability**
- By abstracting monetary logic into a dedicated class, the code becomes easier to read and maintain.
+* Provides a single, type-safe representation of monetary amounts and currency
+* Encourages encapsulation of related operations such as addition, subtraction, and formatting
+* Avoids floating-point errors by using integers or specialized decimal libraries
-5. **Ease of Extension**
- Adding new operations, handling different currencies, or incorporating additional business rules is straightforward.
+Trade-offs
-### Trade-offs
-1. **Increased Complexity**
- Introducing a dedicated `Money` class can add some overhead, especially for small or simple projects.
-
-2. **Potential for Misuse**
- Without proper validation and handling, incorrect usage of the Money pattern may introduce subtle bugs.
-
-3. **Performance Overhead**
- Precision and encapsulation might slightly affect performance in systems with extremely high transaction volumes.
-
----
+* Requires additional classes and infrastructure to handle currency conversions and formatting
+* Might introduce performance overhead when performing large numbers of money operations
## Related Design Patterns
-1. **Value Object**
- Money is a classic example of the Value Object pattern, where objects are immutable and define equality based on their value.
- Link:https://martinfowler.com/bliki/ValueObject.html
-2. **Factory Method**
- Factories can be employed to handle creation logic, such as applying default exchange rates or rounding rules.
- Link:https://www.geeksforgeeks.org/factory-method-for-designing-pattern/
----
+* [Value Object](https://java-design-patterns.com/patterns/value-object/): Money is typically a prime example of a domain-driven design value object.
## References and Credits
-- [Patterns of Enterprise Application Architecture](https://martinfowler.com/eaaCatalog/money.html) by Martin Fowler
-- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze)
+* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
diff --git a/money/etc/money-flowchart.png b/money/etc/money-flowchart.png
new file mode 100644
index 000000000000..022233a0d4e9
Binary files /dev/null and b/money/etc/money-flowchart.png differ
diff --git a/money/etc/money-mind-map.png b/money/etc/money-mind-map.png
new file mode 100644
index 000000000000..0f27143fb85c
Binary files /dev/null and b/money/etc/money-mind-map.png differ
diff --git a/money/src/main/java/com/iluwatar/Money.java b/money/src/main/java/com/iluwatar/Money.java
index a486c4b4ca5b..5c2cda9bf74c 100644
--- a/money/src/main/java/com/iluwatar/Money.java
+++ b/money/src/main/java/com/iluwatar/Money.java
@@ -24,6 +24,7 @@
*/
package com.iluwatar;
+import lombok.AllArgsConstructor;
import lombok.Getter;
/**
@@ -31,21 +32,11 @@
* (addition, subtraction, multiplication), as well as currency conversion while ensuring proper
* rounding.
*/
+@AllArgsConstructor
@Getter
public class Money {
- private @Getter double amount;
- private @Getter String currency;
-
- /**
- * Constructs a Money object with the specified amount and currency.
- *
- * @param amnt the amount of money (as a double).
- * @param curr the currency code (e.g., "USD", "EUR").
- */
- public Money(double amnt, String curr) {
- this.amount = amnt;
- this.currency = curr;
- }
+ private double amount;
+ private String currency;
/**
* Rounds the given value to two decimal places.
diff --git a/money/src/test/java/com/iluwater/money/MoneyTest.java b/money/src/test/java/com/iluwater/money/MoneyTest.java
index 491b12ee2e13..6ee01283fdb6 100644
--- a/money/src/test/java/com/iluwater/money/MoneyTest.java
+++ b/money/src/test/java/com/iluwater/money/MoneyTest.java
@@ -59,11 +59,7 @@ void testAddMoney_DifferentCurrency() {
Money money1 = new Money(100.00, "USD");
Money money2 = new Money(50.25, "EUR");
- assertThrows(
- CannotAddTwoCurrienciesException.class,
- () -> {
- money1.addMoney(money2);
- });
+ assertThrows(CannotAddTwoCurrienciesException.class, () -> money1.addMoney(money2));
}
@Test
@@ -83,11 +79,7 @@ void testSubtractMoney_DifferentCurrency() {
Money money1 = new Money(100.00, "USD");
Money money2 = new Money(50.25, "EUR");
- assertThrows(
- CannotSubtractException.class,
- () -> {
- money1.subtractMoney(money2);
- });
+ assertThrows(CannotSubtractException.class, () -> money1.subtractMoney(money2));
}
@Test
@@ -96,11 +88,7 @@ void testSubtractMoney_AmountTooLarge() {
Money money1 = new Money(50.00, "USD");
Money money2 = new Money(60.00, "USD");
- assertThrows(
- CannotSubtractException.class,
- () -> {
- money1.subtractMoney(money2);
- });
+ assertThrows(CannotSubtractException.class, () -> money1.subtractMoney(money2));
}
@Test
@@ -118,11 +106,7 @@ void testMultiply_NegativeFactor() {
// Test multiplying by a negative factor
Money money = new Money(100.00, "USD");
- assertThrows(
- IllegalArgumentException.class,
- () -> {
- money.multiply(-2);
- });
+ assertThrows(IllegalArgumentException.class, () -> money.multiply(-2));
}
@Test
@@ -141,19 +125,12 @@ void testExchangeCurrency_NegativeExchangeRate() {
// Test converting currency with a negative exchange rate
Money money = new Money(100.00, "USD");
- assertThrows(
- IllegalArgumentException.class,
- () -> {
- money.exchangeCurrency("EUR", -0.85);
- });
+ assertThrows(IllegalArgumentException.class, () -> money.exchangeCurrency("EUR", -0.85));
}
@Test
void testAppExecution() {
assertDoesNotThrow(
- () -> {
- App.main(new String[] {});
- },
- "App execution should not throw any exceptions");
+ () -> App.main(new String[] {}), "App execution should not throw any exceptions");
}
}
diff --git a/monolithic-architecture/README.md b/monolithic-architecture/README.md
index 6704cee7c24a..6bb63e04fbfe 100644
--- a/monolithic-architecture/README.md
+++ b/monolithic-architecture/README.md
@@ -1,50 +1,52 @@
---
-title: "Monolithic Ecommerce App: A Cohesive Application Model"
-shortTitle: Monolithic Ecommerce
-description: "Explore the Monolithic Ecommerce application structure, its design intent, benefits, limitations, and real-world applications. Understand its simplicity and practical use cases."
+title: "Monolithic Architecture in Java: A Cohesive Application Model"
+shortTitle: Monolithic Architecture
+description: "Explore the Monolithic Architecture application structure, its design intent, benefits, limitations, and real-world applications. Understand its simplicity and practical use cases."
category: Architectural
language: en
tag:
- - Cohesion
+ - Architecture
+ - Cohesion
+ - Encapsulation
+ - Layered architecture
+ - Modularity
---
-## Monolithic-Ecommerce App
-* A Monolithic Ecommerce example to showcase Monolithic Architecture
+## Also known as
+
+* Single-tier architecture
+* Monolith
## The Intent of Monolithic Design pattern
-> the Monolithic Design Pattern structures an application as a single, cohesive unit where all components—such as business logic, user interface, and data access are tightly integrated and operate as part of a single executable.
+
+Encapsulate all the functionality of an application within a single, cohesive codebase.
## Detailed Explanation of the Monolithic Architecture
-Real-world Example
-> A traditional E-commerce website is the most straightforward example for a monolithic application as it is comprised of a catalogue of products, orders to be made, shopping carts, and payment processes that are all inseperable of each other.
-
-In Plain words
->The monolithic design pattern structures an application as a single unified unit, where all components are tightly coupled and run within a single process.
-
-GeeksforGeeks states
-> Monolithic architecture, a traditional approach in system design, which contains all application components into a single codebase. This unified structure simplifies development and deployment processes, offering ease of management and tight integration. However, because of its rigidity, it is difficult to scale and maintain, which makes it difficult to adjust to changing needs.
-
-Why use MVC for a Monolithic Application ?
->The Model-View-Controller (MVC) pattern is not inherently tied to microservices or distributed systems. It's a software design pattern that organizes the codebase by separating concerns into three distinct layers:
->* Model
->* View
->* Controller
->
-> this also helps maintain the principles of a Monolithic Architecture which are:
->
-> Simple to
->* Develop
->* Test
->* Deploy
->* Scale
->
-
-Architecture diagram
-
-
-
-## We can clearly see that this is a Monolithic application through the main class
-This is a simplified version of the main application that shows the main interaction point with the CLI and how a user is registered
+
+Real-world example
+
+> An analogous real-world example of the Monolithic Architecture pattern is a department store. Just like a monolithic Java application, a department store hosts all product categories, sales, storage, and customer services within a single, large building. It simplifies shopping by consolidating everything under one roof, making operations straightforward and easy to manage. However, expanding or rearranging specific departments becomes increasingly challenging over time, similar to how scaling individual functionalities within a monolithic system can become complex and cumbersome.
+
+In plain words
+
+> The monolithic design pattern structures an application as a single unified unit, where all components are tightly coupled and run within a single process.
+
+Wikipedia says
+
+> In software engineering, a monolithic application is a single unified software application that is self-contained and independent of other applications, but typically lacks flexibility. There are advantages and disadvantages of building applications in a monolithic style of software architecture, depending on requirements. Monolith applications are relatively simple and have a low cost but their shortcomings are lack of elasticity, fault tolerance and scalability.
+
+Mind map
+
+
+
+Flowchart
+
+
+
+## Programmatic Example of Monolithic Architecture in Java
+
+This is a simplified version of the main application, demonstrating how a monolithic architecture can be implemented. Here, all the essential services—such as user management (`UserCon`), product management (`ProductCon`), and order processing (`OrderCon`) — are tightly integrated within a single executable Java application. The CLI provides a straightforward user interaction point where operations like user registration, adding products, and placing orders are handled.
+
```java
@SpringBootApplication
public class EcommerceApp implements CommandLineRunner {
@@ -105,47 +107,46 @@ public class EcommerceApp implements CommandLineRunner {
}
```
-### We can clearly reach the conclusion that all of these classes reside under the same module and are essential for each other's functionality, this is supported by the presence of all relevant classes as parts of the main application class.
-
-## When should you resort to a Monolithic Architecture ?
->* An enterprise Starting off with a relatively small team
->* Simplicity is the most important factor of the project
->* Maintaining less entry points to the system is cruical
->* Prototyping ideas
->
-## Pros & Cons of using Monolithic Architecture
->### Pros:
->* Simple Development: Easy to develop and deploy.
->* Unified Codebase: All code in one place, simplifying debugging.
->* Better Performance: No inter-service communication overhead.
->* Lower Costs: Minimal infrastructure and tooling requirements.
->* Ease of Testing: Single application makes end-to-end testing straightforward.
-> * This is also assisted by the MVC structure employed in this example.
->### Cons:
->* Scalability Issues: Cannot scale individual components.
->* Tight Coupling: Changes in one area may impact the whole system.
->* Deployment Risks: A single failure can crash the entire application.
->* Complex Maintenance: Harder to manage as the codebase grows.
->* Limited Flexibility: Difficult to adopt new technologies for specific parts.
-
-## Real-World Applications of Monolithic architecture Pattern in Java
->* E-Commerce Platforms
->* Content Management Systems (CMS)
->* Banking and Financial Systems
->* Enterprise Resource Planning (ERP) Systems
->* Retail Point of Sale (POS) Systems
+
+In this example, the core business functionalities are closely interconnected, sharing common resources and residing within the same codebase. This approach simplifies initial application development, testing, and deployment, as each component can easily access the others directly without the overhead of inter-service communication. However, as the application grows, scaling individual components independently becomes more challenging, highlighting the key trade-off inherent in the monolithic architecture.
+
+## When to Use the Monolithic Architecture in Java
+
+* Suitable for small to medium-sized applications where simplicity, cohesive development, and ease of deployment outweigh scalability and flexibility requirements.
+* Applicable in contexts where initial rapid development and ease of testing are prioritized.
+
+## Real-World Applications of Monolithic Architecture in Java
+
+* Early-stage Java web applications developed using frameworks like Spring MVC, Java EE (Servlets/JSP), or frameworks like Play.
+* Traditional Java enterprise applications packaged and deployed as WAR or EAR files on application servers such as Apache Tomcat, Jetty, or WildFly.
+* Standalone Java applications and desktop applications packaged as single executable JAR files.
+
+## Benefits and Trade-offs of Monolithic Architecture
+
+Benefits:
+
+* Simpler to develop, test, and deploy as the application is a single unit.
+* Easier debugging and performance monitoring due to the single unified runtime.
+* Typically faster initial development and straightforward management of dependencies.
+
+Trade-offs:
+
+* Poor scalability and potential performance bottlenecks as the application grows.
+* Limited modularity, leading to increased complexity and harder maintainability over time.
+* Slow deployments and updates due to a single large codebase.
+* Difficult to scale individual functionalities independently.
+
+## Related Patterns
+
+* Microservices Architecture: Breaks down a monolithic application into independently deployable services, directly addressing the scalability and maintainability limitations of monoliths.
+* [Layered Architecture](https://java-design-patterns.com/patterns/layered-architecture/): Often used internally within monoliths to provide clear separation between presentation, business logic, and persistence layers.
## References
->* [GeeksforGeeks](https://www.geeksforgeeks.org/monolithic-architecture-system-design/)
->* [Wikipedia](https://en.wikipedia.org/wiki/Monolithic_application)
->* [vFunction](https://vfunction.com/blog/what-is-monolithic-application/#:~:text=A%20traditional%20e%2Dcommerce%20platform,inseparable%20components%20of%20the%20system.) Blog post
->* [Microservices.io](https://microservices.io/patterns/monolithic.html)
->* [IBM](https://www.ibm.com/think/topics/monolithic-architecture)
->#### References used to create the code
->* [Mockito](https://site.mockito.org/) -Testing
->* [Junit](https://junit.org/junit5/docs/current/user-guide/) -Testing
->* [Springboot](https://docs.spring.io/spring-boot/index.html) -Web Application Initiation (implemented but not utilized in this example)
->* [Sprint Data Jpa](https://docs.spring.io/spring-data/jpa/reference/index.html) -Database connection
->* [Lombok](https://projectlombok.org/) -Simplifying Classes
->* [Log4j](https://logging.apache.org/log4j/2.x/index.html) -Capturing Logs
->* [H2 Databse](https://www.h2database.com/html/tutorial.html) -Efficient, Simple, Dynamic Databse
\ No newline at end of file
+
+* [Building Microservices](https://amzn.to/3UACtrU)
+* [Fundamentals of Software Architecture: An Engineering Approach](https://amzn.to/4cx4A2N)
+* [Monolithic Architecture - System Design (GeeksforGeeks)](https://www.geeksforgeeks.org/monolithic-architecture-system-design/)
+* [Monolithic Application (Wikipedia)](https://en.wikipedia.org/wiki/Monolithic_application)
+* [Pattern: Monolithic Architecture (Microservices.io)](https://microservices.io/patterns/monolithic.html)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
+* [What Is Monolithic Architecture? (IBM)](https://www.ibm.com/think/topics/monolithic-architecture)
diff --git a/monolithic-architecture/etc/monolithic-architecture-flowchart.png b/monolithic-architecture/etc/monolithic-architecture-flowchart.png
new file mode 100644
index 000000000000..179faaf7909b
Binary files /dev/null and b/monolithic-architecture/etc/monolithic-architecture-flowchart.png differ
diff --git a/monolithic-architecture/etc/monolithic-architecture-mind-map.png b/monolithic-architecture/etc/monolithic-architecture-mind-map.png
new file mode 100644
index 000000000000..cf955379806a
Binary files /dev/null and b/monolithic-architecture/etc/monolithic-architecture-mind-map.png differ
diff --git a/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java
index 1f31157325e0..967b86e7080f 100644
--- a/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java
+++ b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java
@@ -95,11 +95,7 @@ void testPlaceOrderUserNotFound() {
new OrderController(mockOrderRepo, mockUserRepository, mockProductRepository);
Exception exception =
- assertThrows(
- NonExistentUserException.class,
- () -> {
- orderCon.placeOrder(1L, 1L, 5);
- });
+ assertThrows(NonExistentUserException.class, () -> orderCon.placeOrder(1L, 1L, 5));
assertEquals("User with ID 1 not found", exception.getMessage());
}
@@ -119,11 +115,7 @@ void testPlaceOrderProductNotFound() {
new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository);
Exception exception =
- assertThrows(
- NonExistentProductException.class,
- () -> {
- orderCon.placeOrder(1L, 1L, 5);
- });
+ assertThrows(NonExistentProductException.class, () -> orderCon.placeOrder(1L, 1L, 5));
assertEquals("Product with ID 1 not found", exception.getMessage());
}
@@ -196,11 +188,7 @@ void testPlaceOrderInsufficientStock() {
new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository);
Exception exception =
- assertThrows(
- InsufficientStockException.class,
- () -> {
- orderCon.placeOrder(1L, 1L, 5);
- });
+ assertThrows(InsufficientStockException.class, () -> orderCon.placeOrder(1L, 1L, 5));
assertEquals("Not enough stock for product 1", exception.getMessage());
}
diff --git a/pom.xml b/pom.xml
index 13dd8eaa02b1..bc12b56e9d98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,7 +46,7 @@
2.0.17
- 0.8.12
+ 0.8.13
1.4
4.7.0
2.11.0
@@ -71,6 +71,7 @@
Java Design Patterns
+
abstract-document
abstract-factory
active-object
@@ -243,6 +244,8 @@
virtual-proxy
visitor
backpressure
+ actor-model
+
@@ -481,7 +484,7 @@
com.diffplug.spotless
spotless-maven-plugin
- 2.44.3
+ 2.44.4
diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md
index 8ff29f2c6a0f..86f6c53166f9 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
-
+Sequence diagram
+
+
## 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 weather events, like earthquakes, floods, and tornadoes.
+* Sending notifications based on temperature changes.
+* Sending emails to customer support when support tickets are created.
-- 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.
+### Defining the Message
-The Message class below will hold the content of the message we need to pass between the publisher and the subscribers.
+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
+
+The Publisher maintains a collection of topics it can publish to.
-- Each new topic has to be registered in the publisher.
-- Publish method will publish the _Message_ to the corresponding _Topic_.
+* 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
```
-## When to Use the Publish-Subscribe Pattern
+This demonstrates:
-- 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.
+* 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.
-- 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.
+## When to Use the Publish-Subscribe Pattern
-- 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.
+* 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.
-- 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).
+## Real-World Applications of Publish-Subscribe Pattern in Java
-- Broadcasting Notifications
- - When a message should be delivered to multiple receivers.
- - Example: Sending promotional offers to multiple user devices.
+* 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
-- 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.
+## Benefits and Trade-offs of Publish-Subscribe Pattern
-## When to avoid the Publish-Subscribe Pattern
+Benefits:
-- Simple applications where direct calls suffice.
-- Strong consistency requirements (e.g., banking transactions).
-- Low-latency synchronous communication needed.
+* 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.
-## Benefits and Trade-offs of Publish-Subscribe Pattern
+Trade-offs:
-### 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.
-
-### 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 000000000000..8fb1213417b5
Binary files /dev/null and b/publish-subscribe/etc/publish-subscribe-sequence-diagram.png differ
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 8080553078f2..50c780cb682d 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 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 05e16e5a1110..ecf015752e48 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.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 void beforeEach(ExtensionContext extensionContext) throws Exception {
}
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 a08624a3fab2..636e1ed66c0f 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 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 eb2d87c8c127..cbb5a9882e71 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.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 d3db88c421bf..7105db20fa93 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 org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-public class PublisherTest {
+class PublisherTest {
@RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension();
diff --git a/service-stub/README.md b/service-stub/README.md
index 980e3561d697..5fa4533587ea 100644
--- a/service-stub/README.md
+++ b/service-stub/README.md
@@ -2,26 +2,30 @@
title: "Service Stub Pattern in Java: Simplifying Testing with Stub Implementations"
shortTitle: Service Stub
description: "Explore the Service Stub design pattern in Java using a Sentiment Analysis example. Learn how stub implementations provide dummy services to facilitate testing and development."
-category: Structural
+category: Testing
language: en
tag:
+ - API design
- Decoupling
+ - Integration
+ - Microservices
+ - Testing
---
## Also known as
-* Dummy Service
-* Fake Service
+* Service Mock
+* Test Double
## Intent of Service Stub Pattern
-The Service Stub pattern provides a lightweight, dummy implementation of an external service to allow testing or development without relying on the real service, which may be unavailable, slow, or resource-intensive.
+Provide a lightweight, simplified implementation of a remote or external service to facilitate testing in isolation.
## Detailed Explanation of Service Stub Pattern with Real-World Example
Real-world example
-> In this example, we simulate a **Sentiment Analysis Service**. The real implementation delays execution and randomly decides the sentiment. The stub implementation, on the other hand, quickly returns predefined responses based on input text ("good", "bad", or neutral), making it ideal for testing.
+> A real-world analogy for the Service Stub pattern could be a flight simulator used to train pilots. Instead of learning to fly directly using a real airplane—which would be costly, dangerous, and often impractical—pilots initially practice within a simulator. This simulator provides predefined, realistic scenarios and reactions, enabling pilots to train safely, repeatedly, and predictably without the complexities and risks associated with actual flight operations. Similarly, a Service Stub provides controlled, predictable responses for external services during testing, simplifying and accelerating software development and testing processes.
In plain words
@@ -37,21 +41,24 @@ Sequence diagram
## Programmatic Example of Service Stub Pattern in Java
-We define a `SentimentAnalysisService` interface and provide two implementations:
+We demonstrate the Service Stub pattern using a simple sentiment analysis example. To illustrate this clearly, we define a common interface `SentimentAnalysisServer` and create two separate implementations:
-1. **RealSentimentAnalysisServer**: Simulates a slow, random sentiment analysis system.
-2. **StubSentimentAnalysisServer**: Returns a deterministic result based on input keywords.
+**RealSentimentAnalysisServer**: Represents a slow, realistic sentiment analysis service, returning random sentiment results to simulate external complexity and latency.
+
+**StubSentimentAnalysisServer**: Provides fast, deterministic results based on simple keyword matching, suitable for isolated testing without external dependencies.
+
+### Step-by-step Example Implementation
+
+First, define a common interface that both implementations will use:
-### Example Implementation
-Both the real service and the stub implement the interface below.
```java
public interface SentimentAnalysisServer {
String analyzeSentiment(String text);
}
```
-The real sentiment analysis class returns a random response for a given input and simulates the runtime by sleeping
-the Thread for 5 seconds. The Supplier\ allows injecting controlled sentiment values during testing, ensuring
-deterministic outputs.
+
+Next, we create a realistic implementation that simulates a slow, external service. It introduces a delay of 5 seconds and returns random sentiment results (`Positive`, `Negative`, or `Neutral`). For flexibility and easier testing, it allows injecting a custom sentiment supplier:
+
```java
public class RealSentimentAnalysisServer implements SentimentAnalysisServer {
@@ -77,8 +84,9 @@ public class RealSentimentAnalysisServer implements SentimentAnalysisServer {
}
}
```
-The stub implementation simulates the real sentiment analysis class and provides a deterministic output
-for a given input. Additionally, its runtime is almost zero.
+
+Then, we provide a simplified stub implementation designed specifically for testing purposes. It returns immediate and predictable results based on simple keyword detection. This enables tests to run quickly and consistently without relying on external factors:
+
```java
public class StubSentimentAnalysisServer implements SentimentAnalysisServer {
@@ -95,9 +103,10 @@ public class StubSentimentAnalysisServer implements SentimentAnalysisServer {
}
}
}
-
```
-Here is the main function of the App class (entry point to the program)
+
+Finally, here's the main application logic illustrating how to use both implementations in practice. Notice the significant performance difference between the real and stub implementations:
+
```java
@Slf4j
public static void main(String[] args) {
@@ -116,39 +125,46 @@ Here is the main function of the App class (entry point to the program)
LOGGER.info("The sentiment is: {}", sentiment);
}
```
-## When to Use the Service Stub Pattern in Java
-Use the Service Stub pattern when:
+In summary, implementing a Service Stub involves creating a simplified substitute (`StubSentimentAnalysisServer`) for an actual external service (`RealSentimentAnalysisServer`). This approach allows your tests to run quickly and consistently by isolating them from external complexity and unpredictability.
+
+## When to Use the Service Stub Pattern in Java
-* Testing components that depend on external services.
-* The real service is slow, unreliable, or unavailable.
-* You need predictable, predefined responses.
-* Developing offline without real service access.
+* When testing systems with external or third-party service dependencies.
+* In integration tests to isolate the service being tested from network or external dependencies.
+* During development when the actual services are unavailable or unreliable.
+* To speed up tests by avoiding calls to slower external systems.
## Real-World Applications of Service Stub Pattern in Java
-* Simulating APIs (payments, recommendation systems) during testing.
-* Bypassing external AI/ML models in tests.
-* Simplifying integration testing.
+* WireMock: Widely used in Java testing to stub HTTP-based external services.
+* Mockito: Allows creating lightweight stubs for dependencies in unit testing.
+* Spring Cloud Contract: Provides contracts and stub servers for services in microservices architectures.
## Benefits and Trade-offs of Service Stub Pattern
Benefits:
-* Reduces dependencies.
-* Provides predictable behavior.
-* Speeds up testing.
+* Simplifies testing by eliminating dependencies on external systems.
+* Speeds up testing processes by removing latency from external network calls.
+* Allows consistent, repeatable, and predictable testing scenarios.
+* Enables parallel test execution, improving overall development productivity.
Trade-offs:
-* Requires maintaining stub logic.
-* May not fully represent real service behavior.
+* Stubs need to be regularly updated to reflect changes in the actual external services.
+* May introduce false confidence if stubs do not accurately represent external system behavior.
+* Can lead to additional overhead and maintenance of stub configurations.
## Related Java Design Patterns
-* [Proxy](https://java-design-patterns.com/patterns/proxy/)
-* [Strategy](https://java-design-patterns.com/patterns/strategy/)
+* [Adapter](https://java-design-patterns.com/patterns/adapter/): Service Stub may sometimes implement Adapter interfaces to mimic external dependencies in a test environment.
+* Mock Object: Similar to Service Stub, but Mock Objects usually verify interactions explicitly, while Service Stubs primarily provide predefined responses without verification.
+* [Proxy](https://java-design-patterns.com/patterns/proxy/): Both Service Stub and Proxy introduce intermediate objects to control access or communication with actual components, though Proxy typically manages access control and communication, while Service Stub specifically aims to isolate for testing.
## References and Credits
-* [Martin Fowler: Test Stubs](https://martinfowler.com/articles/mocksArentStubs.html)
+* [Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation](https://amzn.to/4bjhTSK)
+* [Growing Object-Oriented Software, Guided by Tests](https://amzn.to/4dGfIuk)
+* [Mocks Aren't Stubs (Martin Fowler)](https://martinfowler.com/articles/mocksArentStubs.html)
+* [xUnit Test Patterns: Refactoring Test Code](https://amzn.to/4dHGDpm)
diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java
index dc7c174cbe54..c65f6d172151 100644
--- a/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java
+++ b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java
@@ -36,10 +36,8 @@ public class RealSentimentAnalysisServer implements SentimentAnalysisServer {
* A real sentiment analysis implementation would analyze the input string using, e.g., NLP and
* determine whether the sentiment is positive, negative or neutral. Here we simply choose a
* random number to simulate this. The "model" may take some time to process the input and we
- * simulate this by delaying the execution 5 seconds.
- *
- * @param text the input string to analyze
- * @return sentiment classification result (Positive, Negative, or Neutral)
+ * simulate this by delaying the execution 5 seconds. Analyzes the sentiment of the given input
+ * string and returns the classification result (Positive, Negative, or Neutral).
*/
private final Supplier sentimentSupplier;
@@ -61,6 +59,11 @@ public String analyzeSentiment(String text) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
- return sentiment == 0 ? "Positive" : sentiment == 1 ? "Negative" : "Neutral";
+
+ return switch (sentiment) {
+ case 0 -> "Positive";
+ case 1 -> "Negative";
+ default -> "Neutral";
+ };
}
}
diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java
index 13d2d190dad2..a0acda718b00 100644
--- a/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java
+++ b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java
@@ -28,7 +28,7 @@
import org.junit.jupiter.api.Test;
-public class AppTest {
+class AppTest {
@Test
void shouldExecuteWithoutException() {
assertDoesNotThrow(() -> App.main(new String[] {}));
diff --git a/session-facade/README.md b/session-facade/README.md
index 4724afcfbda3..fb8fe74d7f50 100644
--- a/session-facade/README.md
+++ b/session-facade/README.md
@@ -5,28 +5,34 @@ description: "Learn how to implement the Session Facade Design Pattern in Java t
category: Structural
language: en
tag:
- - Abstraction
+ - API design
+ - Abstraction
+ - Architecture
+ - Business
+ - Decoupling
+ - Enterprise patterns
+ - Facade
+ - Layered architecture
+ - Session management
---
## Also known as
-* Session Facade
+* Remote Facade
## Intent of Session Facade Design Pattern
-Abstracting the underlying business object interactions by providing a service layer that exposes only the required interfaces
+Provide a simplified interface to a complex subsystem, reducing complexity and coupling between client and business logic in enterprise Java applications.
## Detailed Explanation of Session Facade Pattern with Real-World Examples
Real-world example
-> In an e-commerce website, users interact with several subsystems like product catalogs, shopping carts,
-> payment services, and order management. The Session Facade pattern provides a simplified, centralized interface for these subsystems,
-> allowing the client to interact with just a few high-level methods (e.g., addToCart(), placeOrder(), selectPaymentMethod()), instead of directly communicating with each subsystem, using a facade supports low coupling between classes and high cohesion within each service, allowing them to focus on their specific responsibilities.
+> A real-world analogy for the Session Facade pattern is a hotel concierge service. Guests (clients) don't directly interact with various departments like housekeeping, kitchen, transport services, or maintenance. Instead, they interact with the concierge (the facade), who simplifies these interactions. When a guest requests services like room cleaning, dinner reservations, or taxi bookings, the concierge handles communication with multiple hotel departments behind the scenes, providing a simplified and unified interface to the guest, reducing complexity and enhancing guest satisfaction.
In plain words
-> The Session Facade design pattern is an excellent choice for decoupling complex components of the system that need to be interacting frequently.
+> Session Facade provides a simplified interface to complex business logic in Java applications, reducing client complexity and minimizing network overhead by encapsulating interactions within a single session component.
Sequence diagram
@@ -34,14 +40,16 @@ Sequence diagram
## Programmatic Example of Session Facade Pattern in Java
-The Session Facade design pattern is a structural design pattern that provides a simplified interface to a set of complex subsystems, reducing the complexity for the client. This pattern is particularly useful in situations where the client needs to interact with multiple services or systems but doesn’t need to know the internal workings of each service.
+The Session Facade pattern is a structural design pattern that provides a simplified interface to complex subsystems, making the system easier for clients to interact with. It is especially useful when a client needs access to multiple underlying services without needing to understand their internal complexities.
-In the context of an e-commerce website, imagine a system where users can browse products, add items to the shopping cart, process payments, and place orders. Instead of the client directly interacting with each individual service (cart, order, payment), the Session Facade provides a single, unified interface for these operations.
+In the context of an e-commerce website, consider a scenario where users browse products, manage their shopping carts, place orders, and process payments. Rather than directly interacting with each subsystem individually (such as the cart, order, and payment systems), a client can communicate through a single unified Session Facade interface.
-Example Scenario:
-In this example, the ShoppingFacade class manages interactions with three subsystems: the `CartService`, `OrderService`, and `PaymentService`. The client interacts with the facade to perform high-level operations like adding items to the cart, placing an order, and selecting a payment method.
+### Example Scenario:
+
+In this example, the `ShoppingFacade` class simplifies client interactions with three services: the `CartService`, `OrderService`, and `PaymentService`. The client uses the facade to perform high-level operations like adding products to the cart, placing an order, and choosing a payment method, without needing to know the underlying details.
+
+Here's a simplified Java program demonstrating this pattern:
-Here’s a simplified programmatic example:
```java
public class App {
public static void main(String[] args) {
@@ -53,14 +61,15 @@ public class App {
}
```
-The `ShoppingFacade` acts as an intermediary that facilitates interaction between different services promoting low coupling between these services.
+The `ShoppingFacade` serves as a centralized point of interaction for various shopping-related operations, thereby reducing direct coupling between client code and individual subsystem services:
+
```java
public class ShoppingFacade {
-
+
private final CartService cartService;
private final OrderService orderService;
private final PaymentService paymentService;
-
+
public ShoppingFacade() {
Map productCatalog = new HashMap<>();
productCatalog.put(1, new Product(1, "Wireless Mouse", 25.99, "Ergonomic wireless mouse with USB receiver."));
@@ -70,42 +79,45 @@ public class ShoppingFacade {
orderService = new OrderService(cart);
paymentService = new PaymentService();
}
-
+
public Map getCart() {
return this.cartService.getCart();
}
-
+
public void addToCart(int productId) {
this.cartService.addToCart(productId);
}
-
+
public void removeFromCart(int productId) {
this.cartService.removeFromCart(productId);
}
-
+
public void order() {
this.orderService.order();
}
-
+
public Boolean isPaymentRequired() {
double total = this.orderService.getTotal();
- if (total == 0.0) {
+ if (total==0.0) {
LOGGER.info("No payment required");
return false;
}
return true;
}
-
+
public void processPayment(String method) {
Boolean isPaymentRequired = isPaymentRequired();
if (Boolean.TRUE.equals(isPaymentRequired)) {
paymentService.selectPaymentMethod(method);
}
}
+}
```
-Console output for starting the `App` class's `main` method:
+### Console Output
+
+When running the provided example (App.main()), the output might look similar to:
```
19:43:17.883 [main] INFO com.iluwatar.sessionfacade.CartService -- ID: 1
@@ -115,41 +127,40 @@ Description: Ergonomic wireless mouse with USB receiver. successfully added to t
19:43:17.910 [main] INFO com.iluwatar.sessionfacade.OrderService -- Client has chosen to order [ID: 1
```
-This is a basic example of the Session Facade design pattern. The actual implementation would depend on specific requirements of your application.
+This simplified example demonstrates the essence of the Session Facade pattern. Your actual implementation may vary based on the specific needs of your application.
## When to Use the Session Facade Pattern in Java
-* Use when building complex applications with multiple interacting services, where you want to simplify the interaction between various subsystems.
-* Ideal for decoupling complex systems that need to interact but should not be tightly coupled.
-* Suitable for applications where you need a single point of entry to interact with multiple backend services, like ecommerce platforms, booking systems, or order management systems.
+* When dealing with complex enterprise applications containing multiple business objects.
+* To provide simplified API calls to clients, hiding the underlying complexity.
+* When seeking improved performance and reduced network calls between clients and servers.
## Real-World Applications of Server Session Pattern in Java
-* Enterprise JavaBeans (EJB)
-* Java EE (Jakarta EE) Applications
+* Java EE applications utilizing Enterprise JavaBeans (EJB) as session facades to encapsulate business logic.
+* Spring-based applications using services as session facades to simplify interactions between controllers and repositories.
## Benefits and Trade-offs of Server Session Pattern
+Benefits:
-* Simplifies client-side logic by providing a single entry point for complex operations across multiple services.
-* Decouples components of the application, making them easier to maintain, test, and modify without affecting other parts of the system.
-* Improves modularity by isolating the implementation details of subsystems from the client.
-* Centralizes business logic in one place, making the code easier to manage and update.
+* Reduces complexity by providing a simpler interface to a subsystem.
+* Improves performance by minimizing network traffic and reducing remote calls.
+* Enhances modularity and maintainability by clearly separating business logic and client interactions.
-## Trade-offs:
+Trade-offs:
-* Potential performance bottleneck: Since all requests pass through the facade, it can become a bottleneck if not optimized.
-* Increased complexity: If the facade becomes too large or complex, it could counteract the modularity it aims to achieve.
-* Single point of failure: If the facade encounters issues, it could affect the entire system's operation, making it crucial to handle errors and exceptions properly.
+* Can introduce additional layers that might increase initial complexity.
+* Risk of creating overly broad facades that violate single responsibility principles.
## Related Java Design Patterns
-* [Facade](https://java-design-patterns.com/patterns/facade/): The Session Facade pattern is a specific application of the more general Facade pattern, which simplifies access to complex subsystems.
-* [Command](https://java-design-patterns.com/patterns/command/): Useful for encapsulating requests and passing them to the session facade, which could then manage the execution order.
-* [Singleton](https://java-design-patterns.com/patterns/singleton/): Often used to create a single instance of the session facade for managing the entire workflow of a subsystem.
+* [Data Transfer Object (DTO)](https://java-design-patterns.com/patterns/data-transfer-object/): Often used together, Session Facade simplifies data transfer by utilizing DTOs to encapsulate data passed between client and server.
+* [Facade](https://java-design-patterns.com/patterns/facade/): Session Facade is a specialized version of the Facade pattern, applied specifically in enterprise systems to manage business logic and remote interactions.
## References and Credits
* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap)
-* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
+* [Real World Java EE Patterns-Rethinking Best Practices](https://amzn.to/3EvkzS8)
+* [Remote Facade (Martin Fowler)](https://martinfowler.com/eaaCatalog/remoteFacade.html)
diff --git a/session-facade/pom.xml b/session-facade/pom.xml
index 6dada70f5f75..befdb72d7423 100644
--- a/session-facade/pom.xml
+++ b/session-facade/pom.xml
@@ -50,6 +50,11 @@
junit-jupiter-engine
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
org.mockito
mockito-core
diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java
index 707fd944efb8..556c9536d0e9 100644
--- a/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java
+++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java
@@ -27,7 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** The type App test. */
-public class AppTest {
+class AppTest {
/** Should execute application without exception. */
@org.junit.jupiter.api.Test
diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java
index 609cc3559bd2..06852f6d3b31 100644
--- a/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java
+++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java
@@ -27,13 +27,13 @@
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.Logger;
/** The type Payment service test. */
class PaymentServiceTest {
private PaymentService paymentService;
- private OrderService orderService;
private Logger mockLogger;
/** Sets up. */
@@ -44,27 +44,14 @@ void setUp() {
paymentService.LOGGER = mockLogger;
}
- /** Test select cash payment method. */
- @Test
- void testSelectCashPaymentMethod() {
- String method = "cash";
+ @ParameterizedTest
+ @CsvSource({
+ "cash, Client have chosen cash payment option",
+ "credit, Client have chosen credit card payment option",
+ "cheque, Unspecified payment method type"
+ })
+ void testSelectPaymentMethod(String method, String expectedLogMessage) {
paymentService.selectPaymentMethod(method);
- verify(mockLogger).info("Client have chosen cash payment option");
- }
-
- /** Test select credit card payment method. */
- @Test
- void testSelectCreditCardPaymentMethod() {
- String method = "credit";
- paymentService.selectPaymentMethod(method);
- verify(mockLogger).info("Client have chosen credit card payment option");
- }
-
- /** Test select unspecified payment method. */
- @Test
- void testSelectUnspecifiedPaymentMethod() {
- String method = "cheque";
- paymentService.selectPaymentMethod(method);
- verify(mockLogger).info("Unspecified payment method type");
+ verify(mockLogger).info(expectedLogMessage);
}
}
diff --git a/table-inheritance/README.md b/table-inheritance/README.md
index 53e9b2ce002d..fc9f7500286c 100644
--- a/table-inheritance/README.md
+++ b/table-inheritance/README.md
@@ -2,191 +2,255 @@
title: "Table Inheritance Pattern in Java: Modeling Hierarchical Data in Relational Databases"
shortTitle: Table Inheritance
description: "Explore the Table Inheritance pattern in Java with real-world examples, database schema, and tutorials. Learn how to model class hierarchies elegantly in relational databases."
-category: Structural
+category: Data access
language: en
tag:
- - Decoupling
+ - Data access
+ - Database
+ - Inheritance
+ - Persistence
+ - Polymorphism
---
+## Also known as
+
+* Class Table Inheritance
+* Joined Table Inheritance
+
## Intent of Table Inheritance Pattern
-The Table Inheritance pattern models a class hierarchy in a relational database by creating
-separate tables for each class in the hierarchy. These tables share a common primary key, which in
-subclass tables also serves as a foreign key referencing the primary key of the base class table.
-This linkage maintains relationships and effectively represents the inheritance structure. This pattern
-enables the organization of complex data models, particularly when subclasses have unique properties
-that must be stored in distinct tables.
----
+Represent inheritance hierarchies in relational databases by mapping each class in a hierarchy to a database table.
## Detailed Explanation of Table Inheritance Pattern with Real-World Examples
-### Real-World Example
-Consider a **Vehicle Management System** with a `Vehicle` superclass and subclasses like `Car` and `Truck`.
+Real-world example
-- The **Vehicle Table** stores attributes common to all vehicles, such as `make`, `model`, and `year`. Its primary key (`id`) uniquely identifies each vehicle.
-- The **Car Table** and **Truck Table** store attributes specific to their respective types, such as `numberOfDoors` for cars and `payloadCapacity` for trucks.
-- The `id` column in the **Car Table** and **Truck Table** serves as both the primary key for those tables and a foreign key referencing the `id` in the **Vehicle Table**.
+> A classic real-world analogy for the Table Inheritance (Joined Table) pattern is managing employee records in an organization:
+> Imagine a company's database storing information about employees. All employees have common attributes (name, employee ID, hire date), stored in a general "Employee" table. However, the company also has different types of employees: Full-time Employees (with a salary and benefits) and Contractors (hourly rate, contract duration). Each employee type has distinct data stored in separate specialized tables ("FullTimeEmployee" and "Contractor"), which reference the main "Employee" table.
+> This structure mirrors the Table Inheritance pattern—shared fields in a common table and unique fields split into subclass-specific tables.
-This setup ensures each subclass entry corresponds to a base class entry, maintaining the inheritance relationship while keeping subclass-specific data in their own tables.
+In plain words
-### In Plain Words
-In table inheritance, each class in the hierarchy is represented by a separate table, which
-allows for a clear distinction between shared attributes (stored in the base class table) and
-specific attributes (stored in subclass tables).
+> The Table Inheritance pattern maps each class within an inheritance hierarchy to its own database table, storing common attributes in a base table and subclass-specific attributes in separate joined tables.
-### Martin Fowler Says
+Martin Fowler says
-Relational databases don't support inheritance, which creates a mismatch when mapping objects.
-To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining
-relationships through foreign keys, making it easier to link the classes together in the database.
+> Relational databases don't support inheritance, which creates a mismatch when mapping objects. To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining relationships through foreign keys, making it easier to link the classes together in the database.
-For more detailed information, refer to Martin Fowler's article on [Class Table Inheritance](https://martinfowler.com/eaaCatalog/classTableInheritance.html).
+Mind map
+
## Programmatic Example of Table Inheritance Pattern in Java
-
-The `Vehicle` class will be the superclass, and we will have `Car` and `Truck` as subclasses that extend
-`Vehicle`. The `Vehicle` class will store common attributes, while `Car` and `Truck` will store
-attributes specific to those subclasses.
+The `Vehicle` class will be the superclass, and we will have subclasses `Car` and `Truck` that extend `Vehicle`. The superclass `Vehicle` stores common attributes, while subclasses store their own specific attributes.
### Key Aspects of the Pattern:
-1. **Superclass (`Vehicle`)**:
- The `Vehicle` class stores attributes shared by all vehicle types, such as:
- - `make`: The manufacturer of the vehicle.
- - `model`: The model of the vehicle.
- - `year`: The year the vehicle was manufactured.
- - `id`: A unique identifier for the vehicle.
+**Superclass (`Vehicle`):**
+
+The superclass stores shared attributes:
+
+* `make`: Manufacturer of the vehicle.
+* `model`: Model of the vehicle.
+* `year`: Year of manufacture.
+* `id`: Unique identifier for the vehicle.
- These attributes are stored in the **`Vehicle` table** in the database.
+These common attributes will reside in a dedicated database table (`Vehicle` table).
-2. **Subclass (`Car` and `Truck`)**:
- Each subclass (`Car` and `Truck`) stores attributes specific to that vehicle type:
- - `Car`: Has an additional attribute `numberOfDoors` representing the number of doors the car has.
- - `Truck`: Has an additional attribute `payloadCapacity` representing the payload capacity of the truck.
+**Subclasses (`Car` and `Truck`):**
- These subclass-specific attributes are stored in the **`Car` and `Truck` tables**.
+Each subclass adds attributes specific to its type:
-3. **Foreign Key Relationship**:
- Each subclass (`Car` and `Truck`) contains the `id` field which acts as a **foreign key** that
-references the primary key (`id`) of the superclass (`Vehicle`). This foreign key ensures the
-relationship between the common attributes in the `Vehicle` table and the specific attributes in the
-subclass tables (`Car` and `Truck`).
+* `Car`: `numberOfDoors`, indicating how many doors the car has.
+* `Truck`: `payloadCapacity`, representing how much payload the truck can carry.
+Each subclass stores these specific attributes in their respective tables (`Car` and `Truck` tables).
+
+**Foreign Key Relationship:**
+
+Each subclass table references the superclass table via a foreign key. The subclass's `id` links to the primary key of the superclass, thus connecting common and subclass-specific data.
+
+### Java Implementation Using JPA Annotations:
```java
-/**
- * Superclass
- * Represents a generic vehicle with basic attributes like make, model, year, and ID.
- */
+@Setter
+@Getter
public class Vehicle {
- private String make;
- private String model;
- private int year;
- private int id;
- // Constructor, getters, and setters...
+ private String make;
+ private String model;
+ private int year;
+ private int id;
+
+ public Vehicle(int year, String make, String model, int id) {
+ this.make = make;
+ this.model = model;
+ this.year = year;
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "Vehicle{"
+ + "id="
+ + id
+ + ", make='"
+ + make
+ + '\''
+ + ", model='"
+ + model
+ + '\''
+ + ", year="
+ + year
+ + '}';
+ }
}
-/**
- * Represents a car, which is a subclass of Vehicle.
- */
+@Getter
public class Car extends Vehicle {
- private int numberOfDoors;
-
- // Constructor, getters, and setters...
+ private int numDoors;
+
+ public Car(int year, String make, String model, int numDoors, int id) {
+ super(year, make, model, id);
+ if (numDoors <= 0) {
+ throw new IllegalArgumentException("Number of doors must be positive.");
+ }
+ this.numDoors = numDoors;
+ }
+
+ public void setNumDoors(int doors) {
+ if (doors <= 0) {
+ throw new IllegalArgumentException("Number of doors must be positive.");
+ }
+ this.numDoors = doors;
+ }
+
+ @Override
+ public String toString() {
+ return "Car{"
+ + "id="
+ + getId()
+ + ", make='"
+ + getMake()
+ + '\''
+ + ", model='"
+ + getModel()
+ + '\''
+ + ", year="
+ + getYear()
+ + ", numberOfDoors="
+ + getNumDoors()
+ + '}';
+ }
}
-/**
- * Represents a truck, which is a subclass of Vehicle.
- */
+@Getter
public class Truck extends Vehicle {
- private int payloadCapacity;
-
- // Constructor, getters, and setters...
+ private double loadCapacity;
+
+ public Truck(int year, String make, String model, double loadCapacity, int id) {
+ super(year, make, model, id);
+ if (loadCapacity <= 0) {
+ throw new IllegalArgumentException("Load capacity must be positive.");
+ }
+ this.loadCapacity = loadCapacity;
+ }
+
+ public void setLoadCapacity(double capacity) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Load capacity must be positive.");
+ }
+ this.loadCapacity = capacity;
+ }
+
+ @Override
+ public String toString() {
+ return "Truck{"
+ + "id="
+ + getId()
+ + ", make='"
+ + getMake()
+ + '\''
+ + ", model='"
+ + getModel()
+ + '\''
+ + ", year="
+ + getYear()
+ + ", payloadCapacity="
+ + getLoadCapacity()
+ + '}';
+ }
}
```
+### Explanation of the JPA annotations used above:
+* `@Entity`: Indicates that the class is a JPA entity mapped to a database table.
+* `@Inheritance(strategy = InheritanceType.JOINED)`: Configures joined table inheritance, meaning each class (superclass and subclasses) maps to its own table.
+* `@Table(name = "XYZ")`: Explicitly specifies the database table name for clarity.
+* `@Id`: Marks the primary key of the entity.
+* `@GeneratedValue(strategy = GenerationType.IDENTITY)`: Specifies auto-generation of primary key values by the database.
-## Table Inheritance Pattern Class Diagram
+### Database Structure Result:
+Applying this code will result in three database tables structured as follows:
-
+**Vehicle table**
+* id
+* make
+* model
+* year
+**Car table**
+* id (FK to Vehicle)
+* numberOfDoors
+**Truck table**
+* id (FK to Vehicle)
+* payloadCapacity
-
-
-
-## Table Inheritance Pattern Database Schema
-
-### Vehicle Table
-| Column | Description |
-|--------|-------------------------------------|
-| id | Primary key |
-| make | The make of the vehicle |
-| model | The model of the vehicle |
-| year | The manufacturing year of the vehicle |
-
-### Car Table
-| Column | Description |
-|------------------|-------------------------------------|
-| id | Foreign key referencing `Vehicle(id)` |
-| numberOfDoors | Number of doors in the car |
-
-### Truck Table
-| Column | Description |
-|-------------------|-------------------------------------|
-| id | Foreign key referencing `Vehicle(id)` |
-| payloadCapacity | Payload capacity of the truck |
-
----
+This approach clearly represents the Table Inheritance (Joined Table) pattern, with common attributes centrally managed in the superclass table and subclass-specific attributes cleanly separated in their own tables.
## When to Use the Table Inheritance Pattern in Java
-- When your application requires a clear mapping of an object-oriented class hierarchy to relational tables.
-- When subclasses have unique attributes that do not fit into a single base table.
-- When scalability and normalization of data are important considerations.
-- When you need to separate concerns and organize data in a way that each subclass has its own
-table but maintains relationships with the superclass.
+* When persisting an inheritance hierarchy of Java classes in a relational database.
+* Suitable when classes share common attributes but also have distinct fields.
+* Beneficial when polymorphic queries across subclasses are frequent.
## Table Inheritance Pattern Java Tutorials
- [Software Patterns Lexicon: Class Table Inheritance](https://softwarepatternslexicon.com/patterns-sql/4/4/2/)
- [Martin Fowler: Class Table Inheritance](http://thierryroussel.free.fr/java/books/martinfowler/www.martinfowler.com/isa/classTableInheritance.html)
----
-
## Real-World Applications of Table Inheritance Pattern in Java
-- **Vehicle Management System**: Used to store different types of vehicles like Car and Truck in separate tables but maintain a relationship through a common superclass `Vehicle`.
-- **E-Commerce Platforms**: Where different product types, such as Clothing, Electronics, and Furniture, are stored in separate tables with shared attributes in a superclass `Product`.
+* Hibernate ORM (`@Inheritance(strategy = InheritanceType.JOINED)` in Java)
+* EclipseLink (Joined Inheritance strategy in JPA)
+* Spring Data JPA applications modeling complex domain hierarchies.
## Benefits and Trade-offs of Table Inheritance Pattern
-### Benefits
+Benefits:
-- **Clear Structure**: Each class has its own table, making the data model easier to maintain and understand.
-- **Scalability**: Each subclass can be extended independently without affecting the other tables, making the system more scalable.
-- **Data Normalization**: Helps avoid data redundancy and keeps the schema normalized.
+ * Normalized database schema reduces redundancy.
+ * Clearly models class hierarchies at the database level.
+ * Easier to implement polymorphic queries due to clear class distinctions.
-### Trade-offs
+Trade-offs:
-- **Multiple Joins**: Retrieving data that spans multiple subclasses may require joining multiple tables, which could lead to performance issues.
-- **Increased Complexity**: Managing relationships between tables and maintaining integrity can become more complex.
-- **Potential for Sparse Tables**: Subclasses with fewer attributes may end up with tables that have many null fields.
+ * Increased complexity in database queries involving multiple joins.
+ * Reduced performance for deep inheritance hierarchies due to costly joins.
+ * Maintenance overhead increases with the complexity of inheritance structures.
## Related Java Design Patterns
-- **Single Table Inheritance** – A strategy where a single table is used to store all classes in an
-inheritance hierarchy. It stores all attributes of the class and its subclasses in one table.
-- **Singleton Pattern** – Used when a class needs to have only one instance.
-
+* [Single Table Inheritance](https://java-design-patterns.com/patterns/single-table-inheritance/): Alternative strategy mapping an entire class hierarchy into a single database table, useful when fewer joins are preferred at the cost of nullable columns.
+* Concrete Table Inheritance – Each subclass has its own standalone table; related in providing an alternate approach to storing inheritance hierarchies.
## References and Credits
-- **Martin Fowler** - [*Patterns of Enterprise Application Architecture*](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420)
-- **Java Persistence with Hibernate** - [Link to book](https://www.amazon.com/Java-Persistence-Hibernate-Christian-Bauer/dp/193239469X)
-- **Object-Relational Mapping on Wikipedia** - [Link to article](https://en.wikipedia.org/wiki/Object-relational_mapping)
+* [Java Persistence with Hibernate](https://amzn.to/44tP1ox)
+* [Object-Relational Mapping (Wikipedia)](https://en.wikipedia.org/wiki/Object-relational_mapping)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
+* [Pro JPA 2: Mastering the Java Persistence API](https://amzn.to/4b7UoMC)
diff --git a/table-inheritance/etc/table-inheritance-mind-map.png b/table-inheritance/etc/table-inheritance-mind-map.png
new file mode 100644
index 000000000000..c403077b667b
Binary files /dev/null and b/table-inheritance/etc/table-inheritance-mind-map.png differ
diff --git a/table-inheritance/src/test/java/AppTest.java b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java
similarity index 53%
rename from table-inheritance/src/test/java/AppTest.java
rename to table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java
index 1d8e271423f7..8cbae5b2bfb9 100644
--- a/table-inheritance/src/test/java/AppTest.java
+++ b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java
@@ -22,9 +22,33 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
+package com.iluwatar.table.inheritance; /*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.iluwatar.table.inheritance.App;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.logging.ConsoleHandler;
diff --git a/table-inheritance/src/test/java/VehicleDatabaseTest.java b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java
similarity index 75%
rename from table-inheritance/src/test/java/VehicleDatabaseTest.java
rename to table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java
index 71461f990a76..9c290fd5c325 100644
--- a/table-inheritance/src/test/java/VehicleDatabaseTest.java
+++ b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java
@@ -22,14 +22,35 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
+package com.iluwatar.table.inheritance; /*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import com.iluwatar.table.inheritance.Car;
-import com.iluwatar.table.inheritance.Truck;
-import com.iluwatar.table.inheritance.Vehicle;
-import com.iluwatar.table.inheritance.VehicleDatabase;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -43,7 +64,7 @@ class VehicleDatabaseTest {
/** Sets up a new instance of {@link VehicleDatabase} before each test. */
@BeforeEach
- public void setUp() {
+ void setUp() {
vehicleDatabase = new VehicleDatabase();
}