diff --git a/.all-contributorsrc b/.all-contributorsrc index b2fef6640890..ed336828d406 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3493,6 +3493,88 @@ "contributions": [ "code" ] + }, + { + "login": "ssrijan-007-sys", + "name": "ssrijan-007-sys", + "avatar_url": "https://avatars.githubusercontent.com/u/137605821?v=4", + "profile": "https://github.com/ssrijan-007-sys", + "contributions": [ + "code" + ] + }, + { + "login": "e5LA", + "name": "e5LA", + "avatar_url": "https://avatars.githubusercontent.com/u/208197507?v=4", + "profile": "https://github.com/e5LA", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "maziyar-gerami", + "name": "Maziyar Gerami", + "avatar_url": "https://avatars.githubusercontent.com/u/122622721?v=4", + "profile": "http://maziyar-gerami.github.io/portfolio/", + "contributions": [ + "translation" + ] + }, + { + "login": "yybmion", + "name": "yoobin_mion", + "avatar_url": "https://avatars.githubusercontent.com/u/113106136?v=4", + "profile": "https://github.com/yybmion", + "contributions": [ + "code" + ] + }, + { + "login": "ronodhirSoumik", + "name": "Soumik Sarker", + "avatar_url": "https://avatars.githubusercontent.com/u/46843689?v=4", + "profile": "https://ronodhirsoumik.github.io", + "contributions": [ + "doc" + ] + }, + { + "login": "naman-sriv", + "name": "Naman Srivastava", + "avatar_url": "https://avatars.githubusercontent.com/u/82610773?v=4", + "profile": "https://github.com/naman-sriv", + "contributions": [ + "code" + ] + }, + { + "login": "letdtcode", + "name": "Thanh Nguyen Duc", + "avatar_url": "https://avatars.githubusercontent.com/u/92111552?v=4", + "profile": "https://github.com/letdtcode", + "contributions": [ + "code" + ] + }, + { + "login": "skamble2", + "name": "Soham Kamble", + "avatar_url": "https://avatars.githubusercontent.com/u/121136639?v=4", + "profile": "https://github.com/skamble2", + "contributions": [ + "code" + ] + }, + { + "login": "Olexandr88", + "name": "Olexandr88", + "avatar_url": "https://avatars.githubusercontent.com/u/93856062?v=4", + "profile": "https://github.com/Olexandr88", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 6, diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index cac1250b70e8..5f75fff3593e 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -1,35 +1,28 @@ name: Presubmit.ai permissions: - contents: read - pull-requests: write - issues: write + contents: read + pull-requests: write + issues: write on: - pull_request_target: # Handle forked repository PRs in the base repository context - types: [opened, synchronize] - pull_request_review_comment: # Handle review comments - types: [created] + pull_request_target: + types: [opened, synchronize] + pull_request_review_comment: + types: [created] jobs: - review: - runs-on: ubuntu-latest - steps: - - name: Check required secrets - run: | - if [ -z "${{ secrets.LLM_API_KEY }}" ]; then - echo "Error: LLM_API_KEY secret is not configured" - exit 1 - fi - - - name: Check out PR code - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Run AI Reviewer - uses: presubmit/ai-reviewer@latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - LLM_API_KEY: ${{ secrets.LLM_API_KEY }} - LLM_MODEL: "gemini-1.5-flash" + review: + runs-on: ubuntu-latest + steps: + - name: Check required secrets + run: | + if [ -z "${{ secrets.LLM_API_KEY }}" ]; then + echo "Error: LLM_API_KEY secret is not configured" + exit 1 + fi + - uses: presubmit/ai-reviewer@ + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_MODEL: "gpt-5-nano" \ No newline at end of file diff --git a/README.md b/README.md index 881563902e26..bf0e4ec9f073 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Design Patterns Implemented in Java -![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) +[![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg)](https://github.com/iluwatar/java-design-patterns/actions/workflows/maven-ci.yml) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-383-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-392-orange.svg?style=flat-square)](#contributors-)
@@ -45,7 +45,7 @@ If you are willing to contribute to the project you will find the relevant infor # The Book -The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/kcaF9 +The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/bNQFX The project contributors can get the book for free. Contact the maintainer via [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns) or email (iluwatar (at) gmail (dot) com ). Send a message that contains your email address, Github username, and a link to an accepted pull request. @@ -570,6 +570,19 @@ This project is licensed under the terms of the MIT license. Sanura Hettiarachchi
Sanura Hettiarachchi

💻 Kim Gi Uk
Kim Gi Uk

💻 Suchismita Deb
Suchismita Deb

💻 + ssrijan-007-sys
ssrijan-007-sys

💻 + + + e5LA
e5LA

💻 📖 + Maziyar Gerami
Maziyar Gerami

🌍 + yoobin_mion
yoobin_mion

💻 + Soumik Sarker
Soumik Sarker

📖 + Naman Srivastava
Naman Srivastava

💻 + Thanh Nguyen Duc
Thanh Nguyen Duc

💻 + + + Soham Kamble
Soham Kamble

💻 + Olexandr88
Olexandr88

📖 diff --git a/actor-model/README.md b/actor-model/README.md new file mode 100644 index 000000000000..be8065ffefef --- /dev/null +++ b/actor-model/README.md @@ -0,0 +1,201 @@ +--- +title: "Actor Model Pattern in Java: Building Concurrent Systems with Elegance" +shortTitle: Actor Model +description: "Explore the Actor Model pattern in Java with real-world examples and practical implementation. Learn how to build scalable, message-driven systems using actors, messages, and asynchronous communication." +category: Concurrency +language: en +tag: + - Concurrency + - Messaging + - Isolation + - Asynchronous + - Distributed Systems + - Actor Model +--- + +## Also Known As + +- Message-passing concurrency +- Actor-based concurrency + +--- + +## Intent of Actor Model Pattern + +The Actor Model pattern enables the construction of highly concurrent, distributed, and fault-tolerant systems by using isolated components (actors) that interact exclusively through asynchronous message passing. + +--- + +## Detailed Explanation of Actor Model Pattern with Real-World Examples + +### 📦 Real-world Example + +Imagine a customer service system: +- Each **customer support agent** is an **actor**. +- Customers **send questions (messages)** to agents. +- Each agent handles one request at a time and can **respond asynchronously** without interfering with other agents. + +--- + +### 🧠 In Plain Words + +> "Actors are like independent workers that never share memory and only communicate through messages." + +--- + +### 📖 Wikipedia Says + +> [Actor model](https://en.wikipedia.org/wiki/Actor_model) is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation. + +--- + +### 🧹 Architecture Diagram + +![UML Class Diagram](./etc/Actor_Model_UML_Class_Diagram.png) + +--- + +## Programmatic Example of Actor Model Pattern in Java + +### Actor.java + +```java +public abstract class Actor implements Runnable { + + @Setter @Getter private String actorId; + private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); + private volatile boolean active = true; + + + public void send(Message message) { + mailbox.add(message); + } + + public void stop() { + active = false; + } + + @Override + public void run() { + + } + + protected abstract void onReceive(Message message); +} + +``` + +### Message.java + +```java + +@AllArgsConstructor +@Getter +@Setter +public class Message { + private final String content; + private final String senderId; +} +``` + +### ActorSystem.java + +```java +public class ActorSystem { + 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 + } +} +``` + +### App.java + +```java +public class App { + public static void main(String[] args) { + 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 + } +} +``` + +--- + +## When to Use the Actor Model Pattern in Java + +- When building **concurrent or distributed systems** +- When you want **no shared mutable state** +- When you need **asynchronous, message-driven communication** +- When components should be **isolated and loosely coupled** + +--- + +## Actor Model Pattern Java Tutorials + +- [Baeldung – Akka with Java](https://www.baeldung.com/java-akka) +- [Vaughn Vernon – Reactive Messaging Patterns](https://vaughnvernon.co/?p=1143) + +--- + +## Real-World Applications of Actor Model Pattern in Java + +- [Akka Framework](https://akka.io/) +- [Erlang and Elixir concurrency](https://www.erlang.org/) +- [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/) +- JVM-based game engines and simulators + +--- + +## Benefits and Trade-offs of Actor Model Pattern + +### ✅ Benefits +- High concurrency support +- Easy scaling across threads or machines +- Fault isolation and recovery +- Message ordering within actors + +### ⚠️ Trade-offs +- Harder to debug due to asynchronous behavior +- Slight performance overhead due to message queues +- More complex to design than simple method calls + +--- + +## Related Java Design Patterns + +- [Command Pattern](../command) +- [Mediator Pattern](../mediator) +- [Event-Driven Architecture](../event-driven-architecture) +- [Observer Pattern](../observer) + +--- + +## References and Credits + +- *Programming Erlang*, Joe Armstrong +- *Reactive Design Patterns*, Roland Kuhn +- *The Actor Model in 10 Minutes*, [InfoQ Article](https://www.infoq.com/articles/actor-model/) +- [Akka Documentation](https://doc.akka.io/docs/akka/current/index.html) + diff --git a/actor-model/etc/Actor_Model_UML_Class_Diagram.png b/actor-model/etc/Actor_Model_UML_Class_Diagram.png new file mode 100644 index 000000000000..a4c34d7a75fd Binary files /dev/null and b/actor-model/etc/Actor_Model_UML_Class_Diagram.png differ diff --git a/actor-model/etc/actor-model.urm.puml b/actor-model/etc/actor-model.urm.puml new file mode 100644 index 000000000000..020c1fc735a4 --- /dev/null +++ b/actor-model/etc/actor-model.urm.puml @@ -0,0 +1,35 @@ +@startuml actor-model + +title Actor Model - UML Class Diagram + +class ActorSystem { + +actorOf(actor: Actor): Actor + +shutdown(): void +} + +class Actor { + -mailbox: BlockingQueue + -active: boolean + +send(message: Message): void + +stop(): void + +run(): void + #onReceive(message: Message): void +} + +class ExampleActor { + +onReceive(message: Message): void +} + +class Message { + -content: String + -sender: Actor + +getContent(): String + +getSender(): Actor +} + +ActorSystem --> Actor : creates +Actor <|-- ExampleActor : extends +Actor --> Message : processes +ExampleActor --> Message : uses + +@enduml diff --git a/actor-model/pom.xml b/actor-model/pom.xml new file mode 100644 index 000000000000..76c288829b8d --- /dev/null +++ b/actor-model/pom.xml @@ -0,0 +1,114 @@ + + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + actor-model + Actor Model + + + + + + org.junit + junit-bom + 5.11.0 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-launcher + test + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + jar-with-dependencies + + + + com.iluwatar.actormodel.App + + + + + + make-assembly + package + + single + + + + + + + + + diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java new file mode 100644 index 000000000000..6e2aaccd1937 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.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.actormodel; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import lombok.Getter; +import lombok.Setter; + +public abstract class Actor implements Runnable { + + @Setter @Getter private String actorId; + private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); + private volatile boolean active = + true; // always read from main memory and written back to main memory, + + // rather than being cached in a thread's local memory. To make it consistent to all Actors + + public void send(Message message) { + mailbox.add(message); // Add message to queue + } + + public void stop() { + active = false; // Stop the actor loop + } + + @Override + public void run() { + while (active) { + try { + Message message = mailbox.take(); // Wait for a message + onReceive(message); // Process it + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + // Child classes must define what to do with a message + protected abstract void onReceive(Message message); +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java new file mode 100644 index 000000000000..db7c21cb6088 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java @@ -0,0 +1,51 @@ +/* + * 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.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class ActorSystem { + private final ExecutorService executor = Executors.newCachedThreadPool(); + private final ConcurrentHashMap 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/backpressure/README.md b/backpressure/README.md new file mode 100644 index 000000000000..d0734ad1f934 --- /dev/null +++ b/backpressure/README.md @@ -0,0 +1,156 @@ +--- +title: "Backpressure Pattern in Java: Gracefully regulate producer-to-consumer data flow to prevent overload." +shortTitle: Backpressure +description: "Dive into the Backpressure design pattern in Java through practical examples, discovering how it prevents overload while ensuring stability and peak performance by aligning data flow with consumer capacity." +category: Concurrency +language: en +tag: + - Asynchronous + - Event-driven + - Reactive + - Resilience +--- + +## Also known as + +* Flow Control +* Rate Limiting Mechanism + +## Intent of the Backpressure Design Pattern + +Control the rate of data production so downstream consumers are not overwhelmed by excessive load. + +## Detailed Explanation of Backpressure Pattern with Real-World Examples + +Real-world example + +> Imagine a busy coffee shop where multiple baristas brew drinks (producers), and a single barista is responsible for carefully crafting latte art (consumer). If drinks are brewed faster than the latte-art barista can decorate them, they pile up, risking quality issues or discarded drinks. By introducing a pacing system—only sending new cups once the latte-art barista is ready—everyone stays synchronized, ensuring minimal waste and a consistently enjoyable customer experience. + +In plain words + +> The Backpressure design pattern is a flow control mechanism that prevents overwhelming a system by regulating data production based on the consumer’s processing capacity. + +Wikipedia says + +> Back pressure (or backpressure) is the term for a resistance to the desired flow of fluid through pipes. Obstructions or tight bends create backpressure via friction loss and pressure drop. In distributed systems in particular event-driven architecture, back pressure is a technique to regulate flow of data, ensuring that components do not become overwhelmed. + +Sequence diagram + +![Backpressure sequence diagram](./etc/backpressure-sequence-diagram.png) + +## Programmatic Example of Backpressure Pattern in Java + +This example demonstrates how backpressure can be implemented using Project Reactor. We begin by creating a simple publisher that emits a stream of integers, introducing a small delay to mimic a slower production rate: + +```java +public class Publisher { + public static Flux publish(int start, int count, int delay) { + return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log(); + } +} +``` + +Next, we define a custom subscriber by extending Reactor’s BaseSubscriber. It simulates slow processing by sleeping for 500ms per item. Initially, the subscriber requests ten items; for every five items processed, it requests five more: + +```java +public class Subscriber extends BaseSubscriber { + + private static final Logger logger = LoggerFactory.getLogger(Subscriber.class); + + @Override + protected void hookOnSubscribe(@NonNull Subscription subscription) { + logger.info("subscribe()"); + request(10); //request 10 items initially + } + + @Override + protected void hookOnNext(@NonNull Integer value) { + processItem(); + logger.info("process({})", value); + if (value % 5 == 0) { + // request for the next 5 items after processing first 5 + request(5); + } + } + + @Override + protected void hookOnComplete() { + //completed processing. + } + + private void processItem() { + try { + Thread.sleep(500); // simulate slow processing + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } +} +``` + +Finally, in the `main` method, we publish a range of integers and subscribe using the custom subscriber. A short sleep in the main thread allows the emission, backpressure requests, and processing to be fully observed: + +```java +public static void main(String[] args) throws InterruptedException { + Subscriber sub = new Subscriber(); + Publisher.publish(1, 8, 200).subscribe(sub); + Thread.sleep(5000); //wait for execution + +} +``` + +Below is an example of the program’s output. It shows the subscriber’s log entries, including when it requests more data and when each integer is processed: + +``` +23:09:55.746 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework +23:09:55.762 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onSubscribe(FluxConcatMapNoPrefetch.FluxConcatMapNoPrefetchSubscriber) +23:09:55.762 [main] INFO com.iluwatar.backpressure.Subscriber -- subscribe() +23:09:55.763 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(10) +23:09:55.969 [parallel-1] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(1) +23:09:56.475 [parallel-1] INFO com.iluwatar.backpressure.Subscriber -- process(1) +23:09:56.680 [parallel-2] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(2) +23:09:57.185 [parallel-2] INFO com.iluwatar.backpressure.Subscriber -- process(2) +23:09:57.389 [parallel-3] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(3) +23:09:57.894 [parallel-3] INFO com.iluwatar.backpressure.Subscriber -- process(3) +23:09:58.099 [parallel-4] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(4) +23:09:58.599 [parallel-4] INFO com.iluwatar.backpressure.Subscriber -- process(4) +23:09:58.805 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(5) +23:09:59.311 [parallel-5] INFO com.iluwatar.backpressure.Subscriber -- process(5) +23:09:59.311 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(5) +23:09:59.516 [parallel-6] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(6) +23:10:00.018 [parallel-6] INFO com.iluwatar.backpressure.Subscriber -- process(6) +23:10:00.223 [parallel-7] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(7) +23:10:00.729 [parallel-7] INFO com.iluwatar.backpressure.Subscriber -- process(7) +23:10:00.930 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(8) +23:10:01.436 [parallel-8] INFO com.iluwatar.backpressure.Subscriber -- process(8) +23:10:01.437 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onComplete() +``` + +## When to Use the Backpressure Pattern In Java + +* Use in Java systems where data is produced at high velocity and consumers risk overload. +* Applicable in reactive or event-driven architectures to maintain stability under varying load conditions. + +## Benefits and Trade-offs of Backpressure Pattern + +Benefits: + +* Protects consumers from saturation and resource exhaustion. + +Trade-offs: + +* Introduces possible delays if production must slow down to match consumer capacity. +* Requires careful orchestration in complex systems with multiple concurrent data sources. + +## Related Java Design Patterns + +* [Observer Pattern](https://java-design-patterns.com/patterns/observer/): Both patterns involve a producer notifying consumers. Observer is synchronous and tightly coupled (observers know the subject). +* [Publish-Subscribe Pattern](https://java-design-patterns.com/patterns/publish-subscribe/): Both patterns deal with asynchronous data flow and can work together to manage message distribution and consumption effectively. + +## References and Credits + +* [Backpressure Explained (RedHat Developers Blog)](https://developers.redhat.com/articles/backpressure-explained) +* [Hands-On Reactive Programming in Spring 5](https://amzn.to/3YuYfyO) +* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/42negbf) +* [Reactive Streams in Java](https://amzn.to/3RJjUzA) +* [Reactive Streams Specification](https://www.reactive-streams.org/) diff --git a/backpressure/etc/backpressure-sequence-diagram.png b/backpressure/etc/backpressure-sequence-diagram.png new file mode 100644 index 000000000000..b74a5d2a5362 Binary files /dev/null and b/backpressure/etc/backpressure-sequence-diagram.png differ diff --git a/backpressure/etc/backpressure.png b/backpressure/etc/backpressure.png new file mode 100644 index 000000000000..0de9ff54b673 Binary files /dev/null and b/backpressure/etc/backpressure.png differ diff --git a/backpressure/pom.xml b/backpressure/pom.xml new file mode 100644 index 000000000000..8f6a54178799 --- /dev/null +++ b/backpressure/pom.xml @@ -0,0 +1,81 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + backpressure + + + + io.projectreactor + reactor-core + 3.8.0-M1 + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + io.projectreactor + reactor-test + 3.8.0-RC1 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.backpressure.App + + + + + + + + + \ No newline at end of file diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/App.java b/backpressure/src/main/java/com/iluwatar/backpressure/App.java new file mode 100644 index 000000000000..c91add40df89 --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/App.java @@ -0,0 +1,74 @@ +/* + * 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.backpressure; + +import java.util.concurrent.CountDownLatch; + +/** + * The Backpressure pattern is a flow control mechanism. It allows a consumer to signal to a + * producer to slow down or stop sending data when it's overwhelmed. + *

  • Prevents memory overflow, CPU thrashing, and resource exhaustion. + *
  • Ensures fair usage of resources in distributed systems. + *
  • Avoids buffer bloat and latency spikes. Key concepts of this design paradigm involves + *
  • Publisher/Producer: Generates data. + *
  • Subscriber/Consumer: Receives and processes data. + * + *

    In this example we will create a {@link Publisher} and a {@link Subscriber}. Publisher + * will emit a stream of integer values with a predefined delay. Subscriber takes 500 ms to + * process one integer. Since the subscriber can't process the items fast enough we apply + * backpressure to the publisher so that it will request 10 items first, process 5 items and + * request for the next 5 again. After processing 5 items subscriber will keep requesting for + * another 5 until the stream ends. + */ +public class App { + + protected static CountDownLatch latch; + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws InterruptedException { + + /* + * This custom subscriber applies backpressure: + * - Has a processing delay of 500 milliseconds + * - Requests 10 items initially + * - Process 5 items and request for the next 5 items + */ + Subscriber sub = new Subscriber(); + latch = new CountDownLatch(1); + // slow publisher emit 15 numbers with a delay of 200 milliseconds + Publisher.publish(1, 17, 200).subscribe(sub); + latch.await(); + + sub = new Subscriber(); + latch = new CountDownLatch(1); + // fast publisher emit 15 numbers with a delay of 1 millisecond + Publisher.publish(1, 17, 1).subscribe(sub); + latch.await(); + } +} diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java new file mode 100644 index 000000000000..1d39a070ae57 --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.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.backpressure; + +import java.time.Duration; +import reactor.core.publisher.Flux; + +/** This class is the publisher that generates the data stream. */ +public class Publisher { + + private Publisher() {} + + /** + * On message method will trigger when the subscribed event is published. + * + * @param start starting integer + * @param count how many integers to emit + * @param delay delay between each item in milliseconds + * @return a flux stream of integers + */ + public static Flux publish(int start, int count, int delay) { + return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log(); + } +} diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java new file mode 100644 index 000000000000..40ff5aebc814 --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.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. + */ +package com.iluwatar.backpressure; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscription; +import reactor.core.publisher.BaseSubscriber; + +/** This class is the custom subscriber that subscribes to the data stream. */ +@Slf4j +public class Subscriber extends BaseSubscriber { + + @Override + protected void hookOnSubscribe(@NonNull Subscription subscription) { + request(10); // request 10 items initially + } + + @Override + protected void hookOnNext(@NonNull Integer value) { + processItem(); + LOGGER.info("process({})", value); + if (value % 5 == 0) { + // request for the next 5 items after processing first 5 + request(5); + } + } + + @Override + protected void hookOnComplete() { + App.latch.countDown(); + } + + private void processItem() { + try { + Thread.sleep(500); // simulate slow processing + } catch (InterruptedException e) { + LOGGER.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java new file mode 100644 index 000000000000..8fe2245a97b7 --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java @@ -0,0 +1,37 @@ +/* + * 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.backpressure; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +public class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java new file mode 100644 index 000000000000..e99926e00a1a --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java @@ -0,0 +1,60 @@ +/* + * 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.backpressure; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.LoggerFactory; + +public class LoggerExtension implements BeforeEachCallback, AfterEachCallback { + + private final ListAppender listAppender = new ListAppender<>(); + private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Override + public void afterEach(ExtensionContext extensionContext) { + listAppender.stop(); + listAppender.list.clear(); + logger.detachAppender(listAppender); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) { + logger.addAppender(listAppender); + listAppender.start(); + } + + public List getFormattedMessages() { + return listAppender.list.stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java new file mode 100644 index 000000000000..e3b8e5ba905a --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java @@ -0,0 +1,51 @@ +/* + * 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.backpressure; + +import static com.iluwatar.backpressure.Publisher.publish; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +public class PublisherTest { + + @Test + public void testPublish() { + + Flux flux = publish(1, 3, 200); + + StepVerifier.withVirtualTime(() -> flux) + .expectSubscription() + .expectNoEvent(Duration.ofMillis(200)) + .expectNext(1) + .expectNoEvent(Duration.ofMillis(200)) + .expectNext(2) + .expectNoEvent(Duration.ofMillis(200)) + .expectNext(3) + .verifyComplete(); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java new file mode 100644 index 000000000000..b57ee57a1843 --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.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.backpressure; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class SubscriberTest { + + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + @Test + public void testSubscribe() throws InterruptedException { + + Subscriber sub = new Subscriber(); + App.latch = new CountDownLatch(1); + Publisher.publish(1, 8, 100).subscribe(sub); + App.latch.await(); + + String result = String.join(",", loggerExtension.getFormattedMessages()); + assertTrue( + result.endsWith( + "onSubscribe(FluxConcatMapNoPrefetch." + + "FluxConcatMapNoPrefetchSubscriber),request(10),onNext(1),process(1),onNext(2)," + + "process(2),onNext(3),process(3),onNext(4),process(4),onNext(5),process(5),request(5)," + + "onNext(6),process(6),onNext(7),process(7),onNext(8),process(8),onComplete()")); + } +} diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java index 52ce7c593b6b..7652211231ba 100644 --- a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java @@ -78,6 +78,6 @@ public void wash() { /** Method is responsible for ending the washing by changing machine state. */ public synchronized void endOfWashing() { washingMachineState = WashingMachineState.ENABLED; - LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); + LOGGER.info("{}: Washing completed.", Thread.currentThread().threadId()); } } diff --git a/bloc/README.md b/bloc/README.md index da494dba3d4b..76a4a0917485 100644 --- a/bloc/README.md +++ b/bloc/README.md @@ -2,20 +2,23 @@ title: "Bloc Pattern in Java: State Management Simplified" shortTitle: Bloc description: "Learn how the Bloc pattern helps manage state changes in Java applications. This guide covers dynamic listener management, real-world examples, and clean code practices for state management." -category: Structural +category: Architectural language: en tag: - - State Management - - Event-driven - - Listener Management - - Object Composition - - Dynamic Behavior + - Abstraction + - Data binding + - Decoupling + - Event-driven + - Presentation + - Reactive + - Reusability + - State tracking --- ## Also known as -* Event-driven State Management -* State Listener Pattern +* Business Logic Component +* Business Logic Controller ## Intent of the Bloc Pattern @@ -23,128 +26,122 @@ The Bloc pattern manages the state of an object and allows for dynamically notif ## Detailed explanation of the Bloc pattern with real-World examples -### Real-world example +Real-world example > Consider a digital counter application where multiple parts of the UI need to be updated whenever the counter changes. For example, a label displaying the counter value and an activity log showing changes. Instead of directly modifying these UI components, the Bloc pattern manages the counter state and notifies all registered listeners about the state change. Listeners can dynamically subscribe or unsubscribe from receiving updates. -### In plain words +In plain words > The Bloc pattern manages a single state object and dynamically notifies registered listeners whenever the state changes. -### Wikipedia says +Wikipedia says > While not a formalized "Gang of Four" design pattern, Bloc is widely used in state-driven applications. It centralizes state management and propagates state changes to registered observers, following principles of separation of concerns. -### Sequence diagram +Sequence diagram ![Bloc sequence diagram](./etc/bloc-sequence-diagram.png) ---- - ## Programmatic Example of the Bloc Pattern in Java -### **Core Components of the Bloc Pattern** +This example demonstrates how to implement the Bloc pattern using Java and Swing. The pattern separates the state of the application from UI components, and provides a reactive, event-driven approach to managing updates. -#### **1. State Object** +Core components of the Bloc Pattern include a `State` object, a `Bloc` class responsible for managing and updating that state, and interfaces (`StateListener` and `ListenerManager`) for subscribing to changes. -The `State` class holds the representation of the state of the application that will be passed to listeners whenever there is a change to do but in this example it's simplified to be a single value. +The `State` class represents the application's data at any given time. ```java -package com.iluwatar.bloc; - -import lombok.Getter; - -@Getter -public class State { - private final int value; +public record State(int value) {} +``` - public State(int value) { - this.value = value; - } +The `ListenerManager` interface declares methods to add and remove listeners, as well as retrieve them. -} -``` -The `ListenerManager` interface manages the basic operations for the listeners and is implemented by bloc class ```java -import java.util.List; - public interface ListenerManager { - void addListener(StateListener listener); - void removeListener(StateListener listener); - List> getListeners(); + void addListener(StateListener listener); + void removeListener(StateListener listener); + List> getListeners(); } ``` -The `StateListener` interface has a method that the listener needs to react to changes in the state and is used by bloC to notify listeners whenever there is an update to state. + +The `StateListener` interface defines how a listener reacts to state changes. + ```java public interface StateListener { -void onStateChange(T state); + void onStateChange(T state); } ``` -The `Bloc` class holds the current state and manages logic of states and notifies the list of listeners when states changes. -The `Bloc` class contains methods for listeners and states like emitstate which updates the currentstate and notifies listeners addlistener which adds new listener to the listeners list and notifies it with the currentstate removelistener which removes listener from the listeners list and increment which increases the state value by 1 which is like an update to the current state and notifies the listeners in listeners list with the new state which holds a value incremented by 1 and decrement functions which does the opposite of increment function and notifies listeners in listeners list. -```java -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +The `Bloc` class maintains the current state and notifies listeners of updates. It provides `increment` and `decrement` methods to update state and automatically notify registered listeners. +```java public class Bloc implements ListenerManager { -private State currentState; -private final List> listeners = new ArrayList<>(); -public Bloc() { -this.currentState = new State(0); -} + private State currentState; + private final List> listeners = new ArrayList<>(); + + public Bloc() { + this.currentState = new State(0); + } + + @Override + public void addListener(StateListener listener) { + listeners.add(listener); + listener.onStateChange(currentState); + } + + @Override + public void removeListener(StateListener listener) { + listeners.remove(listener); + } + + @Override + public List> getListeners() { + return Collections.unmodifiableList(listeners); + } + + private void emitState(State newState) { + currentState = newState; + for (StateListener listener : listeners) { + listener.onStateChange(currentState); + } + } -@Override -public void addListener(StateListener listener) { -listeners.add(listener); -listener.onStateChange(currentState); -} + public void increment() { + emitState(new State(currentState.value() + 1)); + } -@Override -public void removeListener(StateListener listener) { -listeners.remove(listener); + public void decrement() { + emitState(new State(currentState.value() - 1)); + } } +``` -@Override -public List> getListeners() { -return Collections.unmodifiableList(listeners); -} +This class demonstrates how to integrate the Bloc pattern with a simple Swing GUI. It sets up a counter, buttons to change the state, and a toggle to enable or disable the listener dynamically. -private void emitState(State newState) { -currentState = newState; -for (StateListener listener : listeners) { -listener.onStateChange(currentState); -} +```java +public class Main { + public static void main(String[] args) { + BlocUi blocUi = new BlocUi(); + blocUi.createAndShowUi(); + } } -public void increment() { -emitState(new State(currentState.getValue() + 1)); -} +public class BlocUi { -public void decrement() { -emitState(new State(currentState.getValue() - 1)); -} -} -``` -The `main` class have a simple gui to try and test the bloc pattern components separately from the ui components. -the `main` class creates an instance of bloc then adds a listener to update the ui which resembles the counter and some buttons to change the states and toggle the listener dynamically -```java -import javax.swing.*; -import java.awt.*; + public void createAndShowUi() { + final Bloc bloc = new Bloc(); -public class Main { -public static void main(String[] args) { -Bloc bloc = new Bloc(); -JFrame frame = new JFrame("Bloc Example"); -frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); -frame.setSize(400, 300); -JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); -counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); -JButton incrementButton = new JButton("Increment"); -JButton decrementButton = new JButton("Decrement"); -JButton toggleListenerButton = new JButton("Disable Listener"); + JFrame frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 300); + + JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + JButton decrementButton = new JButton("Decrement"); + JButton toggleListenerButton = new JButton("Disable Listener"); + JButton incrementButton = new JButton("Increment"); frame.setLayout(new BorderLayout()); frame.add(counterLabel, BorderLayout.CENTER); @@ -152,28 +149,30 @@ JButton toggleListenerButton = new JButton("Disable Listener"); frame.add(decrementButton, BorderLayout.SOUTH); frame.add(toggleListenerButton, BorderLayout.EAST); - StateListener stateListener = state -> counterLabel.setText("Counter: " + state.getValue()); + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); bloc.addListener(stateListener); - toggleListenerButton.addActionListener(e -> { - if (bloc.getListeners().contains(stateListener)) { - bloc.removeListener(stateListener); - toggleListenerButton.setText("Enable Listener"); - } else { - bloc.addListener(stateListener); - toggleListenerButton.setText("Disable Listener"); - } - }); + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); incrementButton.addActionListener(e -> bloc.increment()); decrementButton.addActionListener(e -> bloc.decrement()); frame.setVisible(true); -} + } } ``` -## Program Output + +### Program Output - **On Increment** `Counter: 1` @@ -185,48 +184,47 @@ JButton toggleListenerButton = new JButton("Disable Listener"); - Listener disabled: Counter stops updating. - Listener enabled: Counter updates again. ---- - ## When to Use the Bloc Pattern Use the Bloc pattern when: -- You need a centralized system to manage state updates. -- You want to dynamically add/remove listeners without tight coupling. -- You are building an event-driven or state-driven system, such as UI frameworks. ---- +* When you want a clean separation of business logic and UI in Java applications +* When you need a reactive approach to updating UI based on state changes +* When you want to avoid coupling controllers or presenters directly to data manipulation +* When multiple UI elements need access to the same business logic ## Real-World Applications of Bloc Pattern -- **UI State Management**: Reacting to button clicks, updating labels, and toggling views. -- **Event-driven Systems**: Handling multiple subscribers efficiently for state updates. ---- +* Java-based desktop applications that require real-time UI updates +* Backend-driven Java frameworks that separate service layers from presentation +* Cross-platform applications where the logic must remain consistent regardless of the UI technology ## Benefits and Trade-offs of Bloc Pattern -### Benefits: -- Clean separation of state management and UI logic. -- Flexibility to dynamically add/remove listeners. -- Centralized state propagation. +Benefits: -### Trade-offs: -- Adds some complexity with the listener management mechanism. -- May introduce performance concerns with excessive listeners. -- the bloc class handles too many methods which violates the single responsbility principle ---- +* Simplifies UI components by removing direct business logic +* Improves testability by isolating state and behavior +* Encourages code reuse by centralizing data flows +* Enhances maintainability through clear separation of concerns + +Trade-offs: + +* May introduce additional boilerplate code for managing streams or observers +* Requires careful design to avoid a monolithic “god” component +* Demands consistent reactive programming practices to be effective ## Related Patterns -- **Observer**: Bloc is a specialized implementation of the Observer pattern. -- **Mediator**: Bloc centralizes communication and state propagation. -- **cubit**: bloC is more general implementation than cubit ---- +- [Observer](https://java-design-patterns.com/patterns/observer/): Bloc is a specialized implementation of the Observer pattern. +- [Mediator](https://java-design-patterns.com/patterns/mediator/): Orchestrates interactions among multiple objects through a central component +- [MVC](https://java-design-patterns.com/patterns/model-view-controller/): Shares the idea of separating concerns between layers ## References and Credits -- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) -- [Java Swing Documentation](https://docs.oracle.com/javase/tutorial/uiswing/) -- [Event-Driven Programming in Java](https://www.oracle.com/java/) -- [bloC archetecture](https://bloclibrary.dev/architecture/) -- [flutter bloC package](https://pub.dev/documentation/flutter_bloc/latest/) - +* [Bloc architecture(bloclibrary.dev)](https://bloclibrary.dev/architecture/) +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Event-Driven Programming in Java (Oracle)](https://www.oracle.com/java/) +* [Java Swing Documentation (Oracle)](https://docs.oracle.com/javase/tutorial/uiswing/) diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java index 1327e2cb2197..f1fc73947896 100644 --- a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java +++ b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java @@ -32,7 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class BlocUiTest { +class BlocUiTest { private JFrame frame; private JLabel counterLabel; @@ -43,7 +43,7 @@ public class BlocUiTest { private StateListener stateListener; @BeforeEach - public void setUp() { + void setUp() { bloc = new Bloc(); // Re-initialize the Bloc for each test frame = new JFrame("BloC example"); @@ -83,25 +83,25 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { frame.dispose(); bloc = new Bloc(); // Reset Bloc state after each test to avoid state carryover } @Test - public void testIncrementButton() { + void testIncrementButton() { simulateButtonClick(incrementButton); assertEquals("Counter: 1", counterLabel.getText()); } @Test - public void testDecrementButton() { + void testDecrementButton() { simulateButtonClick(decrementButton); assertEquals("Counter: -1", counterLabel.getText()); } @Test - public void testToggleListenerButton() { + void testToggleListenerButton() { // Disable listener simulateButtonClick(toggleListenerButton); simulateButtonClick(incrementButton); diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index a87137e51fe0..17bce29a393e 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -48,6 +48,14 @@ public String toString() { var sb = new StringBuilder(); sb.append("This is a ").append(profession).append(" named ").append(name); + appendHairDescription(sb); + appendArmorDescription(sb); + appendWeaponDescription(sb); + sb.append('.'); + return sb.toString(); + } + + private void appendHairDescription(StringBuilder sb) { if (hairColor != null || hairType != null) { sb.append(" with "); if (hairColor != null) { @@ -58,14 +66,18 @@ public String toString() { } sb.append(hairType != HairType.BALD ? "hair" : "head"); } + } + + private void appendArmorDescription(StringBuilder sb) { if (armor != null) { sb.append(" wearing ").append(armor); } + } + + private void appendWeaponDescription(StringBuilder sb) { if (weapon != null) { sb.append(" and wielding a ").append(weapon); } - sb.append('.'); - return sb.toString(); } /** The builder class. */ diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java index 1d9a5539f51b..5a52ab161f33 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java @@ -32,76 +32,83 @@ /** Test for {@link VirtualMachine} */ class VirtualMachineTest { + private static final int BYTECODE_LENGTH_5 = 5; + private static final int BYTECODE_LENGTH_8 = 8; + private static final int WIZARD_INDEX_0 = 0; + private static final int HEALTH_AMOUNT = 50; + private static final int AGILITY_AMOUNT = 50; + private static final int WISDOM_AMOUNT = 50; + private static final int LITERAL_VALUE = 10; @Test void testLiteral() { var bytecode = new int[2]; bytecode[0] = LITERAL.getIntValue(); - bytecode[1] = 10; + bytecode[1] = LITERAL_VALUE; var vm = new VirtualMachine(); vm.execute(bytecode); assertEquals(1, vm.getStack().size()); - assertEquals(Integer.valueOf(10), vm.getStack().pop()); + assertEquals(Integer.valueOf(LITERAL_VALUE), vm.getStack().pop()); } @Test void testSetHealth() { - var wizardNumber = 0; - var bytecode = new int[5]; + var wizardNumber = WIZARD_INDEX_0; + var bytecode = new int[BYTECODE_LENGTH_5]; bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = HEALTH_AMOUNT; bytecode[4] = SET_HEALTH.getIntValue(); var vm = new VirtualMachine(); vm.execute(bytecode); - assertEquals(50, vm.getWizards()[wizardNumber].getHealth()); + assertEquals(HEALTH_AMOUNT, vm.getWizards()[wizardNumber].getHealth()); } @Test void testSetAgility() { - var wizardNumber = 0; - var bytecode = new int[5]; + var wizardNumber = WIZARD_INDEX_0; + var bytecode = new int[BYTECODE_LENGTH_5]; bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // agility amount + bytecode[3] = AGILITY_AMOUNT; bytecode[4] = SET_AGILITY.getIntValue(); var vm = new VirtualMachine(); vm.execute(bytecode); - assertEquals(50, vm.getWizards()[wizardNumber].getAgility()); + assertEquals(AGILITY_AMOUNT, vm.getWizards()[wizardNumber].getAgility()); } @Test void testSetWisdom() { - var wizardNumber = 0; - var bytecode = new int[5]; + var wizardNumber = WIZARD_INDEX_0; + var bytecode = new int[BYTECODE_LENGTH_5]; bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // wisdom amount + bytecode[3] = WISDOM_AMOUNT; bytecode[4] = SET_WISDOM.getIntValue(); var vm = new VirtualMachine(); vm.execute(bytecode); - assertEquals(50, vm.getWizards()[wizardNumber].getWisdom()); + assertEquals(WISDOM_AMOUNT, vm.getWizards()[wizardNumber].getWisdom()); } @Test void testGetHealth() { - var wizardNumber = 0; - var bytecode = new int[8]; + var wizardNumber = WIZARD_INDEX_0; + var bytecode = new int[BYTECODE_LENGTH_8]; bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = HEALTH_AMOUNT; bytecode[4] = SET_HEALTH.getIntValue(); bytecode[5] = LITERAL.getIntValue(); bytecode[6] = wizardNumber; @@ -110,7 +117,7 @@ void testGetHealth() { var vm = new VirtualMachine(); vm.execute(bytecode); - assertEquals(Integer.valueOf(50), vm.getStack().pop()); + assertEquals(Integer.valueOf(HEALTH_AMOUNT), vm.getStack().pop()); } @Test diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index 9c9107de6f88..0f8912aa4eea 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -151,7 +151,7 @@ public void set(final String userId, final UserAccount userAccount) { setHead(old); } else { var newNode = new Node(userId, userAccount); - if (cache.size() >= capacity) { + if (cache.size() >= capacity && end != null) { LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId); cache.remove(end.userId); // remove LRU data from cache. remove(end); diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java index e47eef55cd8c..e9ffe682cda5 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java +++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java @@ -44,11 +44,30 @@ @Slf4j public class MongoDb implements DbManager { private static final String DATABASE_NAME = "admin"; - private static final String MONGO_USER = "root"; - private static final String MONGO_PASSWORD = "rootpassword"; + private static final String MONGO_USER = getEnvOrDefault("MONGO_USER", "root"); + private static final String MONGO_PASSWORD = getRequiredEnv("MONGO_PASSWORD"); private MongoClient client; private MongoDatabase db; + /** Helper method to get environment variable with fallback for non-secret values. */ + private static String getEnvOrDefault(String key, String defaultValue) { + String value = System.getenv(key); + if (value == null || value.isEmpty()) { + LOGGER.warn("Environment variable {} not set. Using default value {}", key, defaultValue); + return defaultValue; + } + return value; + } + + /** Helper method to get required environment variables for secrets. */ + private static String getRequiredEnv(String key) { + String value = System.getenv(key); + if (value == null || value.isEmpty()) { + throw new IllegalStateException( + String.format("Required environment variable %s is not set or empty", key)); + } + return value; + } void setDb(MongoDatabase db) { this.db = db; } diff --git a/clean-architecture/README.md b/clean-architecture/README.md index 0e2e7f5da99e..599ae19ce13e 100644 --- a/clean-architecture/README.md +++ b/clean-architecture/README.md @@ -2,55 +2,48 @@ title: "Clean Architecture - A Software Maintainable Architectural style." shortTitle: Clean Architecture description: "Learn the Clean Architecture Style in Java with real-world examples, code snippets, and class diagrams. Enhance your coding skills with our detailed explanations." -category: Behavioral +category: Architectural language: en tag: - - Decoupling - - Architectural Style + - Architecture + - Decoupling + - Domain + - Inversion of control + - Layered architecture + - Modularity + - Testing --- -## Also known as - -* Hexagonal Architecture. - ## Intent of Clean Architecture. -The clean architecture is a software design architectural style which ensures the software application is easy to understand, maintainable and can be extend easily as per business requirement. +To organize the system so that the core business logic remains independent from external concerns and frameworks. ## Detailed Explanation of Clean Architecture Pattern with Real-World Examples -Real World. +Real-world example -A real world example of clean architecture is like teh shopping mall example. There some employee is assigned to work on the filling of the products in the counter, one person is responsible for the billing purpose, one person is taking care of the security, one person is taking care of the product they have in storage. The work of every individual is separate and they are focussed on the specific task. Clean architecture also suggests to make the component separate and each component should perform some task. Clean Architecture proposes a layered architecture with clear boundaries between different system components to achieve independence of frameworks, UI, databases, and delivery mechanisms and the possibility to test in isolation. +> Imagine a large pizza chain with multiple ordering channels—web, mobile app, phone calls, and in-store kiosks. The core “pizza domain” logic (calculating prices, preparing orders, managing loyalty points) is kept entirely separate from the user interfaces and storage mechanisms. As a result, the chain can add or change the ordering channel (for example, introducing a chatbot or swapping out the database) without altering the fundamental pizza-ordering rules, thanks to the layered boundaries and strict dependency rules of Clean Architecture. -In plain word +In plain words -It helps to make the system more maintainable and easy to extend. +> Clean Architecture is a software design approach that isolates the core business logic from external concerns (like databases, frameworks, or UI) through strict layering and clear boundaries, ensuring that changes in one layer don't ripple through the entire system. Wikipedia says -> The clean architecture proposed by Robert C. Martin in 2012 combines the principles of the hexagonal architecture, the onion architecture and several other variants. It provides additional levels of detail of the component, which are presented as concentric rings. It isolates adapters and interfaces (user interface, databases, external systems, devices) in the outer rings of the architecture and leaves the inner rings for use cases and entities. -> -> The clean architecture uses the principle of dependency inversion with the strict rule that dependencies shall only exist between an outer ring to an inner ring and never the contrary. - +> The clean architecture proposed by Robert C. Martin in 2012 combines the principles of the hexagonal architecture, the onion architecture and several other variants. It provides additional levels of detail of the component, which are presented as concentric rings. It isolates adapters and interfaces (user interface, databases, external systems, devices) in the outer rings of the architecture and leaves the inner rings for use cases and entities. The clean architecture uses the principle of dependency inversion with the strict rule that dependencies shall only exist between an outer ring to an inner ring and never the contrary. -## Clean architecture Class Diagram - -![Clean Architecture](./etc/cleanArchitectureUMLDiagram.png "Clean Architecture class diagram") - -## When to Use the Clean Architecture Pattern in Java +Mind map -In all application we can use the clean architecture style and make the component separate and business logic separate from the UI and database. +![Clean Architecture Mind Map](./etc/clean-architecture-mind-map.png) -## Real-World Applications of Chain of Responsibility Pattern in Java. +Flowchart -In the application say Ecommerce application user gives teh order and the application is represented using teh clean architecture pattern. - -There are facility like the **product** where user can see the product details like the price and the features, **Cart** user can add the product they have selected and the **Order** where user can see the total order and calculate the price of the order. Learn how to implement this design pattern in Java with the following code snippet. +![Clean Architecture Flowchart](./etc/clean-architecture-flowchart.png) ## Programmatic Example of Clean Architecture Pattern -First we have the entity class like the `Product`, `Order` and teh `Cart` +First, we define the core domain entities: `Product`, `Order`, and `Cart`. These classes capture the fundamental business logic and state. + ```java public class Product { private String id; @@ -94,7 +87,9 @@ public class Order { } } ``` -The repository interfaces are created. + +The repository interfaces are created to abstract data operations for each domain object, allowing us to switch out storage or persistence mechanisms without changing higher-level logic. + ```java public interface CartRepository { void addItemToCart(String userId, Product product, int quantity); @@ -115,8 +110,8 @@ public interface OrderRepository { } ``` +The in-memory data store implementations use simple collections to hold state. They demonstrate how easily we can replace or extend the data layer (e.g., swapping in a database) without affecting the domain logic. -The in memory data store in the cart and order. ```java public class InMemoryCartRepository implements CartRepository { private final Map> userCarts = new HashMap<>(); @@ -182,7 +177,8 @@ public class InMemoryProductRepository implements ProductRepository { } ``` -The order controller. +The order controller coordinates the checkout process by using the use-case or service layer (`ShoppingCartService`). + ```java public class OrderController{ private final ShoppingCartService shoppingCartUseCase; @@ -196,7 +192,10 @@ public class OrderController{ } } ``` -The cart controller. + +The cart controller focuses on cart-related actions like adding or removing items and calculating totals. + + ```java public class CartController { private final ShoppingCartService shoppingCartUseCase; @@ -219,7 +218,8 @@ public class CartController { } ``` -The clean architecture in action. +The clean architecture in action. In the `main` method, we wire up everything, simulating a typical user flow: items are added to the cart, the total is calculated, and finally an order is placed. + ```java public static void main(String[] args) { @@ -246,33 +246,46 @@ public static void main(String[] args) { ``` The output of the code. + ```md Total: $2000.0 Order placed! Order ID: ORDER-1743349969254, Total: $2000.0 ``` -## Benefits and Trade-offs of Clean Architecture Pattern. +## When to Use the Clean Architecture Pattern in Java + +* When you want to keep business rules independent of UI, database, or any other external agency +* When you need a high level of maintainability and testability in large Java applications +* When you aim to enforce clear boundaries among application layers + +## Real-World Applications of Clean Architecture Pattern in Java + +* Large-scale enterprise systems in finance and insurance domains +* Microservices-based architectures that prioritize decoupling and modular design +* Java systems requiring stringent separation of concerns and domain-centered design + +## Benefits and Trade-offs of Clean Architecture Pattern Benefits: -The main benefits of the Clean Architecture involves - -**Scalability** - It allows to add new features without any issue. -**Modularity** - It makes the code loosely coupled and making the change in any component becomes easier. -**Testability** - The architecture promotes unit testing, integration testing, and acceptance testing of different layers independently. +* High maintainability by isolating core logic from infrastructure details +* Enhanced testability through clear boundaries around the domain model +* Flexibility in replacing or upgrading external components without affecting core logic Trade-Offs: -Initially the design needs to be done with high precision and the UML diagram should cover all the architectural structure. It will in return help to make it more channelised and the code extensibility will increase. +* Initial complexity from enforcing strict layers and boundaries +* Potential overhead in smaller projects not requiring such rigid separation +* Requires disciplined team adherence to architecture rules ## Related Java Design Patterns -* Dependency Injection - Dependency Injection (DI) is a key concept in Clean Architecture. It promotes loose coupling between classes by allowing dependencies to be injected rather than directly created by the class itself. -* Singleton Pattern - The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This is often used for shared services or resources, such as a configuration manager, logging service, or database connection pool. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): Facilitates decoupling layers by injecting dependencies rather than hard-coding them +* [Layered Architecture](https://java-design-patterns.com/patterns/layered-architecture/): Both separate concerns into distinct tiers but Clean Architecture emphasizes strict dependency rules +* [Hexagonal Architecture](https://java-design-patterns.com/patterns/hexagonal-architecture/): Similar focus on isolating core logic with ports and adapters ## References and Credits -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) -* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) -* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) -* [Refactoring to Patterns](https://amzn.to/3VOO4F5) -* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) \ No newline at end of file +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3wRnjp5) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) diff --git a/clean-architecture/etc/clean-architecture-flowchart.png b/clean-architecture/etc/clean-architecture-flowchart.png new file mode 100644 index 000000000000..043827f08844 Binary files /dev/null and b/clean-architecture/etc/clean-architecture-flowchart.png differ diff --git a/clean-architecture/etc/clean-architecture-mind-map.png b/clean-architecture/etc/clean-architecture-mind-map.png new file mode 100644 index 000000000000..59786d4a7c3e Binary files /dev/null and b/clean-architecture/etc/clean-architecture-mind-map.png differ diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java index ef6785391280..4b56dcbbfd0f 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java @@ -1,3 +1,27 @@ +/* + * 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.cleanarchitecture; import lombok.extern.slf4j.Slf4j; @@ -39,7 +63,7 @@ public static void main(final String[] args) { cartController.addItemToCart(userId, "2", 2); Order order = orderController.checkout(userId); - LOGGER.info("Total: ${}" + cartController.calculateTotal(userId)); + LOGGER.info("Total: ${}", cartController.calculateTotal(userId)); LOGGER.info( "Order placed! Order ID: {}, Total: ${}", order.getOrderId(), order.getTotalPrice()); diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java index 045ca8d22d5d..c4e65df94845 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java index 9cfaa118e807..da93cc2a6d93 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä @@ -24,7 +22,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package com.iluwatar.cleanarchitecture; /** diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java index 1463500766d4..844bc48345b4 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java index afdc866d7702..2965cddd57f3 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java index cce7a3818b18..b8a17cd6045a 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java index af052ef14781..c91677feeff5 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java index 63505dcf073b..70bf058dc2eb 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java index 36844288d9b0..d61dad322750 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java index f29b907592e1..4c7276fcb53f 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java index dd7050ea9e4b..100613872865 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java index 6b9324fd5d05..713b62e799bc 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using ZK framework - * licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java index 960b1a18b300..cd74aa3145cf 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java @@ -1,7 +1,5 @@ /* - * This project is licensed under the MIT license. - * Module model-view-viewmodel is using - * ZK framework licensed under LGPL (see lgpl-3.0.txt). + * 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ä diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java index 027c4b2dcf0e..7b8142f436ae 100644 --- a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java @@ -1,3 +1,27 @@ +/* + * 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. + */ /** * Provides classes and interfaces for the clean architecture pattern implementation. * diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java index e5904f3d0aae..86265d2886b7 100644 --- a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java @@ -1,3 +1,27 @@ +/* + * 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.cleanarchitecture; import static org.junit.jupiter.api.Assertions.*; diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java index 17af441718bc..c015c54c139c 100644 --- a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java @@ -1,3 +1,27 @@ +/* + * 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.cleanarchitecture; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -5,17 +29,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class CartControllerTest { +class CartControllerTest { - private ShoppingCartService shoppingCartUseCase; private CartController cartController; @BeforeEach - public void setUp() { + void setUp() { ProductRepository productRepository = new InMemoryProductRepository(); CartRepository cartRepository = new InMemoryCartRepository(); OrderRepository orderRepository = new InMemoryOrderRepository(); - shoppingCartUseCase = + ShoppingCartService shoppingCartUseCase = new ShoppingCartService(productRepository, cartRepository, orderRepository); cartController = new CartController(shoppingCartUseCase); } diff --git a/dao-factory/README.md b/dao-factory/README.md new file mode 100644 index 000000000000..c3ee3c5e893d --- /dev/null +++ b/dao-factory/README.md @@ -0,0 +1,360 @@ +--- +title: "DAO Factory Pattern: Flexible Data Access Layer for Seamless Data Source Switching" +shortTitle: DAO Factory +description: "Learn the Data Access Object Pattern combine with Abstract Factory Pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." +category: Structural +language: en +tag: + - Abstraction + - Data access + - Layer architecture + - Persistence +--- + +## Also known as + +* DAO Factory +* Factory for Data Access Object strategy using Abstract Factory + + +## Intent of Data Access Object Factory Design Pattern + +The DAO Factory combines the Data Access Object and Abstract Factory patterns to seperate business logic from data access logic, while increasing flexibility when switching between different data sources. + +## Detailed Explanation of Data Access Object Factory Pattern with Real-World Examples + +Real-world example + +> A real-world analogy for the DAO Factory pattern is a multilingual customer service center. Imagine a bank that serves customers speaking different languages—English, French, and Spanish. When a customer calls, an automated system first detects the customer's preferred language, then routes the call to the appropriate support team that speaks that language. Each team follows the same company policies (standard procedures), but handles interactions in a language-specific way. +> +> In the same way, the DAO Factory pattern uses a factory to determine the correct set of DAO implementations based on the data source (e.g., MySQL, MongoDB). Each DAO factory returns a group of DAOs tailored to a specific data source, all conforming to the same interfaces. This allows the application to interact with any supported database in a consistent manner, without changing the business logic—just like how the customer service system handles multiple languages while following the same support protocols. + +In plain words + +> The DAO Factory pattern abstracts the creation of Data Access Objects (DAOs), allowing you to request a specific DAO from a central factory without worrying about its underlying implementation. This makes the code easier to maintain and flexible to change, especially when switching between databases or storage mechanisms. + +Wikipedia says + +> The Data Access Object (DAO) design pattern is a structural pattern that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, the DAO provides some specific data operations without exposing details of the database. The DAO Factory is an extension of this concept, responsible for generating the required DAO implementations. + +Class diagram + +![Data Access Object Factory class diagram](./etc/dao-factory.png "Data Access Object Factory class diagram") + +## Programmatic Example of Data Access Object Factory in Java + +In this example, the persistence object represents a Customer. + +We are considering a flexible storage strategy where the application should be able to work with three different types of data sources: an H2 in-memory relational database (RDBMS), a MongoDB (object-oriented database), and a JSON flat file (flat file storage). + +``` java +public enum DataSourceType { +H2, +Mongo, +FlatFile +} +``` + +First, we define a Customer class that will be persisted in different storage systems. The ID field is generic to maintain compatibility with both relational and object-oriented databases. + +``` java +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Customer implements Serializable { +private T id; +private String name; +} +``` + +Next, we define a CustomerDAO interface that outlines the standard CRUD operations on the Customer model. This interface will have three concrete implementations, each corresponding to a specific data source: H2 in-memory database, MongoDB, and JSON file. + +``` java +public interface CustomerDAO { + + void save(Customer customer); + + void update(Customer customer); + + void delete(T id); + + List> findAll(); + + Optional> findById(T id); +} +``` + +Here is the implementations + +``` java +@Slf4j +@RequiredArgsConstructor +public class H2CustomerDAO implements CustomerDAO { +private final DataSource dataSource; +private final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)"; +private final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?"; +private final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?"; +private final String SELECT_CUSTOMER_BY_ID = "SELECT * FROM customer WHERE id= ?"; +private final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer"; +private final String CREATE_SCHEMA = +"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"; +private final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + + @Override + public void save(Customer customer) { + // Implement operation save for H2 + } + + @Override + public void update(Customer customer) { + // Implement operation save for H2 + } + + @Override + public void delete(Long id) { + // Implement operation delete for H2 + } + + @Override + public List> findAll() { + // Implement operation find all for H2 + } + + @Override + public Optional> findById(Long id) { + // Implement operation find by id for H2 + } +} +``` + +``` java +@Slf4j +@RequiredArgsConstructor +public class MongoCustomerDAO implements CustomerDAO { +private final MongoCollection customerCollection; + + // Implement CRUD operation with MongoDB data source +} +``` + +``` java +@Slf4j +@RequiredArgsConstructor +public class FlatFileCustomerDAO implements CustomerDAO { + private final Path filePath; + private final Gson gson; + Type customerListType = new TypeToken>>() { + }.getType(); + + // Implement CRUD operation with Flat file data source +} +``` + +After that, we create an abstract class DAOFactory that defines two key methods: a static method getDataSource() and an abstract method createCustomerDAO(). + +- The getDataSource() method is a factory selector—it returns a concrete DAOFactory instance based on the type of data source requested. + +- Each subclass of DAOFactory will implement the createCustomerDAO() method to provide the corresponding CustomerDAO implementation. + +``` java +public abstract class DAOFactory { + public static DAOFactory getDataSource(DataSourceType dataSourceType) { + return switch (dataSourceType) { + case H2 -> new H2DataSourceFactory(); + case Mongo -> new MongoDataSourceFactory(); + case FlatFile -> new FlatFileDataSourceFactory(); + }; + } + + public abstract CustomerDAO createCustomerDAO(); +} +``` + +We then implement three specific factory classes: + +H2DataSourceFactory for H2 in-memory RDBMS +``` java +public class H2DataSourceFactory extends DAOFactory { + private final String DB_URL = "jdbc:h2:~/test"; + private final String USER = "sa"; + private final String PASS = ""; + + @Override + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + return dataSource; + } +} +``` + +MongoDataSourceFactory for MongoDB +``` java +public class MongoDataSourceFactory extends DAOFactory { + private final String CONN_STR = "mongodb://localhost:27017/"; + private final String DB_NAME = "dao_factory"; + private final String COLLECTION_NAME = "customer"; + + @Override + public CustomerDAO createCustomerDAO() { + try { + MongoClient mongoClient = MongoClients.create(CONN_STR); + MongoDatabase database = mongoClient.getDatabase(DB_NAME); + MongoCollection customerCollection = database.getCollection(COLLECTION_NAME); + return new MongoCustomerDAO(customerCollection); + } catch (RuntimeException e) { + throw new RuntimeException("Error: " + e); + } + } +} +``` + +FlatFileDataSourceFactory for flat file storage using JSON +``` java +public class FlatFileDataSourceFactory extends DAOFactory { + private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json"; + @Override + public CustomerDAO createCustomerDAO() { + Path filePath = Paths.get(FILE_PATH); + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create(); + return new FlatFileCustomerDAO(filePath, gson); + } +} +``` + +Finally, in the main function of client code, we will demonstrate CRUD operations on the Customer using three data source type. +``` java + // Perform CRUD H2 Database + LOGGER.debug("H2 - Create customer"); + performCreateCustomer(customerDAO, + List.of(customerInmemory1, customerInmemory2, customerInmemory3)); + LOGGER.debug("H2 - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateInmemory); + LOGGER.debug("H2 - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); + + // Perform CRUD MongoDb + daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + customerDAO = daoFactory.createCustomerDAO(); + LOGGER.debug("Mongo - Create customer"); + performCreateCustomer(customerDAO, List.of(customer4, customer5)); + LOGGER.debug("Mongo - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateMongo); + LOGGER.debug("Mongo - Delete customer"); + performDeleteCustomer(customerDAO, idCustomerMongo2); + deleteSchema(customerDAO); + + // Perform CRUD Flat file + daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + customerDAO = daoFactory.createCustomerDAO(); + LOGGER.debug("Flat file - Create customer"); + performCreateCustomer(customerDAO, + List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + LOGGER.debug("Flat file - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateFlatFile); + LOGGER.debug("Flat file - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); +``` + +The program output +``` java +17:17:24.368 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Create customer +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Green) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Update customer +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Delete customer +17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow) +17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.747 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Create customer +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Elliot) +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Update customer +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Henry) +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Delete customer +17:17:24.850 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.876 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Create customer +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Duc) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Update customer +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Delete customer +17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh) +17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +``` +## When to Use the Data Access Object Factory Pattern in Java + +Use the DAO Factory Pattern when: + +* The application needs to support multiple types of storage (RDBMS, NoSQL, file system, etc.) with minimal changes to business logic. +* You want to abstract and isolate persistence logic from the core application logic. +* You aim to make your data access layer pluggable and easy to extend with new storage technologies. +* You want to enable easier unit testing and dependency injection by providing mock implementations of DAOs. +* Runtime configuration (e.g., via environment variables or application settings) determines which data source to use. + +## Data Access Object Factory Pattern Java Tutorials + +* [Core J2EE Patterns - Data Access Object (Oracle)](https://www.oracle.com/java/technologies/dataaccessobject.html) +* [DAO Factories: Java Design Patterns (Youtube)](https://www.youtube.com/watch?v=5HGe9s9qM-o) +* [Java Design Patterns and Architecture (CaveofProgramming)](https://caveofprogramming.teachable.com/courses/2084/lectures/39549) + +## Real-World Applications of Data Access Object Factory Pattern in Java + +* Enterprise Java Applications: Where switching between test, dev, and production databases is common (e.g., MySQL ↔ MongoDB ↔ In-Memory). +* Spring Data JPA & Repository Abstraction: Though Spring provides its own abstraction, the concept is similar to DAO factory for modular and pluggable persistence. +* Microservices with Varying Storage Backends: Different microservices might store data in SQL, NoSQL, or even flat files; using a DAO Factory per service ensures consistency. +* Data Integration Tools: Tools that support importing/exporting from various formats (CSV, JSON, databases) often use DAO factories behind the scenes. +* Framework-Level Implementations: Custom internal frameworks where persistence layers need to support multiple database types. + +## Benefits and Trade-offs of Data Access Object Factory Pattern + +Benefits: + +* Abstraction of Data Source Logic: Client code interacts only with DAO interfaces, completely decoupled from how and where the data is stored. +* Flexibility in Persistence Strategy: Easily switch between databases (e.g., H2, MongoDB, flat files) by changing the factory configuration. +* Improved Maintainability: Storage logic for each data source is encapsulated within its own DAO implementation and factory, making it easier to update or extend. +* Code Reusability: Common data access logic (e.g., CRUD operations) can be reused across different implementations and projects. +* Testability: DAOs and factories can be mocked or stubbed easily, which supports unit testing and dependency injection. + +Trade-offs: +* Increased Complexity: Introducing abstract DAOs and multiple factory classes adds structural complexity to the codebase. +* Boilerplate Code: Requires defining many interfaces and implementations, even for simple data access needs. +* Less Transparent Behavior: Since clients access DAOs indirectly via factories, understanding the concrete data source behavior may require deeper inspection. + +## Related Java Design Patterns + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): DAO Factory is a concrete application of the Factory Pattern, used to create DAO objects in a flexible way. +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): When supporting multiple data sources (e.g., MySQLDAO, OracleDAO), DAO Factory can act as an Abstract Factory. +* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/data-access-object/): The core pattern managed by DAO Factory, it separates data access logic from business logic. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): DAO Factory is often implemented as a Singleton to ensure only one instance manages DAO creation. +* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Can be used alongside DAO Factory to retrieve DAO services efficiently. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): In frameworks like Spring, DAOs are typically injected into the service layer instead of being retrieved from a factory. + + +## References and Credits + +* [DAO Factory - J2EE Design Patterns Book](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re15.html) +* [DAO Factory patterns with Hibernate](http://www.giuseppeurso.eu/en/dao-factory-patterns-with-hibernate/) +* [Design Patterns - Java Means DURGA SOFT](https://www.scribd.com/document/407219980/2-DAO-Factory-Design-Pattern) +* [Generic DAO pattern - Hibernate](https://in.relation.to/2005/09/09/generic-dao-pattern-with-jdk-50/) + \ No newline at end of file diff --git a/dao-factory/etc/dao-factory.png b/dao-factory/etc/dao-factory.png new file mode 100644 index 000000000000..d93547d79957 Binary files /dev/null and b/dao-factory/etc/dao-factory.png differ diff --git a/dao-factory/etc/dao-factory.puml b/dao-factory/etc/dao-factory.puml new file mode 100644 index 000000000000..5196c5a1ebd9 --- /dev/null +++ b/dao-factory/etc/dao-factory.puml @@ -0,0 +1,74 @@ +@startuml +package com.iluwatar.daofactory { + class App { + {static} void main(String[] args) + } + + class Customer { + T id + String name + } + + interface CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(ID id) + List> findAll() + Optional> findById(ID id) + void deleteSchema() + } + + abstract class DAOFactory { + {static} DAOFactory getDataSource(DataSourceType dataType) + {abstract} CustomerDAO createCustomerDAO() + } + + enum DataSourceType { + H2 + Mongo + FlatFile + } + + class FlatFileCustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(Long id) + List> findAll() + Optional> findById(Long id) + void deleteSchema() + } + + class H2CustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(Long id) + List> findAll() + Optional> findById(Long id) + void deleteSchema() + } + + class FlatFileDataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + class H2DataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + class MongoCustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(ObjectId id) + List> findAll() + Optional> findById(ObjectId id) + void deleteSchema() + } + class MongoDataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + DataSourceType ..+ DAOFactory + DAOFactory ..+ App + App --> Customer + } +@enduml \ No newline at end of file diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml new file mode 100644 index 000000000000..2e771e736688 --- /dev/null +++ b/dao-factory/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + dao-factory + + + 21 + 21 + UTF-8 + + + + + com.h2database + h2 + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.mongodb + mongodb-driver-legacy + + + com.google.code.gson + gson + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java new file mode 100644 index 000000000000..b80d3c5ac56a --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -0,0 +1,124 @@ +/* + * 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.daofactory; + +import java.io.Serializable; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; + +@Slf4j +public class App { + + public static void main(String[] args) { + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2); + CustomerDAO customerDAO = daoFactory.createCustomerDAO(); + + // Perform CRUD H2 Database + if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) { + h2CustomerDAO.deleteSchema(); + h2CustomerDAO.createSchema(); + } + Customer customerInmemory1 = new Customer<>(1L, "Green"); + Customer customerInmemory2 = new Customer<>(2L, "Red"); + Customer customerInmemory3 = new Customer<>(3L, "Blue"); + Customer customerUpdateInmemory = new Customer<>(1L, "Yellow"); + + LOGGER.debug("H2 - Create customer"); + performCreateCustomer( + customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3)); + LOGGER.debug("H2 - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateInmemory); + LOGGER.debug("H2 - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); + + // Perform CRUD MongoDb + daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO); + customerDAO = daoFactory.createCustomerDAO(); + ObjectId idCustomerMongo1 = new ObjectId(); + ObjectId idCustomerMongo2 = new ObjectId(); + Customer customer4 = new Customer<>(idCustomerMongo1, "Masca"); + Customer customer5 = new Customer<>(idCustomerMongo2, "Elliot"); + Customer customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry"); + + LOGGER.debug("Mongo - Create customer"); + performCreateCustomer(customerDAO, List.of(customer4, customer5)); + LOGGER.debug("Mongo - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateMongo); + LOGGER.debug("Mongo - Delete customer"); + performDeleteCustomer(customerDAO, idCustomerMongo2); + deleteSchema(customerDAO); + + // Perform CRUD Flat file + daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE); + customerDAO = daoFactory.createCustomerDAO(); + Customer customerFlatFile1 = new Customer<>(1L, "Duc"); + Customer customerFlatFile2 = new Customer<>(2L, "Quang"); + Customer customerFlatFile3 = new Customer<>(3L, "Nhat"); + Customer customerUpdateFlatFile = new Customer<>(1L, "Thanh"); + LOGGER.debug("Flat file - Create customer"); + performCreateCustomer( + customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + LOGGER.debug("Flat file - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateFlatFile); + LOGGER.debug("Flat file - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); + } + + public static void deleteSchema(CustomerDAO customerDAO) { + customerDAO.deleteSchema(); + } + + public static void performCreateCustomer( + CustomerDAO customerDAO, List> customerList) { + for (Customer customer : customerList) { + customerDAO.save(customer); + } + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } + + public static void performUpdateCustomer( + CustomerDAO customerDAO, Customer customerUpdate) { + customerDAO.update(customerUpdate); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } + + public static void performDeleteCustomer( + CustomerDAO customerDAO, T customerId) { + customerDAO.delete(customerId); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java new file mode 100644 index 000000000000..9559e765c7d4 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java @@ -0,0 +1,36 @@ +/* + * 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.daofactory; + +/** Customer exception */ +public class CustomException extends RuntimeException { + public CustomException(String message) { + super(message); + } + + public CustomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java new file mode 100644 index 000000000000..95b675487d27 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -0,0 +1,47 @@ +/* + * 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.daofactory; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * A customer generic POJO that represents the data that can be stored in any supported data source. + * This class is designed t work with various ID types (e.g., Long, String, or ObjectId) through + * generic, making it adaptable to different persistence system. + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Customer implements Serializable { + private T id; + private String name; +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java new file mode 100644 index 000000000000..34316b4c49af --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -0,0 +1,85 @@ +/* + * 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.daofactory; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +/** + * The Data Access Object (DAO) pattern provides an abstraction layer between the application and + * the database. It encapsulates data access logic, allowing the application to work with domain + * objects instead of direct database operations. + * + *

    Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping + * client code unchanged. + * + * @see H2CustomerDAO + * @see MongoCustomerDAO + * @see FlatFileCustomerDAO + */ +public interface CustomerDAO { + /** + * Persist the given customer + * + * @param customer the customer to persist + */ + void save(Customer customer); + + /** + * Update the given customer + * + * @param customer the customer to update + */ + void update(Customer customer); + + /** + * Delete the customer with the given id + * + * @param id the id of the customer to delete + */ + void delete(T id); + + /** + * Find all customers + * + * @return a list of customers + */ + List> findAll(); + + /** + * Find the customer with the given id + * + * @param id the id of the customer to find + * @return the customer with the given id + */ + Optional> findById(T id); + + /** + * Delete the customer schema. After executing the statements, this function will be called to + * clean up the data and delete the records. + */ + void deleteSchema(); +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java new file mode 100644 index 000000000000..e7d33186bec5 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -0,0 +1,45 @@ +/* + * 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.daofactory; + +/** + * An abstract factory class that provides a way to create concrete DAO (Data Access Object) + * factories for different data sources types (e.g., H2, Mongo, FlatFile). + * + *

    This class follows the Abstract Factory design pattern, allowing applications to retrieve the + * approriate DAO implementation without being tightly coupled to a specific data source. + * + * @see H2DataSourceFactory + * @see MongoDataSourceFactory + * @see FlatFileDataSourceFactory + */ +public abstract class DAOFactory { + /** + * Retrieves a {@link CustomerDAO} implementation specific to the underlying data source.. + * + * @return A data source-specific implementation of {@link CustomerDAO} + */ + public abstract CustomerDAO createCustomerDAO(); +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java new file mode 100644 index 000000000000..ce1ea7115157 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java @@ -0,0 +1,62 @@ +/* + * 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.daofactory; + +/** + * {@code DAOFactoryProvider} is a utility class responsible for providing concrete implementations + * of the {@link DAOFactory} interface based on the specified data source type. + * + *

    This class acts as an entry point to obtain DAO factories for different storage mechanisms + * such as relational databases (e.g., H2), document stores (e.g., MongoDB), or file-based systems. + * It uses the {@link DataSourceType} enumeration to determine which concrete factory to + * instantiate. + * + *

    Example usage: + * + *

    {@code
    + * DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
    + * }
    + */ +public class DAOFactoryProvider { + + private DAOFactoryProvider() {} + + /** + * Returns a concrete {@link DAOFactory} instance based on the specified data source type. + * + * @param dataSourceType The type of data source for which a factory is needed. Supported values: + * {@code H2}, {@code Mongo}, {@code FlatFile} + * @return A {@link DAOFactory} implementation corresponding to the given data source type. + * @throws IllegalArgumentException if the given data source type is not supported. + */ + public static DAOFactory getDataSource(DataSourceType dataSourceType) { + return switch (dataSourceType) { + case H2 -> new H2DataSourceFactory(); + case MONGO -> new MongoDataSourceFactory(); + case FLAT_FILE -> new FlatFileDataSourceFactory(); + default -> throw new IllegalArgumentException("Unsupported data source type"); + }; + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java new file mode 100644 index 000000000000..da01d451f09e --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -0,0 +1,32 @@ +/* + * 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.daofactory; + +/** Enumerates the types of data sources supported by the application. */ +public enum DataSourceType { + H2, + MONGO, + FLAT_FILE +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java new file mode 100644 index 000000000000..8f1f1f144f77 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -0,0 +1,175 @@ +/* + * 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.daofactory; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * A Flat File implementation of {@link CustomerDAO}, which store the customer data in a JSON file + * at path {@code ~/Desktop/customer.json}. + */ +@Slf4j +@RequiredArgsConstructor +public class FlatFileCustomerDAO implements CustomerDAO { + private final Path filePath; + private final Gson gson; + Type customerListType = new TypeToken>>() {}.getType(); + + protected Reader createReader(Path filePath) throws IOException { + return new FileReader(filePath.toFile()); + } + + protected Writer createWriter(Path filePath) throws IOException { + return new FileWriter(filePath.toFile()); + } + + /** {@inheritDoc} */ + @Override + public void save(Customer customer) { + List> customers = new LinkedList<>(); + if (filePath.toFile().exists()) { + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new CustomException("Failed to read customer data", ex); + } + } + customers.add(customer); + try (Writer writer = createWriter(filePath)) { + gson.toJson(customers, writer); + } catch (IOException ex) { + throw new CustomException("Failed to write customer data", ex); + } + } + + /** {@inheritDoc} */ + @Override + public void update(Customer customer) { + if (!filePath.toFile().exists()) { + throw new CustomException("File not found"); + } + List> customers; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new CustomException("Failed to read customer data", ex); + } + customers.stream() + .filter(c -> c.getId().equals(customer.getId())) + .findFirst() + .ifPresentOrElse( + c -> c.setName(customer.getName()), + () -> { + throw new CustomException("Customer not found with id: " + customer.getId()); + }); + try (Writer writer = createWriter(filePath)) { + gson.toJson(customers, writer); + } catch (IOException ex) { + throw new CustomException("Failed to write customer data", ex); + } + } + + /** {@inheritDoc} */ + @Override + public void delete(Long id) { + if (!filePath.toFile().exists()) { + throw new CustomException("File not found"); + } + List> customers; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new CustomException("Failed to read customer data", ex); + } + Customer customerToRemove = + customers.stream() + .filter(c -> c.getId().equals(id)) + .findFirst() + .orElseThrow(() -> new CustomException("Customer not found with id: " + id)); + customers.remove(customerToRemove); + try (Writer writer = createWriter(filePath)) { + gson.toJson(customers, writer); + } catch (IOException ex) { + throw new CustomException("Failed to write customer data", ex); + } + } + + /** {@inheritDoc} */ + @Override + public List> findAll() { + if (!filePath.toFile().exists()) { + throw new CustomException("File not found"); + } + List> customers; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new CustomException("Failed to read customer data", ex); + } + return customers; + } + + /** {@inheritDoc} */ + @Override + public Optional> findById(Long id) { + if (!filePath.toFile().exists()) { + throw new CustomException("File not found"); + } + List> customers = null; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new CustomException("Failed to read customer data", ex); + } + return customers.stream().filter(c -> c.getId().equals(id)).findFirst(); + } + + /** {@inheritDoc} */ + @Override + public void deleteSchema() { + if (!filePath.toFile().exists()) { + throw new CustomException("File not found"); + } + try { + Files.delete(filePath); + } catch (IOException ex) { + throw new CustomException("Failed to delete customer data"); + } + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java new file mode 100644 index 000000000000..c3d267f57f22 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -0,0 +1,54 @@ +/* + * 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.daofactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** FlatFileDataSourceFactory concrete factory. */ +public class FlatFileDataSourceFactory extends DAOFactory { + private static final String FILE_PATH_PROPERTY = "customer.file.path"; + private static final String FILE_PATH_ENV = "CUSTOMER_FILE_PATH"; + + private static Path resolveFilePath() { + String configuredPath = System.getProperty(FILE_PATH_PROPERTY); + if (configuredPath == null || configuredPath.isBlank()) { + configuredPath = System.getenv(FILE_PATH_ENV); + } + if (configuredPath == null || configuredPath.isBlank()) { + return Paths.get(System.getProperty("java.io.tmpdir"), "customer.json"); + } + return Paths.get(configuredPath); + } + + @Override + public CustomerDAO createCustomerDAO() { + Path filePath = resolveFilePath(); + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); + return new FlatFileCustomerDAO(filePath, gson); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java new file mode 100644 index 000000000000..fe027426391c --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -0,0 +1,179 @@ +/* + * 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.daofactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * An implementation of {@link CustomerDAO} that uses H2 database (http://www.h2database.com/) which + * is an in-memory database and data will lost after application exits. + */ +@Slf4j +@RequiredArgsConstructor +public class H2CustomerDAO implements CustomerDAO { + private final DataSource dataSource; + private static final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)"; + private static final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?"; + private static final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?"; + private static final String SELECT_CUSTOMER_BY_ID = + "SELECT customer.id, customer.name FROM customer WHERE id= ?"; + private static final String SELECT_ALL_CUSTOMERS = "SELECT customer.* FROM customer"; + private static final String CREATE_SCHEMA = + "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"; + private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + + /** {@inheritDoc} */ + @Override + public void save(Customer customer) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) { + saveStatement.setLong(1, customer.getId()); + saveStatement.setString(2, customer.getName()); + saveStatement.execute(); + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public void update(Customer customer) { + if (Objects.isNull(customer) || Objects.isNull(customer.getId())) { + throw new CustomException("Custome null or customer id null"); + } + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); + PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) { + selectStatement.setLong(1, customer.getId()); + try (ResultSet resultSet = selectStatement.executeQuery()) { + if (!resultSet.next()) { + throw new CustomException("Customer not found with id: " + customer.getId()); + } + } + updateStatement.setString(1, customer.getName()); + updateStatement.setLong(2, customer.getId()); + updateStatement.executeUpdate(); + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public void delete(Long id) { + if (Objects.isNull(id)) { + throw new CustomException("Customer id null"); + } + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); + PreparedStatement deleteStatement = connection.prepareStatement(DELETE_CUSTOMER)) { + selectStatement.setLong(1, id); + try (ResultSet resultSet = selectStatement.executeQuery()) { + if (!resultSet.next()) { + throw new CustomException("Customer not found with id: " + id); + } + } + deleteStatement.setLong(1, id); + deleteStatement.execute(); + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public List> findAll() { + List> customers = new LinkedList<>(); + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) { + try (ResultSet resultSet = selectStatement.executeQuery()) { + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customers.add(new Customer<>(idCustomer, nameCustomer)); + } + } + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + return customers; + } + + /** {@inheritDoc} */ + @Override + public Optional> findById(Long id) { + if (Objects.isNull(id)) { + throw new CustomException("Customer id null"); + } + Customer customer = null; + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectByIdStatement = + connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) { + selectByIdStatement.setLong(1, id); + try (ResultSet resultSet = selectByIdStatement.executeQuery()) { + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customer = new Customer<>(idCustomer, nameCustomer); + } + } + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + return Optional.ofNullable(customer); + } + + /** Create customer schema. */ + public void createSchema() { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement()) { + statement.execute(CREATE_SCHEMA); + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + } + + /** {@inheritDoc}} */ + @Override + public void deleteSchema() { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); ) { + statement.execute(DROP_SCHEMA); + } catch (SQLException e) { + throw new CustomException(e.getMessage(), e); + } + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java new file mode 100644 index 000000000000..dbb39dd98f3b --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -0,0 +1,48 @@ +/* + * 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.daofactory; + +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; + +/** H2DataSourceFactory concrete factory. */ +public class H2DataSourceFactory extends DAOFactory { + private static final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; + private static final String USER = "sa"; + private static final String PASS = ""; + + @Override + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + return dataSource; + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java new file mode 100644 index 000000000000..1870f61e85fd --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -0,0 +1,106 @@ +/* + * 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.daofactory; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.DeleteResult; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; + +/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */ +@Slf4j +@RequiredArgsConstructor +public class MongoCustomerDAO implements CustomerDAO { + private final MongoCollection customerCollection; + + /** {@inheritDoc} */ + @Override + public void save(Customer customer) { + Document customerDocument = new Document("_id", customer.getId()); + customerDocument.append("name", customer.getName()); + customerCollection.insertOne(customerDocument); + } + + /** {@inheritDoc} */ + @Override + public void update(Customer customer) { + Document updateQuery = new Document("_id", customer.getId()); + Bson update = Updates.set("name", customer.getName()); + customerCollection.updateOne(updateQuery, update); + } + + /** {@inheritDoc} */ + @Override + public void delete(ObjectId objectId) { + Bson deleteQuery = Filters.eq("_id", objectId); + DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery); + if (deleteResult.getDeletedCount() == 0) { + throw new CustomException("Delete failed: No document found with id: " + objectId); + } + } + + /** {@inheritDoc} */ + @Override + public List> findAll() { + List> customers = new LinkedList<>(); + FindIterable customerDocuments = customerCollection.find(); + for (Document customerDocument : customerDocuments) { + Customer customer = + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); + customers.add(customer); + } + return customers; + } + + /** {@inheritDoc} */ + @Override + public Optional> findById(ObjectId objectId) { + Bson filter = Filters.eq("_id", objectId); + Document customerDocument = customerCollection.find(filter).first(); + Customer customerResult = null; + if (customerDocument != null) { + customerResult = + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); + } + return Optional.ofNullable(customerResult); + } + + /** {@inheritDoc} */ + @Override + public void deleteSchema() { + customerCollection.drop(); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java new file mode 100644 index 000000000000..967b0bd8f744 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -0,0 +1,57 @@ +/* + * 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.daofactory; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; +import org.bson.types.ObjectId; + +/** MongoDataSourceFactory concrete factory. */ +public class MongoDataSourceFactory extends DAOFactory { + private static final String CONN_STR = "mongodb://localhost:27017/"; + private static final String DB_NAME = "dao_factory"; + private static final String COLLECTION_NAME = "customer"; + private static final MongoClient MONGO_CLIENT = createMongoClient(); + + private static MongoClient createMongoClient() { + MongoClient mongoClient = MongoClients.create(CONN_STR); + Runtime.getRuntime().addShutdownHook(new Thread(mongoClient::close)); + return mongoClient; + } + + @Override + public CustomerDAO createCustomerDAO() { + try { + MongoDatabase database = MONGO_CLIENT.getDatabase(DB_NAME); + MongoCollection customerCollection = database.getCollection(COLLECTION_NAME); + return new MongoCustomerDAO(customerCollection); + } catch (RuntimeException e) { + throw new CustomException("Error creating Mongo customer DAO", e); + } + } +} diff --git a/dao-factory/src/main/resources/logback.xml b/dao-factory/src/main/resources/logback.xml new file mode 100644 index 000000000000..f82341ebb2ab --- /dev/null +++ b/dao-factory/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java new file mode 100644 index 000000000000..12efea42bdc6 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -0,0 +1,94 @@ +/* + * 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.daofactory; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** {@link App} */ +class AppTest { + /** Test perform CRUD in main class */ + private CustomerDAO mockLongCustomerDAO; + + private CustomerDAO mockObjectIdCustomerDAO; + + @BeforeEach + void setUp() { + mockLongCustomerDAO = mock(CustomerDAO.class); + mockObjectIdCustomerDAO = mock(CustomerDAO.class); + } + + @Test + void testPerformCreateCustomerWithLongId() { + Customer c1 = new Customer<>(1L, "Test1"); + Customer c2 = new Customer<>(2L, "Test2"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2)); + + App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2)); + + verify(mockLongCustomerDAO).save(c1); + verify(mockLongCustomerDAO).save(c2); + verify(mockLongCustomerDAO).findAll(); + } + + @Test + void testPerformUpdateCustomerWithObjectId() { + ObjectId id = new ObjectId(); + Customer updatedCustomer = new Customer<>(id, "Updated"); + + when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer)); + + App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer); + + verify(mockObjectIdCustomerDAO).update(updatedCustomer); + verify(mockObjectIdCustomerDAO).findAll(); + } + + @Test + void testPerformDeleteCustomerWithLongId() { + Long id = 100L; + Customer remainingCustomer = new Customer<>(1L, "Remaining"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer)); + + App.performDeleteCustomer(mockLongCustomerDAO, id); + + verify(mockLongCustomerDAO).delete(id); + verify(mockLongCustomerDAO).findAll(); + } + + @Test + void testDeleteSchema() { + App.deleteSchema(mockLongCustomerDAO); + verify(mockLongCustomerDAO).deleteSchema(); + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java new file mode 100644 index 000000000000..f8aaf199762d --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -0,0 +1,54 @@ +/* + * 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.daofactory; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +/** {@link DAOFactory} */ +class DAOFactoryTest { + + @Test + void verifyH2CustomerDAOCreation() { + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(H2CustomerDAO.class, customerDAO); + } + + @Test + void verifyMongoCustomerDAOCreation() { + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(MongoCustomerDAO.class, customerDAO); + } + + @Test + void verifyFlatFileCustomerDAOCreation() { + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(FlatFileCustomerDAO.class, customerDAO); + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java new file mode 100644 index 000000000000..470964f4217a --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -0,0 +1,500 @@ +/* + * 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.daofactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +/** {@link FlatFileCustomerDAO} */ +class FlatFileCustomerDAOTest { + private Path filePath; + private File file; + private Gson gson; + + private final Type customerListType = new TypeToken>>() {}.getType(); + private final Customer existingCustomer = new Customer<>(1L, "Thanh"); + private FlatFileCustomerDAO flatFileCustomerDAO; + private FileReader fileReader; + private FileWriter fileWriter; + + @BeforeEach + void setUp() { + filePath = mock(Path.class); + file = mock(File.class); + gson = mock(Gson.class); + fileReader = mock(FileReader.class); + fileWriter = mock(FileWriter.class); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; + when(filePath.toFile()).thenReturn(file); + } + + /** Class test with scenario Save Customer */ + @Nested + class Save { + @Test + void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() { + when(file.exists()).thenReturn(false); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); + } + + @Test + void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); + } + + @Test + void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() { + List> customers = new LinkedList<>(); + customers.add(new Customer<>(2L, "Duc")); + customers.add(new Customer<>(3L, "Nguyen")); + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(customers); + + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson).toJson(argThat((List> list) -> list.size() == 3), eq(fileWriter)); + } + + @Test + void whenReadFails_thenThrowException() { + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + when(file.exists()).thenReturn(true); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer)); + } + + @Test + void whenWriteFails_thenThrowException() { + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + throw new IOException("Failed to write file"); + } + }; + when(file.exists()).thenReturn(true); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer)); + } + } + + /** Class test with scenario Update Customer */ + @Nested + class Update { + @Test + void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + + @Test + void whenReadFails_thenThrowException() { + when(file.exists()).thenReturn(true); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + + @Test + void whenWriteFails_thenThrowException() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))) + .thenReturn( + new LinkedList<>() { + { + add(new Customer<>(1L, "Quang")); + } + }); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + throw new IOException("Failed to write file"); + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; + flatFileCustomerDAO.update(existingCustomer); + verify(gson) + .toJson( + argThat( + (List> customers) -> + customers.size() == 1 + && customers.stream() + .anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh"))), + eq(fileWriter)); + } + + @Test + void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(2L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + } + + /** Class test with scenario Delete Customer */ + @Nested + class Delete { + @Test + void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); + } + + @Test + void whenReadFails_thenThrowException() { + when(file.exists()).thenReturn(true); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); + } + + @Test + void whenWriteFails_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + throw new IOException("Failed to write file"); + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); + } + + @Test + void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + + flatFileCustomerDAO.delete(1L); + assertEquals(1, existingListCustomer.size()); + verify(gson) + .toJson( + argThat( + (List> customers) -> + customers.stream() + .noneMatch(c -> c.getId().equals(1L) && c.getName().equals("Quang"))), + eq(fileWriter)); + } + + @Test + void givenIdNotExist_whenDeleteCustomer_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(3L)); + } + } + + /** Class test with scenario Find All Customer */ + @Nested + class FindAll { + @Test + void givenFileNotExist_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll()); + } + + @Test + void whenReadFails_thenThrowException() { + when(file.exists()).thenReturn(true); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll()); + } + + @Test + void givenEmptyCustomer_thenReturnEmptyList() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + List> customers = flatFileCustomerDAO.findAll(); + assertEquals(0, customers.size()); + verify(gson).fromJson(fileReader, customerListType); + } + + @Test + void givenCustomerExist_thenReturnCustomerList() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + List> customers = flatFileCustomerDAO.findAll(); + assertEquals(2, customers.size()); + } + } + + /** Class test with scenario Find By Id Customer */ + @Nested + class FindById { + + @Test + void givenFilePathNotExist_whenFindById_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L)); + } + + @Test + void whenReadFails_thenThrowException() { + when(file.exists()).thenReturn(true); + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L)); + } + + @Test + void givenIdCustomerExist_whenFindById_thenReturnCustomer() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + Optional> customer = flatFileCustomerDAO.findById(1L); + assertTrue(customer.isPresent()); + assertEquals("Quang", customer.get().getName()); + } + + @Test + void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + Optional> customers = flatFileCustomerDAO.findById(1L); + assertTrue(customers.isEmpty()); + } + } + + /** Clas test with scenario Delete schema */ + @Nested + class DeleteSchema { + @Test + void givenFilePathExist_thenDeleteFile() { + when(file.exists()).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + flatFileCustomerDAO.deleteSchema(); + mockedFiles.verify(() -> Files.delete(filePath), times(1)); + } + } + + @Test + void givenFilePathNotExist_thenThrowException() { + when(file.exists()).thenReturn(false); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + assertThrows(CustomException.class, () -> flatFileCustomerDAO.deleteSchema()); + mockedFiles.verify(() -> Files.delete(filePath), times(0)); + } + } + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java new file mode 100644 index 000000000000..ce7def36e5bc --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -0,0 +1,300 @@ +/* + * 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.daofactory; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** Tests {@link H2CustomerDAO} */ +class H2CustomerDAOTest { + private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; + private static final String USER = "sa"; + private static final String PASS = ""; + private static final String CREATE_SCHEMA = + "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"; + private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + private final Customer existingCustomer = new Customer<>(1L, "Nguyen"); + private H2CustomerDAO h2CustomerDAO; + + @BeforeEach + void createSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); + var statement = connection.createStatement()) { + statement.execute(CREATE_SCHEMA); + } + } + + @AfterEach + void deleteSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); + var statement = connection.createStatement()) { + statement.execute(DROP_SCHEMA); + } + } + + /** Class test for scenario connect with datasource succeed */ + @Nested + class ConnectionSucceed { + + @BeforeEach + void setUp() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + h2CustomerDAO = new H2CustomerDAO(dataSource); + assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer)); + var customer = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customer.isPresent()); + assertEquals(customer.get().getName(), existingCustomer.getName()); + assertEquals(customer.get().getId(), existingCustomer.getId()); + } + + @Nested + class SaveCustomer { + @Test + void givenValidCustomer_whenSaveCustomer_thenAddSucceed() { + var customer = new Customer<>(2L, "Duc"); + assertDoesNotThrow(() -> h2CustomerDAO.save(customer)); + var customerInDb = h2CustomerDAO.findById(customer.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(customerInDb.get().getName(), customer.getName()); + assertEquals(customerInDb.get().getId(), customer.getId()); + List> customers = h2CustomerDAO.findAll(); + assertEquals(2, customers.size()); + } + + @Test + void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() { + var customer = new Customer<>(existingCustomer.getId(), "Duc"); + assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + } + } + + @Nested + class UpdateCustomer { + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc"); + assertDoesNotThrow(() -> h2CustomerDAO.update(customerUpdate)); + var customerInDb = h2CustomerDAO.findById(customerUpdate.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(customerInDb.get().getName(), customerUpdate.getName()); + } + + @Test + void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { + var customerUpdate = new Customer<>(100L, "Duc"); + var customerInDb = h2CustomerDAO.findById(customerUpdate.getId()); + assertTrue(customerInDb.isEmpty()); + assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate)); + } + + @Test + void givenNull_whenUpdateCustomer_thenThrowException() { + assertThrows(CustomException.class, () -> h2CustomerDAO.update(null)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + } + } + + @Nested + class DeleteCustomer { + @Test + void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { + assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId())); + var customerInDb = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customerInDb.isEmpty()); + List> customers = h2CustomerDAO.findAll(); + assertEquals(0, customers.size()); + } + + @Test + void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() { + var customerInDb = h2CustomerDAO.findById(100L); + assertTrue(customerInDb.isEmpty()); + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(100L)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + assertEquals(existingCustomer.getId(), customers.get(0).getId()); + } + + @Test + void givenNull_whenDeleteCustomer_thenThrowException() { + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(null)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + } + } + + @Nested + class FindAllCustomers { + @Test + void givenNonCustomerInDb_whenFindAllCustomer_thenReturnEmptyList() { + assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId())); + List> customers = h2CustomerDAO.findAll(); + assertEquals(0, customers.size()); + } + + @Test + void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() { + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + assertEquals(existingCustomer.getId(), customers.get(0).getId()); + } + } + + @Nested + class FindCustomerById { + @Test + void givenValidId_whenFindById_thenReturnCustomer() { + var customerInDb = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(existingCustomer.getName(), customerInDb.get().getName()); + assertEquals(existingCustomer.getId(), customerInDb.get().getId()); + } + + @Test + void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { + var customerNotExist = h2CustomerDAO.findById(100L); + assertTrue(customerNotExist.isEmpty()); + } + + @Test + void givenNull_whenFindById_thenThrowException() { + assertThrows(CustomException.class, () -> h2CustomerDAO.findById(null)); + } + } + + @Nested + class CreateSchema { + @Test + void whenCreateSchema_thenNotThrowException() { + assertDoesNotThrow(() -> h2CustomerDAO.createSchema()); + } + } + + @Nested + class DeleteSchema { + @Test + void whenDeleteSchema_thenNotThrowException() { + assertDoesNotThrow(() -> h2CustomerDAO.deleteSchema()); + } + } + } + + /** Class test with scenario connect with data source failed */ + @Nested + class ConnectionFailed { + private static final String EXCEPTION_CAUSE = "Connection not available"; + + @BeforeEach + void setUp() throws SQLException { + h2CustomerDAO = new H2CustomerDAO(mockedDataSource()); + } + + private DataSource mockedDataSource() throws SQLException { + var mockedDataSource = mock(DataSource.class); + var mockedConnection = mock(Connection.class); + var exception = new SQLException(EXCEPTION_CAUSE); + doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString()); + doThrow(exception).when(mockedConnection).createStatement(); + doReturn(mockedConnection).when(mockedDataSource).getConnection(); + return mockedDataSource; + } + + @Test + void givenValidCustomer_whenSaveCustomer_thenThrowException() { + var customer = new Customer<>(2L, "Duc"); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenThrowException() { + var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc"); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void givenValidId_whenDeleteCustomer_thenThrowException() { + Long idCustomer = existingCustomer.getId(); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(idCustomer)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenFindAll_thenThrowException() { + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::findAll); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenFindById_thenThrowException() { + Long idCustomer = existingCustomer.getId(); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.findById(idCustomer)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenCreateSchema_thenThrowException() { + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::createSchema); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenDeleteSchema_thenThrowException() { + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::deleteSchema); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java new file mode 100644 index 000000000000..c56e72c30389 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -0,0 +1,163 @@ +/* + * 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.daofactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import java.util.List; +import java.util.Optional; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Test; + +/** Tests {@link MongoCustomerDAO} */ +class MongoCustomerDAOTest { + MongoCollection customerCollection = mock(MongoCollection.class); + MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection); + + @Test + void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() { + Customer customer = new Customer<>(new ObjectId(), "John"); + mongoCustomerDAO.save(customer); + verify(customerCollection) + .insertOne( + argThat( + document -> + document.get("_id").equals(customer.getId()) + && document.get("name").equals(customer.getName()))); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + ObjectId customerId = new ObjectId(); + Customer customerUpdated = new Customer<>(customerId, "John"); + when(customerCollection.updateOne(any(Bson.class), any(Bson.class))) + .thenReturn(UpdateResult.acknowledged(1L, 1L, null)); + mongoCustomerDAO.update(customerUpdated); + verify(customerCollection) + .updateOne( + argThat( + (Bson filter) -> { + Document filterDoc = (Document) filter; + return filterDoc.getObjectId("_id").equals(customerId); + }), + argThat( + (Bson update) -> { + BsonDocument bsonDoc = update.toBsonDocument(); + BsonDocument setDoc = bsonDoc.getDocument("$set"); + return setDoc.getString("name").getValue().equals(customerUpdated.getName()); + })); + } + + @Test + void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() { + ObjectId customerId = new ObjectId(); + when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(1)); + mongoCustomerDAO.delete(customerId); + verify(customerCollection) + .deleteOne( + argThat( + (Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); + } + + @Test + void givenIdNotExist_whenDeleteCustomer_thenThrowException() { + ObjectId customerId = new ObjectId(); + when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0)); + assertThrows(CustomException.class, () -> mongoCustomerDAO.delete(customerId)); + verify(customerCollection) + .deleteOne( + argThat( + (Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); + } + + @Test + void findAll_thenReturnAllCustomers() { + FindIterable findIterable = mock(FindIterable.class); + MongoCursor cursor = mock(MongoCursor.class); + Document customerDoc1 = new Document("_id", new ObjectId()).append("name", "Duc"); + Document customerDoc2 = new Document("_id", new ObjectId()).append("name", "Thanh"); + when(customerCollection.find()).thenReturn(findIterable); + when(findIterable.iterator()).thenReturn(cursor); + when(cursor.hasNext()).thenReturn(true, true, false); + when(cursor.next()).thenReturn(customerDoc1, customerDoc2); + List> customerList = mongoCustomerDAO.findAll(); + assertEquals(2, customerList.size()); + verify(customerCollection).find(); + } + + @Test + void givenValidId_whenFindById_thenReturnCustomer() { + FindIterable findIterable = mock(FindIterable.class); + ObjectId customerId = new ObjectId(); + String customerName = "Duc"; + Document customerDoc = new Document("_id", customerId).append("name", customerName); + when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable); + when(findIterable.first()).thenReturn(customerDoc); + + Optional> customer = mongoCustomerDAO.findById(customerId); + assertTrue(customer.isPresent()); + assertEquals(customerId, customer.get().getId()); + assertEquals(customerName, customer.get().getName()); + } + + @Test + void givenNotExistingId_whenFindById_thenReturnEmpty() { + FindIterable findIterable = mock(FindIterable.class); + ObjectId customerId = new ObjectId(); + when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable); + when(findIterable.first()).thenReturn(null); + Optional> customer = mongoCustomerDAO.findById(customerId); + assertTrue(customer.isEmpty()); + verify(customerCollection).find(Filters.eq("_id", customerId)); + } + + @Test + void whenDeleteSchema_thenDeleteCollection() { + mongoCustomerDAO.deleteSchema(); + verify(customerCollection).drop(); + } +} 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 - -![Data Access Object](./etc/dao.png "Data Access Object") - ## 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 - -![Data Locality](./etc/data-locality.urm.png "Data Locality pattern class diagram") - ## 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/dependency-injection/README.md b/dependency-injection/README.md index c3fc2c15a977..c9a2848bdfde 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -118,10 +118,6 @@ The program output: 11:54:05.308 [main] INFO com.iluwatar.dependency.injection.Tobacco -- GuiceWizard smoking RivendellTobacco ``` -## Detailed Explanation of Dependency Injection Pattern with Real-World Examples - -![Dependency Injection](./etc/dependency-injection.png "Dependency Injection") - ## When to Use the Dependency Injection Pattern in Java * When aiming to reduce the coupling between classes and increase the modularity of the application. diff --git a/dynamic-proxy/pom.xml b/dynamic-proxy/pom.xml index 236723c52682..decbb24fd02d 100644 --- a/dynamic-proxy/pom.xml +++ b/dynamic-proxy/pom.xml @@ -46,7 +46,7 @@ com.fasterxml.jackson.core jackson-core - 2.18.2 + 2.19.0 com.fasterxml.jackson.core @@ -56,7 +56,7 @@ org.springframework spring-web - 7.0.0-M3 + 7.0.0-M4 org.junit.jupiter diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index 4cfd05d7adac..5660054d8195 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -50,7 +50,7 @@ com.fasterxml.jackson.core jackson-core - 2.18.2 + 2.19.0 com.fasterxml.jackson.core diff --git a/facade/README.md b/facade/README.md index b0dec75b801a..e9f3daf0cbd8 100644 --- a/facade/README.md +++ b/facade/README.md @@ -10,7 +10,7 @@ tag: - Code simplification - Decoupling - Encapsulation - - Gang Of Four + - Gang of Four - Interface - Object composition --- diff --git a/function-composition/README.md b/function-composition/README.md index 5bfef09d28c3..961aaa6504c1 100644 --- a/function-composition/README.md +++ b/function-composition/README.md @@ -7,21 +7,18 @@ language: en tag: - Code simplification - Composition - - Decoupling - Functional decomposition - - Lambda - Reusability --- ## Also known as * Function Chaining -* Function Pipelining -* Functional Composition +* Higher-Order Function Wrapping ## Intent of Function Composition Design Pattern -The Function Composition design pattern in Java enables the creation of complex functions by combining simpler ones. This enhances modular code and reusability, crucial for maintainable software development. +Combine multiple small functions into a single operation that executes them in a sequence, producing a new function as the result. ## Detailed Explanation of Function Composition Pattern with Real-World Examples @@ -45,24 +42,24 @@ Sequence diagram ## Programmatic Example of Function Composition Pattern in Java -In the functional programming paradigm, function composition is a powerful technique. For instance, in Java, you can use higher-order functions to compose operations like multiplying and squaring numbers. +In the functional programming paradigm, function composition is a powerful technique. For instance, in Java, you can use higher-order functions to combine operations like multiplying and squaring numbers. Using Java's functional interfaces, we can define simple functions and compose them. Here's how function composition works in Java. -Let's start with defining two simple functions. In this case, we have a function `timesTwo` that multiplies its input by 2, and a function `square` that squares its input. +Let's start with defining two simple functions. In this case, we have a function `timesTwo` that multiplies its input by 2, and a function `square` that squares its input: ```java Function timesTwo = x -> x * 2; Function square = x -> x * x; ``` -Next, we use the `FunctionComposer` class to compose these two functions into a new function. The `composeFunctions` method takes two functions as arguments and returns a new function that is the composition of the input functions. +Next, we use the `FunctionComposer` class to compose these two functions into a new function. The `composeFunctions` method takes two functions as arguments and returns a new function that is the composition of the input functions: ```java Function composedFunction = FunctionComposer.composeFunctions(timesTwo, square); ``` -Finally, we apply the composed function to an input value. In this case, we apply it to the number 3. The result is the square of the number 3 multiplied by 2, which is 36. +Finally, we apply the composed function to an input value. In this case, we apply it to the number 3. The result is the square of the number 3 multiplied by 2, which is 36: ```java public static void main(String[] args) { @@ -89,10 +86,9 @@ This example demonstrates how the Function Composition pattern can be used to cr Use the Function Composition pattern when: -* You want to create a pipeline of operations in Java. This enhances code clarity and quality by structuring complex logic into simpler, reusable components. -* You are working in a functional programming environment or a language that supports higher-order functions. -* When you want to avoid deep nesting of function calls and instead build a pipeline of operations. -* When aiming to promote immutability and side-effect-free functions in your design. +* When you want to build complex transformations by chaining smaller, reusable functions in Java. +* When the logic is best expressed through a series of operations that naturally feed one into another. +* When you want to reduce code duplication and improve readability by isolating each operation in its own function. ## Function Composition Pattern Java Tutorials @@ -101,31 +97,28 @@ Use the Function Composition pattern when: ## Real-World Applications of Function Composition Pattern in Java -* Stream processing in Java 8 and above -* Query builders in ORM libraries -* Middleware composition in web frameworks +* Java’s Stream API, where map and filter are composed for data transformations. +* Google Guava’s Function utilities. +* Apache Commons libraries that provide utilities for chaining functions. ## Benefits and Trade-offs of Function Composition Pattern Benefits: -* High reusability of composed functions. -* Increased modularity, making complex functions easier to understand and maintain. -* Flexible and dynamic creation of function pipelines at runtime. -* Enhances readability by structuring code in a linear, declarative manner. -* Facilitates easier testing of individual functions. +* Encourages highly modular and reusable code. +* Simplifies complex logic by breaking it down into smaller, testable units. +* Makes the code more expressive and easier to maintain. Trade-offs: -* Potentially higher complexity when debugging composed functions. -* Overhead from creating and managing multiple function objects in memory-intensive scenarios. -* May require a paradigm shift for developers unfamiliar with functional programming concepts. +* Excessive chaining can reduce readability if taken too far. +* May introduce performance overhead due to multiple function calls. +* Errors can be harder to trace in a deeply composed function pipeline. ## Related Java Design Patterns * [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/) - Both patterns allow processing to be broken down into a series of steps, but Functional Composition focuses on function composition rather than responsibility delegation. -* [Decorator](https://java-design-patterns.com/patterns/decorator/) - Similar in combining behaviors, but Decorator applies additional behavior to objects, while Functional Composition builds new functions. -* [Strategy](https://java-design-patterns.com/patterns/strategy/) - Provides interchangeable functions (strategies), which can be composed in Functional Composition. +* [Composite](https://java-design-patterns.com/patterns/composite/): Also deals with combining smaller components, though it is typically about object structure rather than function operations. ## References and Credits diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/App.java b/function-composition/src/main/java/com/iluwatar/function/composition/App.java index 206d8fe1cd70..0d90414a1930 100644 --- a/function-composition/src/main/java/com/iluwatar/function/composition/App.java +++ b/function-composition/src/main/java/com/iluwatar/function/composition/App.java @@ -25,9 +25,10 @@ package com.iluwatar.function.composition; import java.util.function.Function; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; /** Main application class to demonstrate the use of function composition. */ +@Slf4j public class App { /** @@ -36,7 +37,6 @@ public class App { * @param args command line arguments (not used) */ public static void main(String[] args) { - final var logger = LoggerFactory.getLogger(App.class); Function timesTwo = x -> x * 2; Function square = x -> x * x; @@ -44,6 +44,6 @@ public static void main(String[] args) { FunctionComposer.composeFunctions(timesTwo, square); int result = composedFunction.apply(3); - logger.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: " + result); + LOGGER.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: {}", result); } } diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java index 9438b85507a2..c440ae759a20 100644 --- a/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java +++ b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java @@ -32,6 +32,8 @@ */ public class FunctionComposer { + private FunctionComposer() {} + /** * Composes two functions where the output of the first function becomes the input of the second * function. diff --git a/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java index 451fb787b54c..dbc4f0903236 100644 --- a/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java +++ b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test; /** Test class for FunctionComposer. */ -public class FunctionComposerTest { +class FunctionComposerTest { /** Tests the composition of two functions. */ @Test diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java index 826a7bbb1d83..55cd522a5f46 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java @@ -27,35 +27,11 @@ import java.security.SecureRandom; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; -/** - * Leader/Followers pattern is a concurrency pattern. This pattern behaves like a taxi stand where - * one of the threads acts as leader thread which listens for event from event sources, - * de-multiplexes, dispatches and handles the event. It promotes the follower to be the new leader. - * When processing completes the thread joins the followers queue, if there are no followers then it - * becomes the leader and cycle repeats again. - * - *

    In this example, one of the workers becomes Leader and listens on the {@link TaskSet} for - * work. {@link TaskSet} basically acts as the source of input events for the {@link Worker}, who - * are spawned and controlled by the {@link WorkCenter} . When {@link Task} arrives then the leader - * takes the work and calls the {@link TaskHandler}. It also calls the {@link WorkCenter} to - * promotes one of the followers to be the new leader, who can then process the next work and so on. - * - *

    The pros for this pattern are: It enhances CPU cache affinity and eliminates unbound - * allocation and data buffer sharing between threads by reading the request into buffer space - * allocated on the stack of the leader or by using the Thread-Specific Storage pattern [22] to - * allocate memory. It minimizes locking overhead by not exchanging data between threads, thereby - * reducing thread synchronization. In bound handle/thread associations, the leader thread - * dispatches the event based on the I/O handle. It can minimize priority inversion because no extra - * queuing is introduced in the server. It does not require a context switch to handle each event, - * reducing the event dispatching latency. Note that promoting a follower thread to fulfill the - * leader role requires a context switch. Programming simplicity: The Leader/Followers pattern - * simplifies the programming of concurrency models where multiple threads can receive requests, - * process responses, and de-multiplex connections using a shared handle set. - */ +@Slf4j public class App { - /** The main method for the leader followers pattern. */ public static void main(String[] args) throws InterruptedException { var taskSet = new TaskSet(); var taskHandler = new TaskHandler(); @@ -64,18 +40,27 @@ public static void main(String[] args) throws InterruptedException { execute(workCenter, taskSet); } - /** Start the work, dispatch tasks and stop the thread pool at last. */ private static void execute(WorkCenter workCenter, TaskSet taskSet) throws InterruptedException { var workers = workCenter.getWorkers(); var exec = Executors.newFixedThreadPool(workers.size()); - workers.forEach(exec::submit); - Thread.sleep(1000); - addTasks(taskSet); - exec.awaitTermination(2, TimeUnit.SECONDS); - exec.shutdownNow(); + + try { + workers.forEach(exec::submit); + Thread.sleep(1000); + addTasks(taskSet); + exec.shutdown(); + boolean terminated = exec.awaitTermination(2, TimeUnit.SECONDS); + if (!terminated) { + LOGGER.warn("Executor did not terminate in the given time."); + exec.shutdownNow(); + } + } catch (InterruptedException e) { + exec.shutdownNow(); + Thread.currentThread().interrupt(); + throw e; + } } - /** Add tasks. */ private static void addTasks(TaskSet taskSet) throws InterruptedException { var rand = new SecureRandom(); for (var i = 0; i < 5; i++) { diff --git a/localization/fa/abstract-document/README.md b/localization/fa/abstract-document/README.md new file mode 100644 index 000000000000..7097ffc8b4ea --- /dev/null +++ b/localization/fa/abstract-document/README.md @@ -0,0 +1,243 @@ +--- +title: "الگوی Abstract Document در جاوا: ساده‌سازی مدیریت داده با انعطاف‌پذیری" +shortTitle: Abstract Document +description: "الگوی طراحی Abstract Document در جاوا را بررسی کنید. با هدف، توضیح، کاربرد، مزایا و نمونه‌های دنیای واقعی برای پیاده‌سازی ساختارهای داده‌ای پویا و انعطاف‌پذیر آشنا شوید." +category: Structural +language: fa +tag: + - Abstraction + - Decoupling + - Dynamic typing + - Encapsulation + - Extensibility + - Polymorphism +--- + +## هدف الگوی طراحی Abstract Document + +الگوی طراحی Abstract Document در جاوا یک الگوی طراحی ساختاری مهم است که راهی یکپارچه برای مدیریت ساختارهای داده‌ای سلسله‌مراتبی و درخت‌ی فراهم می‌کند، با تعریف یک واسط مشترک برای انواع مختلف اسناد. این الگو ساختار اصلی سند را از فرمت‌های خاص داده جدا می‌کند، که باعث به‌روزرسانی پویا و نگهداری ساده‌تر می‌شود. + +## توضیح دقیق الگوی Abstract Document با نمونه‌های دنیای واقعی + +الگوی طراحی Abstract Document در جاوا امکان مدیریت پویا ویژگی‌های پویا(غیر استاتیک) را فراهم می‌کند. این الگو از مفهوم traits استفاده می‌کند تا ایمنی نوع‌داده (type safety) را فراهم کرده و ویژگی‌های کلاس‌های مختلف را به مجموعه‌ای از واسط‌ها تفکیک کند. + +مثال دنیای واقعی + +> فرض کنید یک سیستم کتابخانه از الگوی Abstract Document در جاوا استفاده می‌کند، جایی که کتاب‌ها می‌توانند فرمت‌ها و ویژگی‌های متنوعی داشته باشند: کتاب‌های فیزیکی، کتاب‌های الکترونیکی، و کتاب‌های صوتی. هر فرمت ویژگی‌های خاص خود را دارد، مانند تعداد صفحات برای کتاب‌های فیزیکی، حجم فایل برای کتاب‌های الکترونیکی، و مدت‌زمان برای کتاب‌های صوتی. الگوی Abstract Document به سیستم کتابخانه اجازه می‌دهد تا این فرمت‌های متنوع را به‌صورت انعطاف‌پذیر مدیریت کند. با استفاده از این الگو، سیستم می‌تواند ویژگی‌ها را به‌صورت پویا ذخیره و بازیابی کند، بدون نیاز به ساختار سفت و سخت برای هر نوع کتاب، و این کار افزودن فرمت‌ها یا ویژگی‌های جدید را در آینده بدون تغییرات عمده در کد آسان می‌سازد. + +به زبان ساده + +> الگوی Abstract Document اجازه می‌دهد ویژگی‌هایی به اشیاء متصل شوند بدون اینکه خود آن اشیاء از آن اطلاع داشته باشند. + +ویکی‌پدیا می‌گوید + +> یک الگوی طراحی ساختاری شی‌ء‌گرا برای سازماندهی اشیاء در کلید-مقدارهایی با تایپ آزاد و ارائه داده‌ها از طریق نمای تایپ است. هدف این الگو دستیابی به انعطاف‌پذیری بالا بین اجزا در یک زبان strongly typed است که در آن بتوان ویژگی‌های جدیدی را به‌صورت پویا به ساختار درختی اشیاء اضافه کرد، بدون از دست دادن پشتیبانی از type safety. این الگو از traits برای جداسازی ویژگی‌های مختلف یک کلاس در اینترفیس‌های متفاوت استفاده می‌کند. + +نمودار کلاس + +![Abstract Document class diagram](./etc/abstract-document.png "Abstract Document class diagram") + +## مثال برنامه‌نویسی از الگوی Abstract Document در جاوا + +فرض کنید یک خودرو داریم که از قطعات مختلفی تشکیل شده است. اما نمی‌دانیم آیا این خودرو خاص واقعاً همه قطعات را دارد یا فقط برخی از آن‌ها. خودروهای ما پویا و بسیار انعطاف‌پذیر هستند. + +بیایید ابتدا کلاس‌های پایه `Document` و `AbstractDocument` را تعریف کنیم. این کلاس‌ها اساساً یک شیء را قادر می‌سازند تا یک نقشه از ویژگی‌ها و هر تعداد شیء فرزند را نگه دارد. + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + + // Other properties and methods... +} +``` +در ادامه، یک enum به نام Property و مجموعه‌ای از واسط‌ها برای type، price، model و parts تعریف می‌کنیم. این کار به ما اجازه می‌دهد یک واسط با ظاهر استاتیک برای کلاس Car ایجاد کنیم. +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} + +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} + +public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { + + public Part(Map properties) { + super(properties); + } +} +``` +اکنون آماده‌ایم تا کلاس Car را معرفی کنیم. +```java +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } +} +``` +و در نهایت، نحوه ساخت و استفاده از Car را در یک مثال کامل می‌بینید. +```java + public static void main(String[] args) { + LOGGER.info("Constructing parts and car"); + + var wheelProperties = Map.of( + Property.TYPE.toString(), "wheel", + Property.MODEL.toString(), "15C", + Property.PRICE.toString(), 100L); + + var doorProperties = Map.of( + Property.TYPE.toString(), "door", + Property.MODEL.toString(), "Lambo", + Property.PRICE.toString(), 300L); + + var carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + var car = new Car(carProperties); + + LOGGER.info("Here is our car:"); + LOGGER.info("-> model: {}", car.getModel().orElseThrow()); + LOGGER.info("-> price: {}", car.getPrice().orElseThrow()); + LOGGER.info("-> parts: "); + car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) + ); +} +``` +خروجی برنامه: +``` +07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car: +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> model: 300SL +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> price: 10000 +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> parts: +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100 +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300 +``` + + ### چه زمانی از الگوی Abstract Document در جاوا استفاده کنیم؟ + +الگوی طراحی Abstract Document به‌ویژه در سناریوهایی مفید است که نیاز به مدیریت انواع مختلفی از اسناد در جاوا وجود دارد که برخی ویژگی‌ها یا رفتارهای مشترک دارند، ولی ویژگی‌ها یا رفتارهای خاص خود را نیز دارند. در ادامه چند سناریوی مناسب برای این الگو آورده شده است: + +* سیستم‌های مدیریت محتوا (CMS): ممکن است انواع مختلفی از محتوا مانند مقاله، تصویر، ویدئو و... وجود داشته باشد. هر نوع محتوا ویژگی‌های مشترکی مثل تاریخ ایجاد، نویسنده و تگ‌ها دارد، ولی همچنین ویژگی‌های خاصی مثل ابعاد تصویر یا مدت‌زمان ویدئو. + +* سیستم‌های فایل: اگر یک سیستم فایل طراحی می‌کنید که باید انواع مختلف فایل مانند اسناد، تصاویر، فایل‌های صوتی و دایرکتوری‌ها را مدیریت کند، این الگو می‌تواند راهی یکپارچه برای دسترسی به ویژگی‌هایی مانند اندازه فایل یا تاریخ ایجاد، فراهم کند و در عین حال ویژگی‌های خاص هر نوع فایل را هم مدیریت کند. + +* سیستم‌های تجارت الکترونیک: یک پلتفرم فروش آنلاین ممکن است محصولات مختلفی داشته باشد مثل محصولات فیزیکی، فایل‌های دیجیتال، و اشتراک‌ها. این محصولات ویژگی‌هایی مثل نام، قیمت و توضیح را به اشتراک می‌گذارند، ولی ویژگی‌های خاصی هم دارند مانند وزن حمل برای محصولات فیزیکی یا لینک دانلود برای دیجیتال‌ها. + +* سیستم‌های سوابق پزشکی: در مراقبت سلامت، پرونده بیماران ممکن است داده‌های مختلفی مثل مشخصات فردی، سوابق پزشکی، نتایج آزمایش‌ها و نسخه‌ها را شامل شود. این الگو می‌تواند ویژگی‌های مشترک مثل شماره بیمار یا تاریخ تولد را مدیریت کند و هم‌زمان ویژگی‌های خاصی مثل نتایج آزمایش یا داروهای تجویزی را هم پوشش دهد. + +* مدیریت پیکربندی: هنگام کار با تنظیمات پیکربندی نرم‌افزار، ممکن است انواع مختلفی از عناصر پیکربندی وجود داشته باشد، هر یک با ویژگی‌های خاص خود. این الگو می‌تواند برای مدیریت این عناصر مفید باشد. + +* پلتفرم‌های آموزشی: سیستم‌های آموزشی ممکن است انواع مختلفی از منابع یادگیری داشته باشند مثل محتوای متنی، ویدیوها، آزمون‌ها و تمرین‌ها. ویژگی‌های مشترکی مثل عنوان، نویسنده و تاریخ انتشار وجود دارد، ولی ویژگی‌های خاصی مانند مدت ویدیو یا مهلت تحویل تمرین نیز ممکن است وجود داشته باشد. + +* ابزارهای مدیریت پروژه: در برنامه‌های مدیریت پروژه، ممکن است انواع مختلفی از وظایف مانند آیتم‌های to-do، milestoneها و issueها داشته باشید. این الگو می‌تواند برای مدیریت ویژگی‌های عمومی مانند نام وظیفه و مسئول آن استفاده شود و در عین حال ویژگی‌های خاص مانند تاریخ milestone یا اولویت issue را نیز پوشش دهد. + +* اسناد ساختار ویژگی‌های متنوع و در حال تحول دارند. + +* افزودن ویژگی‌های جدید به‌صورت پویا یک نیاز رایج است. + +* جداسازی دسترسی به داده از فرمت‌های خاص حیاتی است. + +* نگهداری‌پذیری و انعطاف‌پذیری برای کد اهمیت دارد. + +ایده اصلی پشت الگوی Abstract Document فراهم کردن روشی انعطاف‌پذیر و قابل توسعه برای مدیریت انواع مختلف اسناد یا موجودیت‌ها با ویژگی‌های مشترک و خاص است. با تعریف یک واسط مشترک و پیاده‌سازی آن در انواع مختلف اسناد، می‌توان به شیوه‌ای منظم و یکپارچه برای مدیریت ساختارهای پیچیده داده دست یافت. +### مزایا و معایب الگوی Abstract Document +

    +مزایا: + +* انعطاف‌پذیری: پشتیبانی از ساختارهای متنوع اسناد و ویژگی‌ها. + +* قابلیت توسعه: افزودن ویژگی‌های جدید بدون شکستن کد موجود. + +* نگهداری‌پذیری: ارتقاء کد تمیز و قابل تطبیق به‌واسطه جداسازی وظایف. + +* قابلیت استفاده مجدد: نمای دید تایپ‌شده باعث استفاده مجدد از کد برای دسترسی به نوع خاصی از ویژگی می‌شود. + +معایب: + +* پیچیدگی: نیاز به تعریف واسط‌ها و نماها، که باعث اضافه شدن سربار پیاده‌سازی می‌شود. + +* کارایی: ممکن است سربار کمی نسبت به دسترسی مستقیم به داده داشته باشد. +
    + +منابع و اعتبارها + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) + +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) + +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)] (https://amzn.to/49zRP4R) + +* [Patterns of Enterprise Application Architecture] (https://amzn.to/3WfKBPR) + +* [Abstract Document Pattern (Wikipedia)] (https://en.wikipedia.org/wiki/Abstract_Document_Pattern) + +* [Dealing with Properties (Martin Fowler)] (http://martinfowler.com/apsupp/properties.pdf) diff --git a/localization/fa/abstract-document/etc/abstract-document.png b/localization/fa/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/fa/abstract-document/etc/abstract-document.png differ diff --git a/localization/fa/abstract-factory/README.md b/localization/fa/abstract-factory/README.md new file mode 100644 index 000000000000..85dce3437ba4 --- /dev/null +++ b/localization/fa/abstract-factory/README.md @@ -0,0 +1,223 @@ +--- +title: "الگوی طراحی Abstract Factory در جاوا: مهارت در ایجاد شیء با ظرافت" +shortTitle: Abstract Factory +description: "با مثال‌های دنیای واقعی، دیاگرام‌های کلاس و آموزش‌ها، الگوی Abstract Factory را در جاوا بیاموزید. منظور، کاربرد، مزایا و نمونه‌های واقعی آن را درک کنید و دانش طراحی الگوهایتان را افزایش دهید." +category: Creational +language: fa +tag: + - Abstraction + - Decoupling + - Gang of Four + - Instantiation + - Polymorphism +--- + +## همچنین به این عنوان شناخته می‌شود + +* کیت + +## هدف از الگوی طراحی Abstract Factory + +الگوی Abstract Factory در جاوا یک واسط برای ایجاد خانواده‌هایی از اشیای مرتبط یا وابسته فراهم می‌کند بدون آنکه کلاس‌های مشخص آن‌ها را تعیین کند، و این کار موجب افزایش مدولاریتی و انعطاف‌پذیری در طراحی نرم‌افزار می‌شود. + +## توضیح دقیق الگوی Abstract Factory با مثال‌های دنیای واقعی + +مثال دنیای واقعی + +> تصور کنید یک شرکت مبلمان وجود دارد که از الگوی Abstract Factory در جاوا برای تولید سبک‌های مختلف مبلمان استفاده می‌کند: مدرن، ویکتوریایی و روستایی. هر سبک شامل محصولاتی مانند صندلی‌ها، میزها و کاناپه‌ها است. برای اطمینان از یکنواختی در هر سبک، شرکت از یک الگوی Abstract Factory استفاده می‌کند. +> +> در این سناریو، Abstract Factory یک واسط برای ایجاد خانواده‌هایی از اشیای مبلمان مرتبط (صندلی‌ها، میزها، کاناپه‌ها) است. هر Factory مشخص (کارخانه‌ی مبلمان مدرن، کارخانه‌ی مبلمان ویکتوریایی، کارخانه‌ی مبلمان روستایی) این واسط را پیاده‌سازی می‌کند و مجموعه‌ای از محصولات مطابق با سبک خاص ایجاد می‌کند. به این ترتیب، مشتریان می‌توانند یک مجموعه کامل از مبلمان مدرن یا ویکتوریایی ایجاد کنند بدون اینکه نگران جزئیات ساخت آن‌ها باشند. این باعث حفظ یکنواختی سبک می‌شود و امکان تغییر آسان سبک مبلمان را فراهم می‌کند. + +به زبان ساده + +> کارخانه‌ای از کارخانه‌ها؛ یک Factory یا کارخانه که مجموعه‌ای از کارخانه‌های مرتبط یا وابسته را بدون مشخص کردن کلاس‌های concrete آن‌ها گروه‌بندی می‌کند. + +ویکی‌پدیا می‌گوید + +> الگوی Abstract Factory راهی برای کپسوله کردن مجموعه‌ای از کارخانه‌های منحصر به فرد با یک تم مشترک بدون تعیین کلاس‌های concrete آن‌ها فراهم می‌کند. + +دیاگرام کلاس + +![Abstract Factory class diagram](./etc/abstract-factory.urm.png "Abstract Factory class diagram") + +## مثال برنامه‌نویسی از Abstract Factory در جاوا + +برای ایجاد یک پادشاهی با استفاده از الگوی Abstract Factory در جاوا، ما به اشیایی با یک تم مشترک نیاز داریم. یک پادشاهی اِلف (Elf) به یک پادشاه اِلف، یک قلعه‌ی اِلف، و یک ارتش اِلف نیاز دارد، در حالی که یک پادشاهی اورک (Orc) به یک پادشاه اورک، یک قلعه‌ی اورک، و یک ارتش اورک نیاز دارد. بین اشیای موجود در پادشاهی وابستگی وجود دارد. + +ترجمه‌ی مثال پادشاهی بالا. ابتدا ما برخی واسط‌ها و پیاده‌سازی‌هایی برای اشیای موجود در پادشاهی داریم: + +```java +public interface Castle { + String getDescription(); +} + +public interface King { + String getDescription(); +} + +public interface Army { + String getDescription(); +} + +// Elven implementations -> +public class ElfCastle implements Castle { + static final String DESCRIPTION = "This is the elven castle!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class ElfKing implements King { + static final String DESCRIPTION = "This is the elven king!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class ElfArmy implements Army { + static final String DESCRIPTION = "This is the elven Army!"; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +// Orcish implementations similarly -> ... +``` + +سپس واسط و پیاده‌سازی‌های کارخانه‌ی پادشاهی را داریم: + +```java +public interface KingdomFactory { + Castle createCastle(); + King createKing(); + Army createArmy(); +} + +public class ElfKingdomFactory implements KingdomFactory { + + @Override + public Castle createCastle() { + return new ElfCastle(); + } + + @Override + public King createKing() { + return new ElfKing(); + } + + @Override + public Army createArmy() { + return new ElfArmy(); + } +} + +// Orcish implementations similarly -> ... +``` + +اکنون می‌توانیم یک کارخانه برای کارخانه‌های مختلف پادشاهی طراحی کنیم. در این مثال، ما `FactoryMaker` را ایجاد کردیم که مسئول برگرداندن یک نمونه از `ElfKingdomFactory` یا `OrcKingdomFactory` است. مشتری می تواند از `FactoryMaker` برای ایجاد کارخانه concrete مورد نظر استفاده کند که به نوبه خود اشیاء concrete مختلف (مشتق شده از ارتش، پادشاه، قلعه) را تولید می‌کند. در این مثال، ما همچنین از یک enum برای پارامتری کردن نوع کارخانه پادشاهی که مشتری درخواست خواهد کرد استفاده کردیم. + +```java +public static class FactoryMaker { + + public enum KingdomType { + ELF, ORC + } + + public static KingdomFactory makeFactory(KingdomType type) { + return switch (type) { + case ELF -> new ElfKingdomFactory(); + case ORC -> new OrcKingdomFactory(); + }; + } +} +``` + +نمونه‌ای از تابع اصلی برنامه: + +```java +LOGGER.info("elf kingdom"); +createKingdom(Kingdom.FactoryMaker.KingdomType.ELF); +LOGGER.info(kingdom.getArmy().getDescription()); +LOGGER.info(kingdom.getCastle().getDescription()); +LOGGER.info(kingdom.getKing().getDescription()); + +LOGGER.info("orc kingdom"); +createKingdom(Kingdom.FactoryMaker.KingdomType.ORC); +LOGGER.info(kingdom.getArmy().getDescription()); +LOGGER.info(kingdom.getCastle().getDescription()); +LOGGER.info(kingdom.getKing().getDescription()); +``` + +خروجی برنامه: + +``` +07:35:46.340 [main] INFO com.iluwatar.abstractfactory.App -- elf kingdom +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven army! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven castle! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven king! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- orc kingdom +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc army! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc castle! +07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king! +``` + +## چه زمانی باید از الگوی Abstract Factory در جاوا استفاده کرد؟ + +* زمانی که سیستم باید مستقل از نحوه‌ی ایجاد، ترکیب و نمایش محصولاتش باشد. +* زمانی که نیاز به پیکربندی سیستم با یکی از چند خانواده محصول دارید. +* زمانی که باید خانواده‌ای از اشیای مرتبط با هم استفاده شوند، برای اطمینان از یکنواختی. +* زمانی که می‌خواهید کتابخانه‌ای از محصولات را فراهم کنید و فقط واسط‌های آن‌ها را نمایان کنید، نه پیاده‌سازی‌ها را. +* زمانی که طول عمر وابستگی‌ها کوتاه‌تر از مصرف‌کننده باشد. +* زمانی که نیاز به ساخت وابستگی‌ها با مقادیر یا پارامترهای زمان اجرا باشد. +* زمانی که باید در زمان اجرا انتخاب کنید که کدام خانواده از محصول را استفاده کنید. +* زمانی که افزودن محصولات یا خانواده های جدید نباید نیاز به تغییر در کد موجود داشته باشد. + +## آموزش‌های الگوی Abstract Factory در جاوا + +* [Abstract Factory Design Pattern in Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/abstract-factory-design-pattern-in-java) +* [Abstract Factory (Refactoring Guru)](https://refactoring.guru/design-patterns/abstract-factory) + +## مزایا و معایب الگوی Abstract Factory + +مزایا: + +> * انعطاف‌پذیری: به راحتی می‌توان خانواده‌های محصول را تعویض کرد بدون تغییر کد. + +> * جداسازی (Decoupling): کد مشتری فقط با واسط‌های انتزاعی کار می‌کند که باعث قابلیت حمل و نگهداری می‌شود. + +> * قابلیت استفاده مجدد: کارخانه‌های انتزاعی و محصولات امکان استفاده مجدد از مؤلفه‌ها را فراهم می‌کنند. + +> * قابلیت نگهداری: تغییرات در خانواده‌های محصول محلی شده و به‌روزرسانی را ساده‌تر می‌کند. + +معایب: + +> * پیچیدگی: تعریف واسط‌های انتزاعی و کارخانه‌های مشخص سربار اولیه ایجاد می‌کند. + +> * غیرمستقیم بودن: کد مشتری از طریق کارخانه‌ها با محصولات کار می‌کند که ممکن است شفافیت را کاهش دهد. + +## نمونه‌های واقعی استفاده از الگوی Abstract Factory در جاوا + +* کلاس‌های `LookAndFeel` در Java Swing برای ارائه گزینه های مختلف look-and-feel +* پیاده‌سازی‌های مختلف در Java AWT برای ایجاد اجزای مختلف GUI +* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) +* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) +* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) + +## الگوهای طراحی مرتبط با جاوا + +* الگوی [Factory Method](https://java-design-patterns.com/patterns/factory-method/): الگوی کارخانه‌ی انتزاعی از روش‌های کارخانه‌ای برای ایجاد محصولات استفاده می‌کند. +* الگوی [Singleton](https://java-design-patterns.com/patterns/singleton/): کلاس‌های کارخانه‌ی انتزاعی اغلب به صورت Singleton پیاده‌سازی می‌شوند. +* الگوی [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): مشابه کارخانه‌ی انتزاعی اما بر پیکربندی و مدیریت مجموعه‌ای از اشیای مرتبط تمرکز دارد. + +## منابع و ارجاعات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Design Patterns in Java](https://amzn.to/3Syw0vC) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) diff --git a/localization/fa/abstract-factory/etc/abstract-factory.urm.png b/localization/fa/abstract-factory/etc/abstract-factory.urm.png new file mode 100644 index 000000000000..836858a2c652 Binary files /dev/null and b/localization/fa/abstract-factory/etc/abstract-factory.urm.png differ diff --git a/localization/fa/active-object/README.md b/localization/fa/active-object/README.md new file mode 100644 index 000000000000..7c105b072aaa --- /dev/null +++ b/localization/fa/active-object/README.md @@ -0,0 +1,220 @@ +--- +title: "الگوی Active Object در جاوا: دستیابی به پردازش ناهمگام کارآمد" +shortTitle: Active Object +description: "با الگوی طراحی Active Object در جاوا آشنا شوید. این راهنما رفتار ناهمگام، هم‌زمانی (concurrency) و مثال‌های کاربردی برای بهبود عملکرد برنامه‌های جاوای شما را پوشش می‌دهد." +category: Concurrency +language: fa +tag: + - Asynchronous + - Decoupling + - Messaging + - Synchronization + - Thread management +--- + +## هدف الگوی طراحی Active Object + +الگوی Active Object روشی مطمئن برای پردازش ناهمگام در جاوا فراهم می‌کند که به پاسخ‌گو بودن برنامه‌ها و مدیریت مؤثر threadها کمک می‌کند. این الگو با محصور کردن وظایف در شیءهایی که هر کدام thread و صف پیام مخصوص خود را دارند، به این هدف می‌رسد. این جداسازی باعث می‌شود thread اصلی پاسخ‌گو باقی بماند و مشکلاتی مانند دست‌کاری مستقیم threadها یا دسترسی به وضعیت مشترک (shared state) به وجود نیاید. + +## توضیح کامل الگوی Active Object با مثال‌های دنیای واقعی + +مثال دنیای واقعی + +> تصور کنید در یک رستوران شلوغ، مشتریان سفارش خود را به گارسون‌ها می‌سپارند. به‌جای آنکه گارسون‌ها خودشان به آشپزخانه بروند و غذا را آماده کنند، سفارش‌ها را روی کاغذهایی می‌نویسند و به یک هماهنگ‌کننده می‌دهند. این هماهنگ‌کننده گروهی از سرآشپزها را مدیریت می‌کند که غذاها را به صورت ناهمگام آماده می‌کنند. هرگاه آشپزی آزاد شود، سفارش بعدی را از صف برمی‌دارد، غذا را آماده می‌کند و پس از آن گارسون را برای سرو غذا مطلع می‌سازد. +> +> در این قیاس، گارسون‌ها نماینده threadهای کلاینت هستند، هماهنگ‌کننده نقش زمان‌بند (scheduler) را ایفا می‌کند، و آشپزها نمایان‌گر اجرای متدها در threadهای جداگانه هستند. این ساختار باعث می‌شود گارسون‌ها بتوانند بدون مسدود شدن توسط فرایند آماده‌سازی غذا، سفارش‌های بیشتری دریافت کنند—درست مانند اینکه الگوی Active Object، فراخوانی متد را از اجرای آن جدا می‌کند تا هم‌زمانی (concurrency) را افزایش دهد. + +به زبان ساده + +> الگوی Active Object، اجرای متد را از فراخوانی آن جدا می‌کند تا در برنامه‌های چندریسمانی (multithreaded)، هم‌زمانی و پاسخ‌گویی بهتری فراهم شود. + +طبق تعریف ویکی‌پدیا + +> الگوی طراحی Active Object اجرای متد را از فراخوانی آن جدا می‌کند، برای شیءهایی که هرکدام thread کنترل مخصوص به خود را دارند. هدف، معرفی هم‌زمانی با استفاده از فراخوانی متد به‌صورت ناهمگام و یک زمان‌بند برای مدیریت درخواست‌ها است. +> +> این الگو شامل شش جزء کلیدی است: +> +> * یک proxy، که رابطی برای کلاینت‌ها با متدهای عمومی فراهم می‌کند. +> * یک interface که درخواست متد برای شیء فعال (active object) را تعریف می‌کند. +> * فهرستی از درخواست‌های معلق از سوی کلاینت‌ها. +> * یک زمان‌بند (scheduler) که تصمیم می‌گیرد کدام درخواست بعدی اجرا شود. +> * پیاده‌سازی متد شیء فعال. +> * یک callback یا متغیر برای اینکه کلاینت نتیجه را دریافت کند. + +نمودار توالی + +![Active Object sequence diagram](./etc/active-object-sequence-diagram.png) + +## مثال برنامه‌نویسی از Active Object در جاوا + +این بخش نحوه عملکرد الگوی Active Object در جاوا را توضیح می‌دهد و کاربرد آن در مدیریت وظایف ناهمگام و کنترل هم‌زمانی را نشان می‌دهد. + +اورک‌ها به دلیل ذات وحشی و غیرقابل مهارشان شناخته می‌شوند. به‌نظر می‌رسد هرکدام thread کنترل مخصوص خود را دارند. برای پیاده‌سازی یک موجود که دارای سازوکار thread مستقل خود باشد و فقط API را در اختیار قرار دهد نه اجرای داخلی را، می‌توان از الگوی Active Object استفاده کرد. + +```java +public abstract class ActiveCreature { + private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); + + private BlockingQueue requests; + + private String name; + + private Thread thread; + + public ActiveCreature(String name) { + this.name = name; + this.requests = new LinkedBlockingQueue(); + thread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + requests.take().run(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + } + } + ); + thread.start(); + } + + public void eat() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} is eating!", name()); + logger.info("{} has finished eating!", name()); + } + } + ); + } + + public void roam() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} has started to roam the wastelands.", name()); + } + } + ); + } + + public String name() { + return this.name; + } +} +``` + +می‌توان دید هر کلاسی که از ActiveCreature ارث‌بری کند، دارای thread کنترل مختص به خود برای فراخوانی و اجرای متدها خواهد بود. + +برای مثال، کلاس Orc: + +```java +public class Orc extends ActiveCreature { + + public Orc(String name) { + super(name); + } +} +``` +اکنون می‌توان چند موجود مانند orc ایجاد کرد، به آن‌ها دستور داد که بخورند و پرسه بزنند، و آن‌ها این دستورات را در thread مختص به خود اجرا می‌کنند: + +```java +public class App implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); + + private static final int NUM_CREATURES = 3; + + public static void main(String[] args) { + var app = new App(); + app.run(); + } + + @Override + public void run() { + List creatures = new ArrayList<>(); + try { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.add(new Orc(Orc.class.getSimpleName() + i)); + creatures.get(i).eat(); + creatures.get(i).roam(); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + Thread.currentThread().interrupt(); + } finally { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.get(i).kill(0); + } + } + } +} +``` + +خروجی برنامه: + +``` +09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating! +09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating! +09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands. +``` + +چه زمانی از الگوی Active Object در جاوا استفاده کنیم؟ + +از الگوی Active Object در جاوا استفاده کنید زمانی که: +> * نیاز دارید وظایف ناهمگام را بدون مسدود کردن thread اصلی مدیریت کنید تا عملکرد و پاسخ‌گویی بهتری داشته باشید. +> * نیاز به تعامل ناهمگام با منابع خارجی دارید. +> * می‌خواهید پاسخ‌گویی برنامه را افزایش دهید. +> * نیاز به مدیریت وظایف هم‌زمان به‌صورت ماژولار و قابل نگهداری دارید. + +آموزش‌های Java برای الگوی Active Object +> [Android and Java Concurrency: The Active Object Pattern (Douglas Schmidt)]((https://www.youtube.com/watch?v=Cd8t2u5Qmvc)) + +کاربردهای دنیای واقعی الگوی Active Object در جاوا + +> سیستم‌های معاملات بلادرنگ که درخواست‌ها به‌صورت ناهمگام پردازش می‌شوند. +> که در آن وظایف طولانی در پس‌زمینه اجرا می‌شوند بدون آنکه رابط کاربری را متوقف کنند. +> رابط‌های کاربری گرافیکی (GUI) +> برنامه‌نویسی بازی‌ها برای مدیریت به‌روزرسانی‌های هم‌زمان وضعیت بازی یا محاسبات هوش مصنوعی. + +مزایا و ملاحظات الگوی Active Object + +با مزایا و معایب استفاده از الگوی Active Object در جاوا آشنا شوید؛ از جمله بهبود ایمنی threadها و ملاحظات سربار احتمالی (overhead). + +> مزایا: +> +> * پاسخ‌گویی بهتر thread اصلی. +> * محصورسازی مسائل مربوط به هم‌زمانی درون شیءها. +> * بهبود سازمان‌دهی کد و قابلیت نگهداری. +> * فراهم‌سازی ایمنی در برابر شرایط بحرانی (thread safety) و جلوگیری از مشکلات وضعیت مشترک. + +> معایب: +> +> * سربار اضافی به دلیل ارسال پیام و مدیریت threadها. +> * برای تمام سناریوهای هم‌زمانی مناسب نیست. + +الگوهای طراحی مرتبط در جاوا + +> * [Command](https://java-design-patterns.com/patterns/command/): درخواست را به‌عنوان یک شیء کپسوله می‌کند، مشابه روشی که Active Object فراخوانی متد را کپسوله می‌کند. +> * [Promise](https://java-design-patterns.com/patterns/promise/): راهی برای دریافت نتیجه یک فراخوانی متد ناهمگام فراهم می‌کند؛ اغلب همراه با Active Object استفاده می‌شود. +> * [Proxy](https://java-design-patterns.com/patterns/proxy/): الگوی Active Object می‌تواند از proxy برای مدیریت فراخوانی‌های متد به‌صورت ناهمگام استفاده کند. + +منابع و مراجع + +> * [Design Patterns: Elements of Reusable Object Software](https://amzn.to/3HYqrBE) +> * [Concurrent Programming in Java: Design Principles and Patterns](https://amzn.to/498SRVq) +> * [Java Concurrency in Practice](https://amzn.to/4aRMruW) +> * [Learning Concurrent Programming in Scala](https://amzn.to/3UE07nV) +> * [Pattern Languages of Program Design 3](https://amzn.to/3OI1j61) +> * [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) + diff --git a/localization/fa/active-object/etc/active-object-sequence-diagram.png b/localization/fa/active-object/etc/active-object-sequence-diagram.png new file mode 100644 index 000000000000..b725d9b07b6d Binary files /dev/null and b/localization/fa/active-object/etc/active-object-sequence-diagram.png differ diff --git a/localization/fa/active-object/etc/active-object.urm.png b/localization/fa/active-object/etc/active-object.urm.png new file mode 100644 index 000000000000..c14f66144ee2 Binary files /dev/null and b/localization/fa/active-object/etc/active-object.urm.png differ diff --git a/localization/fa/active-object/etc/active-object.urm.puml b/localization/fa/active-object/etc/active-object.urm.puml new file mode 100644 index 000000000000..3fc3c8e1e921 --- /dev/null +++ b/localization/fa/active-object/etc/active-object.urm.puml @@ -0,0 +1,25 @@ +@startuml +package com.iluwatar.activeobject { + abstract class ActiveCreature { + - logger : Logger + - name : String + - requests : BlockingQueue + - thread : Thread + + ActiveCreature(name : String) + + eat() + + name() : String + + roam() + } + class App { + - creatures : Integer + - logger : Logger + + App() + + main(args : String[]) {static} + + run() + } + class Orc { + + Orc(name : String) + } +} +Orc --|> ActiveCreature +@enduml \ No newline at end of file diff --git a/localization/fa/factory/README.md b/localization/fa/factory/README.md new file mode 100644 index 000000000000..db41813464e3 --- /dev/null +++ b/localization/fa/factory/README.md @@ -0,0 +1,155 @@ +--- +title: "الگوی factory در جاوا: ساده‌سازی ایجاد اشیاء" +shortTitle: factory +description: "الگوی طراحی factory در جاوا را با مثال‌ها و توضیحات دقیق بیاموزید. یاد بگیرید چگونه با استفاده از الگوی factory کدی انعطاف‌پذیر و مقیاس‌پذیر ایجاد کنید. مناسب برای توسعه‌دهندگانی که به دنبال بهبود مهارت‌های طراحی شیءگرا هستند." +category: structural +language: fa +tag: + - Abstraction + - Encapsulation + - Gang of Four + - Instantiation + - Polymorphism +--- + +## هدف از الگوی طراحی factory + +الگوی طراحی factory در جاوا یک الگوی ساختاری است که یک رابط برای ایجاد یک شیء تعریف می‌کند اما به زیرکلاس‌ها اجازه می‌دهد نوع اشیائی را که ایجاد خواهند شد تغییر دهند. این الگو انعطاف‌پذیری و مقیاس‌پذیری را در کد شما ترویج می‌دهد. + +## توضیح دقیق الگوی factory با مثال‌های دنیای واقعی + +### مثال دنیای واقعی + +> تصور کنید در یک نانوایی انواع مختلف کیک‌ها با استفاده از الگوی طراحی factory ساخته می‌شوند. `CakeFactory` فرآیند ایجاد را مدیریت می‌کند و امکان افزودن آسان انواع جدید کیک‌ها را بدون تغییر در فرآیند اصلی فراهم می‌کند. `CakeFactory` می‌تواند انواع مختلفی از کیک‌ها مانند کیک شکلاتی، کیک وانیلی و کیک توت‌فرنگی تولید کند. به جای اینکه کارکنان نانوایی به صورت دستی مواد اولیه را انتخاب کنند و دستورالعمل‌های خاصی را برای هر نوع کیک دنبال کنند، از `CakeFactory` برای مدیریت فرآیند استفاده می‌کنند. مشتری فقط نوع کیک را درخواست می‌کند و `CakeFactory` مواد اولیه و دستورالعمل مناسب را تعیین کرده و نوع خاصی از کیک را ایجاد می‌کند. این تنظیم به نانوایی اجازه می‌دهد تا انواع جدید کیک‌ها را به راحتی اضافه کند بدون اینکه فرآیند اصلی تغییر کند، که این امر انعطاف‌پذیری و مقیاس‌پذیری را ترویج می‌دهد. + +### تعریف ویکی‌پدیا + +> الگوی factory یک شیء برای ایجاد اشیاء دیگر است – به طور رسمی، factory یک تابع یا متدی است که اشیاء با نمونه‌ها یا کلاس‌های مختلف را بازمی‌گرداند. + +### نمودار توالی + +![نمودار توالی factory](./etc/factory-sequence-diagram.png) + +## مثال برنامه‌نویسی از الگوی factory در جاوا + +تصور کنید یک کیمیاگر قصد دارد سکه‌هایی تولید کند. کیمیاگر باید بتواند هم سکه‌های طلا و هم سکه‌های مسی ایجاد کند و تغییر بین آن‌ها باید بدون تغییر در کد موجود امکان‌پذیر باشد. الگوی factory این امکان را فراهم می‌کند با ارائه یک متد ایجاد استاتیک که می‌توان آن را با پارامترهای مرتبط فراخوانی کرد. + +در جاوا، می‌توانید الگوی factory را با تعریف یک رابط `Coin` و پیاده‌سازی‌های آن `GoldCoin` و `CopperCoin` پیاده‌سازی کنید. کلاس `CoinFactory` یک متد استاتیک `getCoin` ارائه می‌دهد تا اشیاء سکه را بر اساس نوع ایجاد کند. + +```java +public interface Coin { + String getDescription(); +} +``` + +```java +public class GoldCoin implements Coin { + + static final String DESCRIPTION = "This is a gold coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +```java +public class CopperCoin implements Coin { + + static final String DESCRIPTION = "This is a copper coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +کد زیر انواع سکه‌هایی که پشتیبانی می‌شوند (`GoldCoin` و `CopperCoin`) را نشان می‌دهد. + +```java +@RequiredArgsConstructor +@Getter +public enum CoinType { + + COPPER(CopperCoin::new), + GOLD(GoldCoin::new); + + private final Supplier constructor; +} +``` + +سپس متد استاتیک `getCoin` برای ایجاد اشیاء سکه در کلاس factory `CoinFactory` کپسوله شده است. + +```java +public class CoinFactory { + + public static Coin getCoin(CoinType type) { + return type.getConstructor().get(); + } +} +``` + +اکنون، در کد کلاینت، می‌توانیم انواع مختلفی از سکه‌ها را با استفاده از کلاس factory تولید کنیم. + +```java +public static void main(String[] args) { + LOGGER.info("The alchemist begins his work."); + var coin1 = CoinFactory.getCoin(CoinType.COPPER); + var coin2 = CoinFactory.getCoin(CoinType.GOLD); + LOGGER.info(coin1.getDescription()); + LOGGER.info(coin2.getDescription()); +} +``` + +خروجی برنامه: + +``` +06:19:53.530 [main] INFO com.iluwatar.factory.App -- The alchemist begins his work. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a copper coin. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a gold coin. +``` + +## زمان استفاده از الگوی factory در جاوا + +* از الگوی طراحی factory در جاوا زمانی استفاده کنید که کلاس از قبل نوع دقیق و وابستگی‌های اشیائی که نیاز به ایجاد آن دارد را نمی‌داند. +* زمانی که یک متد یکی از چندین کلاس ممکن که یک کلاس والد مشترک دارند را بازمی‌گرداند و می‌خواهد منطق انتخاب شیء را کپسوله کند. +* این الگو معمولاً هنگام طراحی فریم‌ورک‌ها یا کتابخانه‌ها برای ارائه بهترین انعطاف‌پذیری و جداسازی از انواع کلاس‌های خاص استفاده می‌شود. + +## کاربردهای دنیای واقعی الگوی factory در جاوا + +> * [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +> * [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +> * [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +> * [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +> * این مورد [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) اشیاء singleton مختلف را بر اساس یک پروتکل بازمی‌گرداند +> * [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) +> * [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) و متدهای مشابه دیگر. +> +> * کتابخانه‌ی JavaFX از الگوهای factory برای ایجاد کنترل‌های مختلف رابط کاربری متناسب با نیازهای محیط کاربر استفاده می‌کند. + +## مزایا و معایب الگوی factory + +### مزایا: + +> * پیاده‌سازی الگوی factory در برنامه جاوای شما، وابستگی بین پیاده‌سازی و کلاس‌هایی که استفاده می‌کند را کاهش می‌دهد. +> * از [اصل Open/Closed](https://java-design-patterns.com/principles/#open-closed-principle) پشتیبانی می‌کند، زیرا سیستم می‌تواند انواع جدیدی را بدون تغییر کد موجود معرفی کند. + +### معایب: + +> * کد می‌تواند به دلیل معرفی چندین کلاس اضافی پیچیده‌تر شود. +> * استفاده بیش از حد می‌تواند کد را کمتر خوانا کند اگر پیچیدگی ایجاد اشیاء کم یا غیرضروری باشد. + +## الگوهای طراحی مرتبط با جاوا + +> * الگوی [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): می‌توان آن را نوعی factory در نظر گرفت که با گروهی از محصولات کار می‌کند. +> * الگوی [Singleton](https://java-design-patterns.com/patterns/singleton/): اغلب همراه با factory استفاده می‌شود تا اطمینان حاصل شود که یک کلاس تنها یک نمونه دارد. +> * الگوی [Builder](https://java-design-patterns.com/patterns/builder/): ساخت یک شیء پیچیده را از نمایش آن جدا می‌کند، مشابه نحوه‌ای که factoryها مدیریت نمونه‌سازی را انجام می‌دهند. +> * الگوی [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): یک factory از محتوای غیرقابل تغییر با رابط‌های builder و factory جداگانه است. + +## منابع و اعتبارات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) diff --git a/localization/fa/factory/etc/factory-sequence-diagram.png b/localization/fa/factory/etc/factory-sequence-diagram.png new file mode 100644 index 000000000000..260bea92f247 Binary files /dev/null and b/localization/fa/factory/etc/factory-sequence-diagram.png differ diff --git a/map-reduce/README.md b/map-reduce/README.md index 2b0b150033bd..d832616ba5a2 100644 --- a/map-reduce/README.md +++ b/map-reduce/README.md @@ -2,23 +2,26 @@ title: "MapReduce Pattern in Java" shortTitle: MapReduce description: "Learn the MapReduce pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." -category: Performance optimization +category: Functional language: en tag: + - Concurrency - Data processing - - Code simplification - - Delegation - - Performance + - Data transformation + - Functional decomposition + - Immutable + - Multithreading + - Scalability --- ## Also known as -* Split-Apply-Combine Strategy -* Scatter-Gather Pattern +* Map-Reduce +* Divide and Conquer for Data Processing ## Intent of Map Reduce Design Pattern -MapReduce aims to process and generate large datasets with a parallel, distributed algorithm on a cluster. It divides the workload into two main phases: Map and Reduce, allowing for efficient parallel processing of data. +To efficiently process large-scale datasets by dividing computation into two phases: map and reduce, which can be executed in parallel and distributed across multiple nodes. ## Detailed Explanation of Map Reduce Pattern with Real-World Examples @@ -32,19 +35,22 @@ In plain words Wikipedia says -> "MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster". -MapReduce consists of two main steps: -The "Map" step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. -The "Reduce" step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. -This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing. +> MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster. MapReduce consists of two main steps: +The Map step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. The Reduce step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing. + +Flowchart + +![MapReduce flowchart](./etc/mapreduce-flowchart.png) ## Programmatic Example of Map Reduce in Java ### 1. Map Phase (Splitting & Processing Data) -* The Mapper takes an input string, splits it into words, and counts occurrences. -* Output: A map {word → count} for each input line. +* Each input string is split into words, normalized, and counted. +* Output: A map `{word → count}` for each input string. + #### `Mapper.java` + ```java public class Mapper { public static Map map(String input) { @@ -60,13 +66,17 @@ public class Mapper { } } ``` + Example Input: ```"Hello world hello"``` Output: ```{hello=2, world=1}``` -### 2. Shuffle Phase (Grouping Data by Key) +### 2. Shuffle Phase – Grouping Words Across Inputs + +* Takes results from all mappers and groups values by word. +* Output: A map `{word → list of counts}`. -* The Shuffler collects key-value pairs from multiple mappers and groups values by key. #### `Shuffler.java` + ```java public class Shuffler { public static Map> shuffleAndSort(List> mapped) { @@ -81,14 +91,18 @@ public class Shuffler { } } ``` + Example Input: + ``` [ {"hello": 2, "world": 1}, {"hello": 1, "java": 1} ] ``` + Output: + ``` { "hello": [2, 1], @@ -97,10 +111,13 @@ Output: } ``` -### 3. Reduce Phase (Aggregating Results) +### 3. Reduce Phase – Aggregating Counts + +* Sums the list of counts for each word. +* Output: A sorted list of word counts in descending order. -* The Reducer sums up occurrences of each word. #### `Reducer.java` + ```java public class Reducer { public static List> reduce(Map> grouped) { @@ -115,7 +132,9 @@ public class Reducer { } } ``` + Example Input: + ``` { "hello": [2, 1], @@ -123,7 +142,9 @@ Example Input: "java": [1] } ``` + Output: + ``` [ {"hello": 3}, @@ -132,10 +153,12 @@ Output: ] ``` -### 4. Running the Full MapReduce Process +### 4. MapReduce Coordinator – Running the Whole Pipeline + +* Coordinates map, shuffle, and reduce phases. -* The MapReduce class coordinates the three steps. #### `MapReduce.java` + ```java public class MapReduce { public static List> mapReduce(List inputs) { @@ -151,10 +174,12 @@ public class MapReduce { } ``` -### 4. Main Execution (Calling MapReduce) +### 5. Main Execution – Example Usage + +* Runs the MapReduce process and prints results. -* The Main class executes the MapReduce pipeline and prints the final word count. #### `Main.java` + ```java public static void main(String[] args) { List inputs = Arrays.asList( @@ -171,6 +196,7 @@ public class MapReduce { ``` Output: + ``` hello: 4 world: 2 @@ -186,10 +212,11 @@ fun: 1 ## When to Use the Map Reduce Pattern in Java Use MapReduce when: -* Processing large datasets that don't fit into a single machine's memory -* Performing computations that can be parallelized -* Dealing with fault-tolerant and distributed computing scenarios -* Analyzing log files, web crawl data, or scientific data + +* When processing large datasets that can be broken into independent chunks. +* When data operations can be naturally divided into map (transformation) and reduce (aggregation) phases. +* When horizontal scalability and parallelization are essential, especially in distributed or big data environments. +* When leveraging Java-based distributed computing platforms like Hadoop or Spark. ## Map Reduce Pattern Java Tutorials @@ -200,32 +227,39 @@ Use MapReduce when: Benefits: -* Scalability: Can process vast amounts of data across multiple machines -* Fault-tolerance: Handles machine failures gracefully -* Simplicity: Abstracts complex distributed computing details +* Enables massive scalability by distributing processing across nodes. +* Encourages a functional style, promoting immutability and stateless operations. +* Simplifies complex data workflows by separating transformation (map) from aggregation (reduce). +* Fault-tolerant due to isolated, recoverable processing tasks. Trade-offs: -* Overhead: Not efficient for small datasets due to setup and coordination costs -* Limited flexibility: Not suitable for all types of computations or algorithms -* Latency: Batch-oriented nature may not be suitable for real-time processing needs +* Requires a suitable problem structure — not all tasks fit the map/reduce paradigm. +* Data shuffling between map and reduce phases can be performance-intensive. +* Higher complexity in debugging and optimizing distributed jobs. +* Intermediate I/O can become a bottleneck in large-scale operations. ## Real-World Applications of Map Reduce Pattern in Java -* Google's original implementation for indexing web pages -* Hadoop MapReduce for big data processing -* Log analysis in large-scale systems -* Genomic sequence analysis in bioinformatics +* Hadoop MapReduce: Java-based framework for distributed data processing using MapReduce. +* Apache Spark: Utilizes similar map and reduce transformations in its RDD and Dataset APIs. +* Elasticsearch: Uses MapReduce-style aggregation pipelines for querying distributed data. +* Google Bigtable: Underlying storage engine influenced by MapReduce principles. +* MongoDB Aggregation Framework: Conceptually applies MapReduce in its data pipelines. ## Related Java Design Patterns -* Chaining Pattern -* Master-Worker Pattern -* Pipeline Pattern +* [Master-Worker](https://java-design-patterns.com/patterns/master-worker/): Similar distribution of tasks among workers, with a master coordinating job execution. +* [Pipeline](https://java-design-patterns.com/patterns/pipeline/): Can be used to chain multiple MapReduce operations into staged transformations. +* [Iterator](https://java-design-patterns.com/patterns/iterator/): Often used under the hood to process input streams lazily in map and reduce steps. ## References and Credits -* [What is MapReduce](https://www.ibm.com/think/topics/mapreduce) -* [Wy MapReduce is not dead](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) -* [Scalabe Distributed Data Processing Solutions](https://tcpp.cs.gsu.edu/curriculum/?q=system%2Ffiles%2Fch07.pdf) +* [Big Data: Principles and Paradigms](https://amzn.to/3RJIGPZ) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3E6VhtD) +* [Hadoop: The Definitive Guide: Storage and Analysis at Internet Scale](https://amzn.to/4ij2y7F) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) * [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) +* [Programming Pig: Dataflow Scripting with Hadoop](https://amzn.to/4cAU36K) +* [What is MapReduce (IBM)](https://www.ibm.com/think/topics/mapreduce) +* [Why MapReduce is not dead (Codemotion)](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) diff --git a/map-reduce/etc/mapreduce-flowchart.png b/map-reduce/etc/mapreduce-flowchart.png new file mode 100644 index 000000000000..dade8f7ccdf2 Binary files /dev/null and b/map-reduce/etc/mapreduce-flowchart.png differ diff --git a/mediator/README.md b/mediator/README.md index f9650d3f1c13..bcf64f022b87 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -6,7 +6,7 @@ category: Behavioral language: en tag: - Decoupling - - Gang Of Four + - Gang of Four - Messaging - Object composition --- diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java index 7d2275234ec1..3b108e913850 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java @@ -35,7 +35,7 @@ public class User { private Integer id; private String username; - private String password; + private char[] password; public User() {} @@ -47,6 +47,13 @@ public User() {} */ public User(String username, String password) { this.username = username; - this.password = password; + this.password = password != null ? password.toCharArray() : null; + } + + /** Secure password cleanup. Call this after authentication is complete. */ + public void clearPassword() { + if (password != null) { + java.util.Arrays.fill(password, '\0'); + } } } diff --git a/microservices-api-gateway/README.md b/microservices-api-gateway/README.md index 4f03560b92ef..ff5d4099376f 100644 --- a/microservices-api-gateway/README.md +++ b/microservices-api-gateway/README.md @@ -1,6 +1,6 @@ --- title: "Microservices API Gateway Pattern in Java: Simplifying Service Access with a Unified Endpoint" -shortTitle: Microservice API Gateway +shortTitle: Microservices API Gateway description: "Learn how the API Gateway pattern simplifies client-side development, enhances security, and optimizes communication in microservices architecture. Explore examples, benefits, and best practices." category: Integration language: en diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java index 4e618b2aa0a1..2ebef5bd39b3 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java @@ -45,6 +45,7 @@ public class ImageClientImpl implements ImageClient { */ @Override public String getImagePath() { + var httpClient = HttpClient.newHttpClient(); var httpGet = HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:50005/image-path")).build(); diff --git a/microservices-client-side-ui-composition/README.md b/microservices-client-side-ui-composition/README.md index c11b7990308c..d2b1ae7fd158 100644 --- a/microservices-client-side-ui-composition/README.md +++ b/microservices-client-side-ui-composition/README.md @@ -1,49 +1,53 @@ --- -title: "Client-Side UI Composition Pattern: Assembling Modular UIs in Microservices Architecture" -shortTitle: Client-Side UI Composition Pattern +title: "Microservices Client-Side UI Composition Pattern In Java: Assembling Modular UIs in Microservices Architecture" +shortTitle: Microservices Client-Side UI Composition description: "Learn how the Client-Side UI Composition pattern allows the assembly of modular UIs on the client side, enabling independent teams to develop, deploy, and scale UI components in a microservices architecture. Discover the benefits, implementation examples, and best practices." -category: User Interface +category: Architectural language: en tag: - - Micro Frontends - - API Gateway - - Asynchronous Data Fetching - - UI Integration - - Microservices Architecture - - Scalability + - Client-server + - Cloud distributed + - Composition + - Decoupling + - Integration + - Microservices + - Modularity + - Scalability + - Web development --- -## **Intent of Client-Side UI Composition Design Pattern** +## Intent of Client-Side UI Composition Design Pattern -The Client-Side UI Composition Pattern allows the assembly of UIs on the client side by composing independent UI components (Micro Frontends). Each component is developed, tested, and deployed independently by separate teams, ensuring flexibility and scalability in microservices architecture. +Compose user interface from independently deployable microservices on the client side for greater flexibility and decoupling. ---- - -## **Also Known As** +## Also Known As -- Micro Frontends -- Modular UI Assembly -- Client-Side Integration +* UI Aggregator +* Frontend-Driven Composition ---- +## Detailed Explanation of Client-Side UI Composition Pattern with Real-World Examples -## **Detailed Explanation of Client-Side UI Composition Pattern with Real-World Examples** +Real-world Example -### **Real-world Example** > In a SaaS dashboard, a client-side composition pattern enables various independent modules like “Billing,” “Reports,” and “Account Settings” to be developed and deployed by separate teams. These modules are composed into a unified interface for the user, with each module independently fetching data from its respective microservice. -### **In Plain Words** +In Plain Words + > The Client-Side UI Composition pattern breaks down the user interface into smaller, independent parts that can be developed, maintained, and scaled separately by different teams. -### **Wikipedia says** ->UI composition refers to the practice of building a user interface from modular components, each responsible for fetching its own data and rendering its own content. This approach enables faster development cycles, easier maintenance, and better scalability in large systems. ---- +Wikipedia says -## **Programmatic Example of Client-Side UI Composition in JavaScript** +> UI composition refers to the practice of building a user interface from modular components, each responsible for fetching its own data and rendering its own content. This approach enables faster development cycles, easier maintenance, and better scalability in large systems. -In this example, an e-commerce platform composes its frontend by integrating three independent modules: **CartService**, **ProductService**, and **OrderService**. Each module is served by a microservice and fetched on the client side through an API Gateway. +Sequence diagram -`ApiGateway` Implementation +![Client-Side UI Composition sequence diagram](./etc/microservices-client-side-ui-composition-sequence-diagram.png) + +## Programmatic Example of Client-Side UI Composition in Java + +This example composes an e-commerce frontend by integrating three independent modules. Each module is served by a microservice and fetched on the client side through an API Gateway. + +### `ApiGateway` Implementation ```java public class ApiGateway { @@ -65,13 +69,16 @@ public class ApiGateway { ``` -`FrontendComponent` Implementation +### `FrontendComponent` Interface + ```java public interface FrontendComponent { String fetchData(Map params); } ``` -## Example components + +### Example Components + ```java public class ProductComponent implements FrontendComponent { @Override @@ -87,50 +94,53 @@ public class CartComponent implements FrontendComponent { } } ``` -This approach dynamically assembles different UI components based on the route provided in the client-side request. Each component fetches its data asynchronously and renders it within the main interface. ---- +This approach dynamically assembles UI components based on the route in the client-side request. Each component fetches its data asynchronously and renders it within the main interface. -## **When to Use the Client-Side UI Composition Pattern** +## When to Use the Client-Side UI Composition Pattern -- When you have a microservices architecture where multiple teams develop different parts of the frontend. -- When you need to scale and deploy different UI modules independently. -- When you want to integrate multiple data sources or services into a cohesive frontend. - ---- +* When each microservice must present its own UI components +* When frequent independent deployments of UI features are required +* When teams own end-to-end functionality, including front-end modules +* When Java-based backends provide separate APIs for direct UI consumption -## **Client-Side UI Composition Pattern JavaScript Tutorials** +## Client-Side UI Composition Pattern Tutorials - [Micro Frontends in Action (O'Reilly)](https://www.oreilly.com/library/view/micro-frontends-in/9781617296873/) - [Micro Frontends with React (ThoughtWorks)](https://www.thoughtworks.com/insights/articles/building-micro-frontends-using-react) - [API Gateway in Microservices (Spring Cloud)](https://spring.io/guides/gs/gateway/) ---- +## Real-World Applications of Client-Side UI Composition Pattern in Java -## **Benefits and Trade-offs of Client-Side UI Composition Pattern** +* Large-scale e-commerce portals with microservices powering modular pages +* SaaS platforms requiring rapid iteration of front-end features +* Java-based microservice architectures using frameworks like Spring Boot for modular UI components -### **Benefits**: -- **Modularity**: Each UI component is independent and can be developed by separate teams. -- **Scalability**: Micro Frontends allow for independent deployment and scaling of each component. -- **Flexibility**: Teams can choose different technologies to build components, offering flexibility in development. -- **Asynchronous Data Fetching**: Components can load data individually, improving performance. +## Benefits and Trade-offs of Client-Side UI Composition Pattern -### **Trade-offs**: -- **Increased Complexity**: Managing multiple micro frontends can increase overall system complexity. -- **Client-Side Performance**: Depending on the number of micro frontends, it may introduce a performance overhead due to multiple asynchronous requests. -- **Integration Overhead**: Client-side integration logic can become complex as the number of components grows. +Benefits: ---- +* Facilitates independent deployment cycles for UI features +* Reduces overall coupling and fosters autonomy among teams +* Enables polyglot front-end development -## **Related Design Patterns** +Trade-offs: -- [Microservices API Gateway Pattern](https://java-design-patterns.com/patterns/microservices-api-gateway/) – API Gateway serves as a routing mechanism for client-side UI requests. -- [Backend for Frontend (BFF)](https://microservices.io/patterns/apigateway.html) – BFF pattern helps build custom backends for different UI experiences. +* Increases client complexity for rendering and data aggregation +* Can cause performance challenges with multiple service calls +* Demands consistent UX guidelines across diverse microservices ---- +## Related Design Patterns + +* Backend for Frontend (BFF): Provides custom endpoints for specific UIs +* Micro Frontends: Splits the front-end into smaller, individually deployable fragments +* [Microservices API Gateway Pattern](https://java-design-patterns.com/patterns/microservices-api-gateway/): API Gateway serves as a routing mechanism for client-side UI requests. -## **References and Credits** +## References and Credits -- [Micro Frontends Architecture (Microfrontends.org)](https://micro-frontends.org/) -- [Building Microservices with Micro Frontends](https://martinfowler.com/articles/micro-frontends.html) -- [Client-Side UI Composition (Microservices.io)](https://microservices.io/patterns/client-side-ui-composition.html) +* [Building Microservices](https://amzn.to/3UACtrU) +* [Building Microservices with Micro Frontends (Martin Fowler)](https://martinfowler.com/articles/micro-frontends.html) +* [Client-Side UI Composition (Microservices.io)](https://microservices.io/patterns/client-side-ui-composition.html) +* [Cloud Native Java: Designing Resilient Systems with Spring Boot, Spring Cloud, and Cloud Foundry](https://amzn.to/44vDTat) +* [Micro Frontends Architecture (Microfrontends.org)](https://micro-frontends.org/) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) diff --git a/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png new file mode 100644 index 000000000000..cb0dd57ae2f5 Binary files /dev/null and b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png differ diff --git a/microservices-distributed-tracing/README.md b/microservices-distributed-tracing/README.md index 14b34726fc10..4fc271a68824 100644 --- a/microservices-distributed-tracing/README.md +++ b/microservices-distributed-tracing/README.md @@ -1,32 +1,32 @@ --- -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: Integration +category: Architectural language: en tag: - - Distributed tracing - - Microservices architecture - - Service communication - - Performance monitoring - - Scalability - - Observability + - Cloud distributed + - Microservices + - Resilience + - Observability + - Scalability + - System health --- ## Intent of Microservices Distributed Tracing Design Pattern -Distributed tracing aims to monitor and track requests as they flow through different services in a microservices architecture, providing insights into performance, dependencies, and failures. +Provide a mechanism to trace and correlate requests as they traverse multiple microservices in a distributed system, enabling end-to-end visibility and easier troubleshooting. ## Also known as * Distributed Request Tracing -* End-to-End Tracing +* End-to-End Microservice Tracing ## Detailed Explanation of Microservices Distributed Tracing Pattern with Real-World Examples Real-world example -> In an e-commerce platform, distributed tracing is used to track a customer's request from the moment they add an item to the cart until the order is processed and shipped. This helps in identifying bottlenecks, errors, and latency issues across different services. +> Imagine an online food delivery platform where one microservice handles user orders, another manages restaurant menus, and yet another coordinates courier assignments. When a user places an order, the request travels through all three services in sequence. By implementing distributed tracing, each service attaches a trace identifier to its logs. This allows the operations team to follow the journey of a single order across the entire pipeline, identify any delays along the way, and quickly pinpoint which service is causing the bottleneck or experiencing an error. In plain words @@ -36,10 +36,13 @@ Wikipedia says > Tracing in software engineering refers to the process of capturing and recording information about the execution of a software program. This information is typically used by programmers for debugging purposes, and additionally, depending on the type and detail of information contained in a trace log, by experienced system administrators or technical-support personnel and by software monitoring tools to diagnose common problems with software. -## Programmatic Example of Microservices Distributed Tracing in Java +Sequence diagram + +![Microservices Distributed Tracing Sequence Diagram](./etc/microservices-distributed-tracing-sequence-diagram.png) +## Programmatic Example of Microservices Distributed Tracing in Java -This implementation shows how an e-commerce platform's `OrderService` interacts with both `PaymentService` and `ProductService`. When a customer places an order, the `OrderService` calls the `PaymentService` to process the payment and the `ProductService` to check the product inventory. Distributed tracing logs are generated for each of these interactions and can be viewed in the Zipkin interface to monitor the flow and performance of requests across these services. +This implementation shows how an e-commerce platform's `OrderService` interacts with both `PaymentService` and `ProductService`. When a customer places an order, the `OrderService` calls the `PaymentService` to process the payment and the `ProductService` to check the product inventory. By adding distributed trace instrumentation (usually via libraries like Spring Cloud Sleuth or OpenTelemetry), each service attaches trace context to outgoing requests and logs. These logs can then be viewed in the Zipkin interface (or other tracing tools, such as Jaeger) to observe the entire flow of the request and quickly identify any performance bottlenecks or failures across multiple services. Here's the `Order microservice` implementation. @@ -155,12 +158,13 @@ public class ProductController { } ``` -## When to Use the Microservices Distributed Tracing Pattern in Java +In this example, each microservice would typically be configured with tracing libraries (like Sleuth or OpenTelemetry). The trace context is propagated via HTTP headers, enabling the logs and metrics for each service call to be grouped together and visualized in Zipkin or another compatible tool. This ensures complete end-to-end visibility into each request’s journey. -* When you have a microservices architecture and need to monitor the flow of requests across multiple services. -* When troubleshooting performance issues or errors in a distributed system. -* When you need to gain insights into system bottlenecks and optimize overall performance. +## When to Use the Microservices Distributed Tracing Pattern in Java +* When multiple services form a single user request path and debugging failures requires visibility across service boundaries. +* When monitoring or diagnosing performance bottlenecks is critical in a multi-service environment. +* When correlation of logs and metrics from independent services is needed to understand overall system health. ## Microservices Distributed Tracing Pattern Java Tutorials @@ -168,12 +172,18 @@ public class ProductController { * [Reactive Observability (Spring Academy)](https://spring.academy/guides/microservices-observability-reactive-spring-boot-3) * [Spring Cloud – Tracing Services with Zipkin (Baeldung)](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) +## Real-World Applications of Microservices Distributed Tracing Pattern in Java + +* OpenTelemetry for tracing instrumentation in Java services. +* Spring Cloud Sleuth for automatic tracing in Spring Boot microservices. +* Jaeger and Zipkin for collecting and visualizing distributed traces in Java-based systems. + ## Benefits and Trade-offs of Microservices Distributed Tracing Pattern Benefits: -* Provides end-to-end visibility into requests. -* Helps in identifying performance bottlenecks. +* Centralized insight into request paths across services, reducing time to diagnose issues. +* Improved observability enables proactive identification of system bottlenecks. * Aids in debugging and troubleshooting complex systems. Trade-offs: @@ -182,20 +192,17 @@ Trade-offs: * Requires additional infrastructure (e.g., Zipkin, Jaeger) for collecting and visualizing traces. * Can become complex to manage in large-scale systems. -## Real-World Applications of Microservices Distributed Tracing Pattern in Java - -* Monitoring and troubleshooting e-commerce platforms. -* Performance monitoring in financial transaction systems. -* Observability in large-scale SaaS applications. - ## Related Java Design Patterns -* [Log Aggregation Microservice](https://java-design-patterns.com/patterns/microservices-log-aggregation/) - Distributed tracing works well in conjunction with log aggregation to provide comprehensive observability and troubleshooting capabilities. -* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) - Distributed tracing can be used alongside the Circuit Breaker pattern to monitor and handle failures gracefully, preventing cascading failures in microservices. -* [API Gateway Microservice](https://java-design-patterns.com/patterns/microservices-api-gateway/) - The API Gateway pattern can be integrated with distributed tracing to provide a single entry point for tracing requests across multiple microservices. +* [API Gateway Microservice](https://java-design-patterns.com/patterns/microservices-api-gateway/): Acts as an entry point to microservices and can propagate trace information to downstream services. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Distributed tracing can be used alongside the Circuit Breaker pattern to monitor and handle failures gracefully, preventing cascading failures in microservices. +* [Log Aggregation Microservice](https://java-design-patterns.com/patterns/microservices-log-aggregation/): Distributed tracing works well in conjunction with log aggregation to provide comprehensive observability and troubleshooting capabilities. +* [Saga](https://java-design-patterns.com/patterns/saga/): Orchestrates distributed transactions, which benefit from trace identifiers to correlate steps across services. ## References and Credits * [Building Microservices](https://amzn.to/3UACtrU) -* [OpenTelemetry Documentation](https://opentelemetry.io/docs/) * [Distributed tracing (microservices.io)](https://microservices.io/patterns/observability/distributed-tracing.html) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [OpenTelemetry Documentation](https://opentelemetry.io/docs/) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/3Uul4kF) diff --git a/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png b/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png new file mode 100644 index 000000000000..7840697100da Binary files /dev/null and b/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png differ 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-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java index 517a1b20b088..b1053be8f137 100644 --- a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java +++ b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java @@ -36,7 +36,7 @@ class ProductControllerTest { private ProductController productController; @BeforeEach - public void setUp() { + void setUp() { productController = new ProductController(); } diff --git a/microservices-idempotent-consumer/README.md b/microservices-idempotent-consumer/README.md index 794eb6e644aa..738bbcda5a3d 100644 --- a/microservices-idempotent-consumer/README.md +++ b/microservices-idempotent-consumer/README.md @@ -1,56 +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: Messaging language: en tag: - - Messaging - - Fault tolerance + - Asynchronous + - Decoupling - Event-driven - - Reliability + - 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. +![Microservices Idempotent Consumer flowchart](./etc/microservices-idempotent-consumer-flowchart.png) -## 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 @@ -79,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 { @@ -105,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()); @@ -130,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/microservices-self-registration/README.md b/microservices-self-registration/README.md new file mode 100644 index 000000000000..bfc837817c57 --- /dev/null +++ b/microservices-self-registration/README.md @@ -0,0 +1,236 @@ +--- +title: "Microservices Self-Registration Pattern in Java with Spring Boot and Eureka" +shortTitle: Microservices Pattern - Self-Registration +description: "Dynamically register and discover Java microservices using Spring Boot and Eureka for resilient, scalable communication." +category: Service Discovery +language: en +tag: + - Microservices + - Self-Registration + - Service Discovery + - Eureka + - Spring Boot + - Spring Cloud + - Java + - Dynamic Configuration + - Resilience +--- + +## Intent of Microservices Self-Registration Pattern + +The intent of the Self-Registration pattern is to enable microservices to automatically announce their presence and location to a central registry (like Eureka) upon startup, simplifying service discovery and allowing other services to find and communicate with them without manual configuration or hardcoded addresses. This promotes dynamic and resilient microservices architectures. + +## What's in the Project + +This project demonstrates the Microservices Self-Registration pattern using Java, Spring Boot (version 3.4.4), and Eureka for service discovery. It consists of three main components: a Eureka Server and two simple microservices, a Greeting Service and a Context Service, which discover and communicate with each other. + +### Project Structure +* **`eureka-server`:** The central service registry where microservices register themselves. +* **`greeting-service`:** A simple microservice that provides a greeting. +* **`context-service`:** A microservice that consumes the greeting from the Greeting Service and adds context. + + The **Eureka Server** acts as the discovery service. Microservices register themselves with the Eureka Server, providing their network location. + + package com.example.eurekaserver; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + + @SpringBootApplication + @EnableEurekaServer + public class EurekaServerApplication { + + public static void main(String[] args) { + SpringApplication.run(EurekaServerApplication.class, args); + } + } + + The **Greeting Service** is a simple microservice that exposes an endpoint to retrieve a greeting. + + package com.example.greetingservice; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + + @SpringBootApplication + @EnableDiscoveryClient + public class GreetingServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(GreetingServiceApplication.class, args); + } + } + + Greeting Controller + + package com.example.greetingservice.controller; + + import org.springframework.web.bind.annotation.GetMapping; + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class GreetingController { + + @GetMapping("/greeting") + public String getGreeting() { + return "Hello"; + } + } + +The **Context Service** consumes the greeting from the Greeting Service using OpenFeign and adds contextual information. + + package com.example.contextservice; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + import org.springframework.cloud.openfeign.EnableFeignClients; + + @SpringBootApplication + @EnableDiscoveryClient + @EnableFeignClients + public class ContextServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(ContextServiceApplication.class, args); + } + } + + Feign Client : Spring Cloud OpenFeign is a declarative HTTP client that makes it easier to consume RESTful web services in your Spring Cloud applications. Instead of writing the boilerplate code for making HTTP requests, you simply declare interface with annotations that describe the web service you want to consume. + + package com.example.contextservice.client; + + import org.springframework.cloud.openfeign.FeignClient; + import org.springframework.web.bind.annotation.GetMapping; + + @FeignClient(name = "greeting-service") + public interface GreetingServiceClient { + + @GetMapping("/greeting") + String getGreeting(); + } + + Context Controller + + package com.example.contextservice.controller; + + import com.example.contextservice.client.GreetingServiceClient; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.beans.factory.annotation.Value; + import org.springframework.web.bind.annotation.GetMapping; + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class ContextController { + + @Autowired + private GreetingServiceClient greetingServiceClient; + + @Value("${user.region}") + private String userRegion; + + @GetMapping("/context") + public String getContext() { + String greeting = greetingServiceClient.getGreeting(); + return "The Greeting Service says: " + greeting + " from " + userRegion + "!"; + } + } + + 1. Both the Greeting Service and the Context Service register themselves with the Eureka Server upon startup using the _@EnableDiscoveryClient_ annotation. + 2. The Context Service, annotated with _@EnableFeignClients_, uses the GreetingServiceClient interface with _@FeignClient(name = "greeting-service")_ to declare its intent to communicate with the service named "greeting-service" in Eureka. + 3. When the /context endpoint of the Context Service is accessed, it calls the _getGreeting()_ method of the GreetingServiceClient. + 4. OpenFeign, leveraging the service discovery information from Eureka, resolves the network location of an available instance of the Greeting Service and makes an HTTP GET request to its /greeting endpoint. + 5. The Greeting Service responds with "Hello", and the Context Service then adds the configured user.region to the response. + + This project utilizes Spring Boot Actuator, which is included as a dependency, to provide health check endpoints for each microservice. These endpoints (e.g., /actuator/health) can be used by Eureka Server to monitor the health of the registered instances. + +## Steps to use for this Project + +Prerequisites: + - Java Development Kit (JDK): Make sure you have a compatible JDK installed (ideally Java 17 or later, as Spring Boot 3.x requires it). + - Maven or Gradle: You'll need either Maven (if you chose Maven during Spring Initializr setup) or Gradle (if you chose Gradle) installed on your system. + - An IDE (Optional but Recommended): IntelliJ IDEA, Eclipse, or Spring Tool Suite (STS) can make it easier to work with the project. + - Web Browser: You'll need a web browser to access the Eureka dashboard and the microservice endpoints. + +Step : + - You'll need to build each microservice individually. Navigate to the root directory of each project in your terminal or command prompt and run the appropriate build command: + _cd eurekaserver + mvn clean install + cd ../greetingservice + mvn clean install + cd ../contextservice + mvn clean install_ +Step : + - Navigate to the root directory of your eurekaserver project in your terminal or command prompt + _mvn spring-boot:run_ + - Wait for the Eureka Server application to start. You should see logs in the console indicating that it has started on port 8761 (as configured). + - Open your web browser and go to http://localhost:8761/. You should see the Eureka Server dashboard. Initially, the list of registered instances will be empty. +Step : + - Run the Greeting Service + - Open a new terminal or command prompt. + - Navigate to the root directory of your greetingservice project. + - Run the Spring Boot application: _mvn spring-boot:run_ + - Wait for the Greeting Service to start. You should see logs indicating that it has registered with the Eureka Server. + - Go back to your Eureka Server dashboard in the browser (http://localhost:8761/). You should now see GREETINGSERVICE listed under the "Instances currently registered with Eureka". Its status should be "UP". +Step : + - Run the Context Service + - Open a new terminal or command prompt. + - Navigate to the root directory of your contextservice project. + - Run the Spring Boot application: _mvn spring-boot:run_ + - Wait for the Context Service to start. You should see logs indicating that it has registered with the Eureka Server. + - Go back to your Eureka Server dashboard in the browser (http://localhost:8761/). You should now see CONTEXTSERVICE listed under the "Instances currently registered with Eureka". Its status should be "UP". +STEP : + - Test the Greeting Service Directly: Open your web browser and go to http://localhost:8081/greeting. You should see the output: Hello. + - Test the Context Service (which calls the Greeting Service): Open your web browser and go to http://localhost:8082/context. You should see the output: The Greeting Service says: Hello from Chennai, Tamil Nadu, India!. This confirms that the Context Service successfully discovered and called the Greeting Service through Eureka. + +Optional: Check Health Endpoints + +You can also verify the health status of each service using Spring Boot Actuator: + - Greeting Service Health: http://localhost:8081/actuator/health (should return {"status":"UP"}) + - Context Service Health: http://localhost:8082/actuator/health (should return {"status":"UP"}) + - Eureka Server Health: http://localhost:8761/actuator/health (should return {"status":"UP"}) + +## When to use Microservices Self-Registration Pattern + + - **Dynamic Environments:** When your microservices are frequently deployed, scaled up or down, or their network locations (IP addresses and ports) change often. This is common in cloud-based or containerized environments (like Docker and Kubernetes). + - **Large Number of Services:** As the number of microservices in your system grows, manually managing their configurations and dependencies becomes complex and error-prone. Self-registration automates this process. + - **Need for Automatic Service** Discovery: When services need to find and communicate with each other without hardcoding network locations. This allows for greater flexibility and reduces coupling. + - **Implementing Load Balancing:** Service registries like Eureka often integrate with load balancers, enabling them to automatically distribute traffic across available instances of a service that have registered themselves. + - **Improving System Resilience:** If a service instance fails, the registry will eventually be updated (through heartbeats or health checks), and other services can discover and communicate with the remaining healthy instances. + - **DevOps Automation:** This pattern aligns well with DevOps practices, allowing for more automated deployment and management of microservices. + +## Real-World Applications of Self-Registration pattern + + - E-Commerce platforms have numerous independent services for product catalogs, order processing, payments, shipping, etc. Self-registration allows these services to dynamically discover and communicate with each other as the system scales during peak loads or as new features are deployed. + - Streaming services rely on many microservices for user authentication, content delivery networks (CDNs), recommendation engines, billing systems, etc. Self-registration helps these services adapt to varying user demands and infrastructure changes. + - Social media These platforms use microservices for managing user profiles, timelines, messaging, advertising, and more. Self-registration enables these services to scale independently and handle the massive traffic they experience. + +## Advantages + + - Microservices can dynamically locate and communicate with each other without needing to know their specific network addresses beforehand. This is crucial in dynamic environments where IP addresses and ports can change frequently. + - Reduces the need for manual configuration of service locations in each microservice. Services don't need to be updated every time another service's location changes. + - Scaling microservices up or down becomes easier. New instances automatically register themselves with the service registry, making them immediately discoverable by other services without manual intervention. + - If a service instance fails, it will eventually stop sending heartbeats to the registry and will be removed. Consumers can then discover and connect to other healthy instances, improving the system's overall resilience. + - Services are less tightly coupled as they don't have direct dependencies on the physical locations of other services. This makes deployments and updates more flexible. + - Service registries often integrate with load balancers. When a new service instance registers, the load balancer can automatically include it in the pool of available instances, distributing traffic effectively. + - Microservices can be deployed across different environments (development, testing, production) without significant changes to their discovery mechanism, as long as they are configured to connect to the appropriate service registry for that environment. + +## Trade-offs + + - Introducing a service registry adds another component to your system that needs to be set up, managed, and monitored. This increases the overall complexity of the infrastructure. + - The service registry itself becomes a critical component. If the service registry becomes unavailable, it can disrupt communication between microservices. High availability for the service registry is therefore essential. + - Microservices need to communicate with the service registry for registration, sending heartbeats, and querying for other services. This can lead to increased network traffic. + - There might be a slight delay between when a microservice instance starts and when it becomes fully registered and discoverable in the service registry. This needs to be considered, especially during scaling events. + - You need to consider how your microservices will behave if they fail to register with the service registry upon startup. Robust error handling and retry mechanisms are often necessary. + - Microservices need to include and configure client libraries (like the Eureka Discovery Client) to interact with the service registry. This adds a dependency to your application code. + - In distributed service registries, ensuring consistency of the registry data across all nodes can be a challenge. Different registries might have different consistency models (e.g., eventual consistency). + +## References + + - Microservices Patterns: https://microservices.io/ + - Eureka Documentation: https://github.com/Netflix/eureka | https://spring.io/projects/spring-cloud-netflix + - Spring Boot Documentation: https://spring.io/projects/spring-boot + - Spring Cloud OpenFeignDocumentation: https://spring.io/projects/spring-cloud-openfeign + - Spring Boot Actuator Documentation: https://www.baeldung.com/spring-boot-actuators \ No newline at end of file diff --git a/microservices-self-registration/application.log.2025-04-09.0.gz b/microservices-self-registration/application.log.2025-04-09.0.gz new file mode 100644 index 000000000000..d51965d73d7a Binary files /dev/null and b/microservices-self-registration/application.log.2025-04-09.0.gz differ diff --git a/microservices-self-registration/contextservice/.gitattributes b/microservices-self-registration/contextservice/.gitattributes new file mode 100644 index 000000000000..3b41682ac579 --- /dev/null +++ b/microservices-self-registration/contextservice/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/microservices-self-registration/contextservice/.gitignore b/microservices-self-registration/contextservice/.gitignore new file mode 100644 index 000000000000..549e00a2a96f --- /dev/null +++ b/microservices-self-registration/contextservice/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz new file mode 100644 index 000000000000..6ceb03b833a7 Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz differ diff --git a/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz new file mode 100644 index 000000000000..eb2a63ce194e Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz differ diff --git a/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz new file mode 100644 index 000000000000..bd773dc8ba59 Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz differ diff --git a/microservices-self-registration/contextservice/pom.xml b/microservices-self-registration/contextservice/pom.xml new file mode 100644 index 000000000000..ea6d105bd06f --- /dev/null +++ b/microservices-self-registration/contextservice/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + com.learning + contextservice + 0.0.1-SNAPSHOT + contextservice + contextservice + + + 2024.0.1 + + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + 1.18.38 + provided + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + \ No newline at end of file diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java new file mode 100644 index 000000000000..eb22d094ffce --- /dev/null +++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java @@ -0,0 +1,17 @@ +package com.learning.contextservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableDiscoveryClient +@EnableFeignClients +public class ContextserviceApplication { + + public static void main(String[] args) { + SpringApplication.run(ContextserviceApplication.class, args); + } + +} diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java new file mode 100644 index 000000000000..0226fc50e803 --- /dev/null +++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java @@ -0,0 +1,42 @@ +package com.learning.contextservice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + + +@Component("myCustomHealthCheck") +public class MyCustomHealthCheck implements HealthIndicator { + + private static final Logger log = LoggerFactory.getLogger(MyCustomHealthCheck.class); + + private volatile boolean isHealthy = true; + + @Scheduled(fixedRate = 5000) // Run every 5 seconds + public void updateHealthStatus() { + // Perform checks here to determine the current health + // For example, check database connectivity, external service availability, etc. + isHealthy = performHealthCheck(); + log.info("Update health status : {}", isHealthy); + } + + boolean performHealthCheck() { + boolean current = System.currentTimeMillis() % 10000 < 5000; // Simulate fluctuating health + log.debug("Performing health check, current status: {}", current); + return current; // Simulate fluctuating health + } + + @Override + public Health health() { + if (isHealthy) { + log.info("Health check successful, service is UP"); + return Health.up().withDetail("message", "Service is running and scheduled checks are OK").build(); + } else { + log.warn("Health check failed, service is DOWN"); + return Health.down().withDetail("error", "Scheduled health checks failed").build(); + } + } +} diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java new file mode 100644 index 000000000000..367a3bdd496c --- /dev/null +++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java @@ -0,0 +1,11 @@ +package com.learning.contextservice.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +@FeignClient(name = "greetingservice") +public interface GreetingServiceClient { + + @GetMapping("/greeting") + String getGreeting(); +} diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java new file mode 100644 index 000000000000..5ad8969e0871 --- /dev/null +++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java @@ -0,0 +1,26 @@ +package com.learning.contextservice.controller; + +import com.learning.contextservice.client.GreetingServiceClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ContextController { + + private final GreetingServiceClient greetingServiceClient; + private final String userRegion; + + @Autowired + public ContextController(GreetingServiceClient greetingServiceClient, @Value("${user.region}") String userRegion) { + this.greetingServiceClient = greetingServiceClient; + this.userRegion = userRegion; + } + + @GetMapping("/context") + public String getContext() { + String greeting = greetingServiceClient.getGreeting(); + return "The Greeting Service says: "+greeting+" from "+userRegion; + } +} diff --git a/microservices-self-registration/contextservice/src/main/resources/application.yml b/microservices-self-registration/contextservice/src/main/resources/application.yml new file mode 100644 index 000000000000..dfef73bbbeec --- /dev/null +++ b/microservices-self-registration/contextservice/src/main/resources/application.yml @@ -0,0 +1,25 @@ +server: + port: 8082 + +spring: + application: + name: contextservice + +eureka: + client: + service-url.defaultZone: http://localhost:8761/eureka + +user: + region: Chennai, Tamil Nadu, India + +management: + endpoint: + health: + show-details: always + web: + exposure: + include: health + +logging: + file: + name: application.log diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java new file mode 100644 index 000000000000..f11da867cda0 --- /dev/null +++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java @@ -0,0 +1,49 @@ +package com.learning.contextservice; + +import com.learning.contextservice.client.GreetingServiceClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@SpringBootTest(classes = ContextserviceApplication.class) +@AutoConfigureMockMvc +@Import(TestConfig.class) +class ContextControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private GreetingServiceClient greetingServiceClient; + + @Value("${user.region}") + private String userRegion; + + @Test + void shouldReturnContextGreeting() throws Exception{ + Mockito.when(greetingServiceClient.getGreeting()).thenReturn("Mocked Hello"); + + mockMvc.perform(MockMvcRequestBuilders.get("/context") + .accept(MediaType.TEXT_PLAIN)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("The Greeting Service says: Mocked Hello from Chennai, Tamil Nadu, India")); + } + + @Test + void shouldReturnContextServiceHealthStatusUp() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("\"status\":\"UP\""))); + } +} diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java new file mode 100644 index 000000000000..a5d5c869c664 --- /dev/null +++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java @@ -0,0 +1,17 @@ +package com.learning.contextservice; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ContextserviceApplicationTests { + + @Test + void contextLoads() { + // This is a basic integration test that checks if the Spring Application Context loads successfully. + // If the context loads without any exceptions, the test is considered passing. + // It is often left empty as the act of loading the context is the primary verification. + // You can add specific assertions here if you want to verify the presence or state of certain beans. + } + +} diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java new file mode 100644 index 000000000000..f378f46f59df --- /dev/null +++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java @@ -0,0 +1,17 @@ +package com.learning.contextservice; + +import com.learning.contextservice.client.GreetingServiceClient; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TestConfig { + + @Bean + public GreetingServiceClient greetingServiceClient() { + GreetingServiceClient mockClient = Mockito.mock(GreetingServiceClient.class); + Mockito.when(mockClient.getGreeting()).thenReturn("Mocked Hello"); + return mockClient; + } +} diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java new file mode 100644 index 000000000000..129209469827 --- /dev/null +++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java @@ -0,0 +1,33 @@ +package com.learning.contextservice; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.health.Health; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.boot.actuate.health.Status; +import static org.junit.jupiter.api.Assertions.*; + +class MyCustomHealthCheckTest { + + @Test + void testHealthUp() { + MyCustomHealthCheck healthCheck = new MyCustomHealthCheck(); + // Simulate a healthy state + ReflectionTestUtils.setField(healthCheck, "isHealthy", true); + Health health = healthCheck.health(); + assertEquals(Status.UP, health.getStatus()); + assertTrue(health.getDetails().containsKey("message")); + assertEquals("Service is running and scheduled checks are OK", health.getDetails().get("message")); + } + + @Test + void testHealthDown() { + MyCustomHealthCheck healthCheck = new MyCustomHealthCheck(); + // Simulate an unhealthy state + ReflectionTestUtils.setField(healthCheck, "isHealthy", false); + Health health = healthCheck.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertTrue(health.getDetails().containsKey("error")); + assertEquals("Scheduled health checks failed", health.getDetails().get("error")); + } + +} \ No newline at end of file diff --git a/microservices-self-registration/eurekaserver/.gitattributes b/microservices-self-registration/eurekaserver/.gitattributes new file mode 100644 index 000000000000..3b41682ac579 --- /dev/null +++ b/microservices-self-registration/eurekaserver/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/microservices-self-registration/eurekaserver/.gitignore b/microservices-self-registration/eurekaserver/.gitignore new file mode 100644 index 000000000000..549e00a2a96f --- /dev/null +++ b/microservices-self-registration/eurekaserver/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices-self-registration/eurekaserver/pom.xml b/microservices-self-registration/eurekaserver/pom.xml new file mode 100644 index 000000000000..b1a4b26cf4f4 --- /dev/null +++ b/microservices-self-registration/eurekaserver/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + com.learning + eurekaserver + 0.0.1-SNAPSHOT + eurekaserver + eurekaserver + + + 2024.0.1 + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + \ No newline at end of file diff --git a/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java b/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java new file mode 100644 index 000000000000..80b3d904ff4c --- /dev/null +++ b/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java @@ -0,0 +1,15 @@ +package com.learning.eurekaserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + +@SpringBootApplication +@EnableEurekaServer +public class EurekaserverApplication { + + public static void main(String[] args) { + SpringApplication.run(EurekaserverApplication.class, args); + } + +} diff --git a/microservices-self-registration/eurekaserver/src/main/resources/application.yml b/microservices-self-registration/eurekaserver/src/main/resources/application.yml new file mode 100644 index 000000000000..51f8a815d251 --- /dev/null +++ b/microservices-self-registration/eurekaserver/src/main/resources/application.yml @@ -0,0 +1,10 @@ +server: + port: 8761 + +eureka: + client: + register-with-eureka: false + fetch-registry: false + server: + enable-self-preservation: true + diff --git a/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java b/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java new file mode 100644 index 000000000000..b5150fefa940 --- /dev/null +++ b/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java @@ -0,0 +1,17 @@ +package com.learning.eurekaserver; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EurekaserverApplicationTests { + + @Test + void contextLoads() { + // This is a basic integration test that checks if the Spring Application Context loads successfully. + // If the context loads without any exceptions, the test is considered passing. + // It is often left empty as the act of loading the context is the primary verification. + // You can add specific assertions here if you want to verify the presence or state of certain beans. + } + +} diff --git a/microservices-self-registration/greetingservice/.gitattributes b/microservices-self-registration/greetingservice/.gitattributes new file mode 100644 index 000000000000..3b41682ac579 --- /dev/null +++ b/microservices-self-registration/greetingservice/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/microservices-self-registration/greetingservice/.gitignore b/microservices-self-registration/greetingservice/.gitignore new file mode 100644 index 000000000000..549e00a2a96f --- /dev/null +++ b/microservices-self-registration/greetingservice/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz new file mode 100644 index 000000000000..93d6a2e62ac1 Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz differ diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz new file mode 100644 index 000000000000..40c96f702853 Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz differ diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz new file mode 100644 index 000000000000..59f2cbc3c8df Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz differ diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz new file mode 100644 index 000000000000..62d73c3020c1 Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz differ diff --git a/microservices-self-registration/greetingservice/pom.xml b/microservices-self-registration/greetingservice/pom.xml new file mode 100644 index 000000000000..45988a145866 --- /dev/null +++ b/microservices-self-registration/greetingservice/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + com.learning + greetingservice + 0.0.1-SNAPSHOT + greetingservice + greetingservice + + + 2024.0.1 + + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + 1.18.38 + provided + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + \ No newline at end of file diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java new file mode 100644 index 000000000000..6868c2624f16 --- /dev/null +++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java @@ -0,0 +1,15 @@ +package com.learning.greetingservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +@SpringBootApplication +@EnableDiscoveryClient +public class GreetingserviceApplication { + + public static void main(String[] args) { + SpringApplication.run(GreetingserviceApplication.class, args); + } + +} diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java new file mode 100644 index 000000000000..218a4ad002d4 --- /dev/null +++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java @@ -0,0 +1,41 @@ +package com.learning.greetingservice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component("myCustomHealthCheck") +public class MyCustomHealthCheck implements HealthIndicator { + + private static final Logger log = LoggerFactory.getLogger(MyCustomHealthCheck.class); + + private volatile boolean isHealthy = true; + + @Scheduled(fixedRate = 5000) // Run every 5 seconds + public void updateHealthStatus() { + // Perform checks here to determine the current health + // For example, check database connectivity, external service availability, etc. + isHealthy = performHealthCheck(); + log.info("Update health status : {}", isHealthy); + } + + boolean performHealthCheck() { + boolean current = System.currentTimeMillis() % 10000 < 5000; // Simulate fluctuating health + log.debug("Performing health check, current status: {}", current); + return current; // Simulate fluctuating health + } + + @Override + public Health health() { + if (isHealthy) { + log.info("Health check successful, service is UP"); + return Health.up().withDetail("message", "Service is running and scheduled checks are OK").build(); + } else { + log.warn("Health check failed, service is DOWN"); + return Health.down().withDetail("error", "Scheduled health checks failed").build(); + } + } +} diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java new file mode 100644 index 000000000000..ea385beb1abe --- /dev/null +++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java @@ -0,0 +1,13 @@ +package com.learning.greetingservice.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GreetingsController { + + @GetMapping("/greeting") + public String getGreeting() { + return "Hello"; + } +} diff --git a/microservices-self-registration/greetingservice/src/main/resources/application.yml b/microservices-self-registration/greetingservice/src/main/resources/application.yml new file mode 100644 index 000000000000..adcfac884c2b --- /dev/null +++ b/microservices-self-registration/greetingservice/src/main/resources/application.yml @@ -0,0 +1,22 @@ +server: + port: 8081 + +spring: + application: + name: greetingservice +eureka: + client: + service-url.defaultZone: http://localhost:8761/eureka + +management: + endpoint: + health: + show-details: always + web: + exposure: + include: health + +logging: + file: + name: application.log + diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java new file mode 100644 index 000000000000..945898278aa9 --- /dev/null +++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java @@ -0,0 +1,17 @@ +package com.learning.greetingservice; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class GreetingserviceApplicationTests { + + @Test + void contextLoads() { + // This is a basic integration test that checks if the Spring Application Context loads successfully. + // If the context loads without any exceptions, the test is considered passing. + // It is often left empty as the act of loading the context is the primary verification. + // You can add specific assertions here if you want to verify the presence or state of certain beans. + } + +} diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java new file mode 100644 index 000000000000..8ba8b2a30f93 --- /dev/null +++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java @@ -0,0 +1,22 @@ +package com.learning.greetingservice; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.health.Health; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.boot.actuate.health.Status; +import static org.junit.jupiter.api.Assertions.*; + +class MyCustomHealthCheckTest { + + @Test + void testHealthUp() { + MyCustomHealthCheck healthCheck = new MyCustomHealthCheck(); + // Simulate a healthy state + ReflectionTestUtils.setField(healthCheck, "isHealthy", true); + Health health = healthCheck.health(); + assertEquals(Status.UP, health.getStatus()); + assertTrue(health.getDetails().containsKey("message")); + assertEquals("Service is running and scheduled checks are OK", health.getDetails().get("message")); + } + +} \ No newline at end of file diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java new file mode 100644 index 000000000000..5ae98c8b6aeb --- /dev/null +++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java @@ -0,0 +1,36 @@ +package com.learning.greetingservice.controller; + +import com.learning.greetingservice.GreetingserviceApplication; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@SpringBootTest(classes = GreetingserviceApplication.class) +@AutoConfigureMockMvc +@ActiveProfiles("test") +class GreetingControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldReturnGreeting() throws Exception{ + mockMvc.perform(MockMvcRequestBuilders.get("/greeting") + .accept(MediaType.TEXT_PLAIN)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Hello")); + } + + @Test + void shouldReturnHealthStatusUp() throws Exception{ + mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.containsString("\"status\":\"UP\""))); + } +} diff --git a/microservices-self-registration/pom.xml b/microservices-self-registration/pom.xml new file mode 100644 index 000000000000..4b708cb0eb12 --- /dev/null +++ b/microservices-self-registration/pom.xml @@ -0,0 +1,63 @@ + + + + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + 4.0.0 + microservices-self-registration + pom + + eurekaserver + greetingservice + contextservice + + + + 21 + ${java.version} + ${java.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + \ No newline at end of file diff --git a/money/README.md b/money/README.md index ca64d68fe882..88fc45e2dc44 100644 --- a/money/README.md +++ b/money/README.md @@ -2,18 +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 - - Precision handling - - Currency safety - - Value Object - - Financial operations - - Currency - - Financial - - Immutable - - Value Object + - Business + - Domain + - Encapsulation + - Immutable --- ## Also known as @@ -22,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." +![Money Pattern Mind Map](./etc/money-mind-map.png) + +Flowchart + +![Money Pattern Flowchart](./etc/money-flowchart.png) ## 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 Money 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 fd288dab3b99..6bb63e04fbfe 100644 --- a/monolithic-architecture/README.md +++ b/monolithic-architecture/README.md @@ -1,54 +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 - - Simplicity - - Scalability - - Deployment - - Maintainability + - 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 - -![Model-View-Controller Architecture Diagram](./etc/mvc-architecture-diagram.png) - -## 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 + +![Monolithic Architecture Mind Map](./etc/monolithic-architecture-mind-map.png) + +Flowchart + +![Monolithic Architecture Flowchart](./etc/monolithic-architecture-flowchart.png) + +## 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 { @@ -109,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/main/java/com/iluwatar/monolithic/model/User.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java index 0b05f447c924..f19fa52bc008 100644 --- a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java @@ -48,5 +48,12 @@ public class User { @Column(unique = true) private String email; - private String password; + private char[] password; + + /** Secure password cleanup. Must be called when done with authentication. */ + public void clearPassword() { + if (password != null) { + java.util.Arrays.fill(password, '\0'); + } + } } 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/notification/README.md b/notification/README.md index e94421ff504e..04feb01cb814 100644 --- a/notification/README.md +++ b/notification/README.md @@ -32,6 +32,10 @@ In plain words > The Notification design pattern enables an object to automatically notify a list of interested observers about changes or events without knowing the specifics of the subscribers. +Sequence diagram + +![Notification sequence diagram](./etc/notification-sequence-diagram.png) + ## Programmatic Example of Notification Pattern in Java The Java Notification pattern is used to capture information passed between layers, validate the information, and return any errors to the presentation layer if needed. It reduces coupling between the producer and consumer of events, enhances flexibility and reusability of components, and allows for dynamic event subscription and unsubscription. diff --git a/notification/etc/notification-sequence-diagram.png b/notification/etc/notification-sequence-diagram.png new file mode 100644 index 000000000000..ec9c25992fa2 Binary files /dev/null and b/notification/etc/notification-sequence-diagram.png differ diff --git a/null-object/README.md b/null-object/README.md index 75da7452a01a..00799a1a31aa 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -35,6 +35,10 @@ Wikipedia says > In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). +Sequence diagram + +![Null Object sequence diagram](./etc/null-object-sequence-diagram.png) + ## Programmatic Example of Null Object in Java By implementing the Null Object Pattern, Java developers can ensure that their applications handle 'empty' objects more gracefully, enhancing code stability and readability. diff --git a/null-object/etc/null-object-sequence-diagram.png b/null-object/etc/null-object-sequence-diagram.png new file mode 100644 index 000000000000..bfeda450aa0c Binary files /dev/null and b/null-object/etc/null-object-sequence-diagram.png differ diff --git a/object-mother/README.md b/object-mother/README.md index c80ad2eaa8fc..67ad32f268a3 100644 --- a/object-mother/README.md +++ b/object-mother/README.md @@ -37,6 +37,14 @@ wiki.c2.com says > 2. providing methods to update the objects during the tests, and > 3. if necessary, deleting the object from the database at the completion of the test. +Mind map + +![Object Mother mind map](./etc/object-mother-mind-map.png) + +Flowchart + +![Object Mother flowchart](./etc/object-mother-flowchart.png) + ## Programmatic Example of Object Mother Pattern in Java The Object Mother is a design pattern that aims to provide an easy way to create objects for testing purposes. It encapsulates the logic for building instances of complex objects in one place, making it easier to maintain and reuse across multiple tests. diff --git a/object-mother/etc/object-mother-flowchart.png b/object-mother/etc/object-mother-flowchart.png new file mode 100644 index 000000000000..b475a0532282 Binary files /dev/null and b/object-mother/etc/object-mother-flowchart.png differ diff --git a/object-mother/etc/object-mother-mind-map.png b/object-mother/etc/object-mother-mind-map.png new file mode 100644 index 000000000000..2234eedde064 Binary files /dev/null and b/object-mother/etc/object-mother-mind-map.png differ diff --git a/object-pool/README.md b/object-pool/README.md index 56888b017a05..9746c27c574b 100644 --- a/object-pool/README.md +++ b/object-pool/README.md @@ -35,6 +35,10 @@ Wikipedia says > The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. +Sequence diagram + +![Object Pool sequence diagram](./etc/object-pool-sequence-diagram.png) + ## Programmatic Example of Object Pool Pattern in Java In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they are extremely expensive to create. The solution is to create a pool of them, track which ones are in-use, and instead of disposing them re-use the instances. diff --git a/object-pool/etc/object-pool-sequence-diagram.png b/object-pool/etc/object-pool-sequence-diagram.png new file mode 100644 index 000000000000..2aa438024455 Binary files /dev/null and b/object-pool/etc/object-pool-sequence-diagram.png differ diff --git a/observer/README.md b/observer/README.md index 1bf11fbe331b..6b8a700771f3 100644 --- a/observer/README.md +++ b/observer/README.md @@ -7,7 +7,7 @@ language: en tag: - Decoupling - Event-driven - - Gang Of Four + - Gang of Four - Publish/subscribe --- @@ -33,6 +33,10 @@ Wikipedia says > The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. +Sequence diagram + +![Observer sequence diagram](./etc/observer-sequence-diagram.png) + ## Programmatic Example of Observer Pattern in Java In a land far away live the races of hobbits and orcs. Both of them are mostly outdoors, so they closely follow the weather changes. One could say that they are constantly observing the weather. diff --git a/observer/etc/observer-sequence-diagram.png b/observer/etc/observer-sequence-diagram.png new file mode 100644 index 000000000000..94c7c3ee179c Binary files /dev/null and b/observer/etc/observer-sequence-diagram.png differ diff --git a/optimistic-offline-lock/README.md b/optimistic-offline-lock/README.md index 98bf4140b6e7..c14e102ba250 100644 --- a/optimistic-offline-lock/README.md +++ b/optimistic-offline-lock/README.md @@ -35,6 +35,10 @@ Wikipedia says > Optimistic concurrency control (OCC), also known as optimistic locking, is a concurrency control method applied to transactional systems such as relational database management systems and software transactional memory. +Sequence diagram + +![Optimistic Offline Lock sequence diagram](./etc/optimistic-offline-lock-sequence-diagram.png) + ## Programmatic Example of Optimistic Offline Lock Pattern in Java In this section, we delve into the practical implementation of the Optimistic Offline Lock in Java. By following these steps, you can ensure that your application handles data conflicts and concurrency with minimal overhead. diff --git a/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png b/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png new file mode 100644 index 000000000000..7870596ca211 Binary files /dev/null and b/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png differ diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java index 88134b923fab..a2c819b29d82 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java @@ -28,12 +28,19 @@ import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; -/** ignup model. */ +/** Signup model. */ @Component @Data @NoArgsConstructor public class SignupModel { private String name; private String email; - private String password; + private char[] password; + + /** Secure password cleanup. Must be called when done with authentication. */ + public void clearPassword() { + if (password != null) { + java.util.Arrays.fill(password, '\0'); + } + } } diff --git a/page-object/README.md b/page-object/README.md index 6f21070a3582..8209a0fc52ab 100644 --- a/page-object/README.md +++ b/page-object/README.md @@ -39,6 +39,14 @@ selenium.dev says > > Page Object is a Design Pattern that has become popular in test automation for enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your AUT. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. +Mind map + +![Page Object mind map](./etc/page-object-mind-map.png) + +Flowchart + +![Page Object flowchart](./etc/page-object-flowchart.png) + ## Programmatic Example of Page Object Pattern in Java The Page Object design pattern is a popular design pattern in test automation. It helps in enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your Application Under Test (AUT). The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. diff --git a/page-object/etc/page-object-flowchart.png b/page-object/etc/page-object-flowchart.png new file mode 100644 index 000000000000..b32c413b78af Binary files /dev/null and b/page-object/etc/page-object-flowchart.png differ diff --git a/page-object/etc/page-object-mind-map.png b/page-object/etc/page-object-mind-map.png new file mode 100644 index 000000000000..c8af6acce9e7 Binary files /dev/null and b/page-object/etc/page-object-mind-map.png differ diff --git a/parameter-object/README.md b/parameter-object/README.md index bbb1c73d95cc..226d4073a352 100644 --- a/parameter-object/README.md +++ b/parameter-object/README.md @@ -40,6 +40,14 @@ wiki.c2.com says > Replace the LongParameterList with a ParameterObject; an object or structure with data members representing the arguments to be passed in. +Mind map + +![Parameter Object mind map](./etc/parameter-object-mind-map.png) + +Flowchart + +![Parameter Object flowchart](./etc/parameter-object-flowchart.png) + ## Programmatic Example of Parameter Object Pattern in Java The Parameter Object design pattern is a way to group multiple parameters into a single object. This simplifies method signatures and enhances code maintainability enabling Java developers to streamline complex method calls, focusing on cleaner and more maintainable Java code. diff --git a/parameter-object/etc/parameter-object-flowchart.png b/parameter-object/etc/parameter-object-flowchart.png new file mode 100644 index 000000000000..c63b41f75d9e Binary files /dev/null and b/parameter-object/etc/parameter-object-flowchart.png differ diff --git a/parameter-object/etc/parameter-object-mind-map.png b/parameter-object/etc/parameter-object-mind-map.png new file mode 100644 index 000000000000..3f920502f28b Binary files /dev/null and b/parameter-object/etc/parameter-object-mind-map.png differ diff --git a/partial-response/README.md b/partial-response/README.md index a3704a4c3453..f4161443f687 100644 --- a/partial-response/README.md +++ b/partial-response/README.md @@ -33,6 +33,10 @@ In plain words > The Partial Response design pattern allows a system to send portions of data to the client as they become available, enabling the client to start processing the data before the complete response is received. +Sequence diagram + +![Partial Response sequence diagram](./etc/partial-response-sequence-diagram.png) + ## Programmatic Example of Partial Response Pattern in Java The Partial Response design pattern allows clients to specify which fields of a resource they need. This pattern is useful for reducing the amount of data transferred over the network and allowing clients to start processing data sooner. diff --git a/partial-response/etc/partial-response-sequence-diagram.png b/partial-response/etc/partial-response-sequence-diagram.png new file mode 100644 index 000000000000..ac2f550154ef Binary files /dev/null and b/partial-response/etc/partial-response-sequence-diagram.png differ diff --git a/pipeline/README.md b/pipeline/README.md index e05f151e25ac..0b44fe143a72 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -46,6 +46,10 @@ Wikipedia says > In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. +Flowchart + +![Pipeline flowchart](./etc/pipeline-flowchart.png) + ## Programmatic Example of Pipeline Pattern in Java Let's create a string processing pipeline example. The stages of our pipeline are called `Handler`s. diff --git a/pipeline/etc/pipeline-flowchart.png b/pipeline/etc/pipeline-flowchart.png new file mode 100644 index 000000000000..48a5cf513b21 Binary files /dev/null and b/pipeline/etc/pipeline-flowchart.png differ diff --git a/poison-pill/README.md b/poison-pill/README.md index 31921326e347..5e9e690cb962 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -29,6 +29,10 @@ In plain words > Poison Pill is a known message structure that ends the message exchange. +Sequence diagram + +![Poison Pill sequence diagram](./etc/poison-pill-sequence-diagram.png) + ## Programmatic Example of Poison Pill Pattern in Java In this Java example, the Poison Pill serves as a shutdown signal within message queues, demonstrating effective thread management and consumer communication. @@ -210,10 +214,6 @@ Program output: 07:43:01.520 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Consumer CONSUMER_1 receive request to terminate. ``` -## Detailed Explanation of Poison Pill Pattern with Real-World Examples - -![Poison Pill](./etc/poison-pill.png "Poison Pill") - ## When to Use the Poison Pill Pattern in Java Use the Poison Pill idiom when: diff --git a/poison-pill/etc/poison-pill-sequence-diagram.png b/poison-pill/etc/poison-pill-sequence-diagram.png new file mode 100644 index 000000000000..9976f2cc69de Binary files /dev/null and b/poison-pill/etc/poison-pill-sequence-diagram.png differ diff --git a/pom.xml b/pom.xml index fb0bfd7565e1..82d15bdf1d74 100644 --- a/pom.xml +++ b/pom.xml @@ -46,9 +46,9 @@ 2.0.17 - 0.8.12 + 0.8.13 1.4 - 4.7.0 + 4.17.0 2.11.0 6.0.0 1.1.0 @@ -59,11 +59,11 @@ 3.5.2 - 4.6 + 5.0.0 3.14.0 - 5.0.0.4389 + 5.1.0.4751 https://sonarcloud.io iluwatar iluwatar_java-design-patterns @@ -106,6 +106,7 @@ converter curiously-recurring-template-pattern currying + dao-factory data-access-object data-bus data-locality @@ -166,6 +167,7 @@ microservices-distributed-tracing microservices-idempotent-consumer microservices-log-aggregation + microservices-self-registration model-view-controller model-view-intent model-view-presenter @@ -230,6 +232,7 @@ table-module template-method templateview + thread-pool-executor throttling tolerant-reader trampoline @@ -240,8 +243,12 @@ update-method value-object version-number + view-helper virtual-proxy visitor + backpressure + actor-model + rate-limiting-pattern @@ -315,12 +322,6 @@ ${junit.version} test - - org.junit.jupiter - junit-jupiter-params - ${junit.version} - test - org.junit.jupiter junit-jupiter-migrationsupport @@ -480,7 +481,7 @@ com.diffplug.spotless spotless-maven-plugin - 2.44.3 + 2.44.4 diff --git a/presentation-model/README.md b/presentation-model/README.md index 9b0eb6659451..df191ddb6591 100644 --- a/presentation-model/README.md +++ b/presentation-model/README.md @@ -33,7 +33,6 @@ Architecture diagram ![Presentation Model Architecture Diagram](./etc/presentation-model-architecture-diagram.png) - ## Programmatic Example of Presentation Model Pattern in Java The Presentation Model design pattern is a pattern that separates the responsibility of managing the state and behavior of the GUI in a separate model class. This model class is not tied to the view and can be used to test the GUI behavior independently of the GUI itself. diff --git a/private-class-data/README.md b/private-class-data/README.md index 18dd48053849..fe9bc08e0b87 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -33,6 +33,14 @@ Wikipedia says > Private class data is a design pattern in computer programming used to encapsulate class attributes and their manipulation. +Mind map + +![Private Class Data mind map](./etc/private-class-data-mind-map.png) + +Flowchart + +![Private Class Data flowchart](./etc/private-class-data-flowchart.png) + ## Programmatic Example of Private Class Data Pattern in Java Imagine you are cooking a stew for your family dinner. You want to stop your family members from tasting the stew while you're still preparing it. If they do, there might not be enough stew left for dinner. diff --git a/private-class-data/etc/private-class-data-flowchart.png b/private-class-data/etc/private-class-data-flowchart.png new file mode 100644 index 000000000000..2af3e1a6cb3a Binary files /dev/null and b/private-class-data/etc/private-class-data-flowchart.png differ diff --git a/private-class-data/etc/private-class-data-mind-map.png b/private-class-data/etc/private-class-data-mind-map.png new file mode 100644 index 000000000000..adfaa6abbc68 Binary files /dev/null and b/private-class-data/etc/private-class-data-mind-map.png differ diff --git a/producer-consumer/README.md b/producer-consumer/README.md index a40ef5a654bd..575be804cd45 100644 --- a/producer-consumer/README.md +++ b/producer-consumer/README.md @@ -36,6 +36,10 @@ Wikipedia says > Dijkstra wrote about the case: "We consider two processes, which are called the 'producer' and the 'consumer' respectively. The producer is a cyclic process that produces a certain portion of information, that has to be processed by the consumer. The consumer is also a cyclic process that needs to process the next portion of information, as has been produced by the producer. We assume the two processes to be connected for this purpose via a buffer with unbounded capacity." +Sequence diagram + +![Producer-Consumer sequence diagram](./etc/producer-consumer-sequence-diagram.png) + ## Programmatic Example of Producer-Consumer Pattern in Java Consider a manufacturing process of item, the producer will need to pause the production when manufacturing pipeline is full and the consumer will need to pause the consumption of item when the manufacturing pipeline is empty. We can separate the process of production and consumption which work together and pause at separate times. @@ -177,10 +181,6 @@ Program output: 08:10:17.483 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [10] produced by [Producer_1] ``` -## Detailed Explanation of Producer-Consumer Pattern with Real-World Examples - -![Producer-Consumer](./etc/producer-consumer.png "Producer-Consumer") - ## When to Use the Producer-Consumer Pattern in Java * When you need to manage a buffer or queue where producers add data and consumers take data, often in a multithreaded environment. diff --git a/producer-consumer/etc/producer-consumer-sequence-diagram.png b/producer-consumer/etc/producer-consumer-sequence-diagram.png new file mode 100644 index 000000000000..7a97024daa69 Binary files /dev/null and b/producer-consumer/etc/producer-consumer-sequence-diagram.png differ diff --git a/promise/README.md b/promise/README.md index 76b67e2fb58e..9c919862a4dd 100644 --- a/promise/README.md +++ b/promise/README.md @@ -37,6 +37,10 @@ Wikipedia says > In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete. +Sequence diagram + +![Promise sequence diagram](./etc/promise-sequence-diagram.png) + ## Programmatic Example of Promise Pattern in Java The Promise design pattern is a software design pattern that's often used in concurrent programming to handle asynchronous operations. It represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. diff --git a/promise/etc/promise-sequence-diagram.png b/promise/etc/promise-sequence-diagram.png new file mode 100644 index 000000000000..9209fea11441 Binary files /dev/null and b/promise/etc/promise-sequence-diagram.png differ diff --git a/property/README.md b/property/README.md index 0bfd89183531..5eb1ddef44c0 100644 --- a/property/README.md +++ b/property/README.md @@ -31,6 +31,10 @@ In plain words > Define and manage a dynamic set of properties for an object, allowing customization without altering its structure. +Sequence diagram + +![Property sequence diagram](./etc/property-sequence-diagram.png) + ## Programmatic Example of Property Pattern in Java The Property design pattern, also known as Prototype inheritance, is a pattern that allows objects to be created from other objects, forming object hierarchies. This pattern is particularly useful when you want to create a new object that is a slight variation of an existing object. diff --git a/property/etc/property-sequence-diagram.png b/property/etc/property-sequence-diagram.png new file mode 100644 index 000000000000..00c9ee3b762d Binary files /dev/null and b/property/etc/property-sequence-diagram.png differ diff --git a/prototype/README.md b/prototype/README.md index c68c5fbc001f..b97df0707ffc 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -5,7 +5,7 @@ description: "Explore the Prototype design pattern in Java with a comprehensive category: Creational language: en tag: - - Gang Of Four + - Gang of Four - Instantiation - Object composition - Polymorphism @@ -35,6 +35,10 @@ Wikipedia says > The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. +Sequence diagram + +![Prototype sequence diagram](./etc/prototype-sequence-diagram.png) + ## Programmatic Example of Prototype Pattern in Java In Java, the prototype pattern is recommended to be implemented as follows. First, create an interface with a method for cloning objects. In this example, `Prototype` interface accomplishes this with its `copy` method. @@ -152,10 +156,6 @@ Here's the console output from running the example. 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish wolf attacks with laser ``` -## Detailed Explanation of Prototype Pattern with Real-World Examples - -![alt text](./etc/prototype.urm.png "Prototype pattern class diagram") - ## When to Use the Prototype Pattern in Java * When the classes to instantiate are specified at run-time, for example, by dynamic loading. diff --git a/prototype/etc/prototype-sequence-diagram.png b/prototype/etc/prototype-sequence-diagram.png new file mode 100644 index 000000000000..fbab1beeeceb Binary files /dev/null and b/prototype/etc/prototype-sequence-diagram.png differ diff --git a/proxy/README.md b/proxy/README.md index f237fb47e9e5..5e5109348815 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -7,7 +7,7 @@ language: en tag: - Decoupling - Encapsulation - - Gang Of Four + - Gang of Four - Lazy initialization - Proxy - Security @@ -36,6 +36,10 @@ Wikipedia says > A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. +Sequence diagram + +![Proxy sequence diagram](./etc/proxy-sequence-diagram.png) + ## Programmatic Example of Proxy Pattern in Java Imagine a tower where the local wizards go to study their spells. The ivory tower can only be accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy represents the functionality of the tower and adds access control to it. diff --git a/proxy/etc/proxy-sequence-diagram.png b/proxy/etc/proxy-sequence-diagram.png new file mode 100644 index 000000000000..938dffc28c13 Binary files /dev/null and b/proxy/etc/proxy-sequence-diagram.png differ diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md index 4e88cf2e8d9b..86f6c53166f9 100644 --- a/publish-subscribe/README.md +++ b/publish-subscribe/README.md @@ -1,100 +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 - - Gang Of Four + - Messaging + - Microservices - Publish/subscribe + - Scalability --- ## Intent of the Publish-Subscribe Design Pattern -The Publish-Subscribe design pattern is widely used in software architecture to transmit data between various components in a system. -It is a behavioral design pattern aimed at achieving loosely coupled communication between objects. -The primary intent is to allow a one-to-many dependency relationship where one object (the Publisher) notifies multiple other objects (the Subscribers) -about changes or events, without needing to know who or what the subscribers are. +Defines a one-to-many dependency between objects, enabling automatic notification of multiple subscribers when a publisher's state changes or an event occurs. ## Detailed Explanation of Publish-Subscribe Pattern with Real-World Examples -### Real-world example +Real-world example -- Messaging systems like Kafka, RabbitMQ, AWS SNS, JMS - - **Kafka** : publishes messages to topics and subscribers consumes them in real time for analytics, logs or other purposes. - - **RabbitMQ** : Uses exchanges as publisher and queues as subscribers to route messages - - **AWS SNS** : Simple Notification Service (SNS) received the messages from publishers with topic and the subscribers on that topic will receive the messages. (SQS, Lambda functions, emails, SMS) +> An analogous real-world example of the Publish-Subscribe pattern is a news broadcasting system. A news agency (publisher) broadcasts breaking news stories without knowing who specifically receives them. Subscribers, such as television stations, news websites, or mobile news apps, independently decide which types of news they want to receive (e.g., sports, politics, weather) and are automatically notified whenever relevant events occur. This approach keeps the news agency unaware of subscribers' specifics, allowing flexible and scalable distribution of information. +In plain words -- Event driven microservices - - **Publisher** : Point of Sale(PoS) system records the sale of an item and publish the event - - **Subscribers** : Inventory management service updates stock, Billing service sends e-bill to customer +> The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, enabling loose coupling and asynchronous communication in a system. +Wikipedia says -- Newsletter subscriptions - - **Publisher** : Writes a new blog post and publish to subscribers - - **Subscribers** : All the subscribers to the newsletter receive the email - -### In plain words - -The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, -enabling loose coupling and asynchronous communication in a system - -### Wikipedia says - -In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. -This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. - -Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. - -Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. +> In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. +This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. Most messaging systems support both the pub/sub and message queue models in their API; e.g., Java Message Service (JMS). -### Architectural Diagram -![pub-sub](./etc/pub-sub.png) +Sequence diagram + +![Publish-Subscribe sequence diagram](./etc/publish-subscribe-sequence-diagram.png) ## Programmatic Example of Publish-Subscribe Pattern in Java -First we need to identify the Event on which we need the pub-sub methods to trigger. -For example: +First, we identify events that trigger the publisher-subscriber interactions. Common examples include: + +* Sending alerts based on 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 { @@ -118,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 { @@ -139,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 { @@ -208,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 @@ -222,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/main/java/com/iluwatar/publish/subscribe/model/Topic.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java index 4c421afe735a..2011f096a5eb 100644 --- a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java @@ -51,7 +51,7 @@ public void addSubscriber(Subscriber subscriber) { } /** - * Remove a subscriber to the list of subscribers. + * Remove a subscriber from the list of subscribers. * * @param subscriber subscriber to remove */ 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/queue-based-load-leveling/README.md b/queue-based-load-leveling/README.md index a72d39ea3c43..5bf577052f80 100644 --- a/queue-based-load-leveling/README.md +++ b/queue-based-load-leveling/README.md @@ -40,6 +40,10 @@ Wikipedia says > Message Queues are essential components for inter-process communication (IPC) and inter-thread communication, using queues to manage the passing of messages. They help in decoupling producers and consumers, allowing asynchronous processing, which is a key aspect of the Queue-Based Load Leveling pattern. +Flowchart + +![Queue-Based Load Leveling flowchart](./etc/queue-based-load-leveling-flowchart.png) + ## Programmatic Example of Queue-Based Load Leveling Pattern in Java The Queue-Based Load Leveling pattern helps to manage high-volume, sporadic bursts of tasks that can overwhelm a system. It uses a queue as a buffer to hold tasks, decoupling the task generation from task processing. Tasks are processed at a controlled rate, ensuring optimal load management and fault tolerance, crucial for maintaining robust system architecture. diff --git a/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png b/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png new file mode 100644 index 000000000000..2fdf718414c5 Binary files /dev/null and b/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png differ diff --git a/rate-limiting-pattern/README.md b/rate-limiting-pattern/README.md new file mode 100644 index 000000000000..de7742a5d13f --- /dev/null +++ b/rate-limiting-pattern/README.md @@ -0,0 +1,328 @@ +--- +title: "Rate Limiting Pattern in Java: Controlling System Overload Gracefully" +shortTitle: Rate Limiting +description: "Explore multiple rate limiting strategies in Java—Token Bucket, Fixed Window, and Adaptive Limiting. Learn with diagrams, programmatic examples, and real-world simulation." +category: Behavioral +language: en +tag: + - Resilience + - System Overload Protection + - API Throttling + - Concurrency + - Cloud Patterns +--- + +## Also known as + +- Throttling +- Request Limiting +- API Rate Limiting + +--- + +## Intent of Rate Limiting Design Pattern + +To regulate the number of requests sent to a service in a specific time window, avoiding resource exhaustion and ensuring system stability. This is especially useful in distributed and cloud-native architectures. + +--- + +## Detailed Explanation of Rate Limiting with Real-World Examples + +### Real-world example + +Imagine you're entering a concert hall that only allows 50 people per minute. If too many fans arrive at once, the gate staff slows down entry, allowing only a few at a time. This prevents overcrowding and ensures safety. Similarly, the rate limiter controls how many requests are processed to avoid overloading a server. + +### In plain words + +Regulate the number of requests a system handles within a time frame to protect availability and performance. + + +### AWS says + +> "API Gateway limits the steady-state rate and burst rate of requests that it allows for each method in your REST APIs. When request rates exceed these limits, API Gateway begins to throttle requests." + +— [API Gateway quotas and important notes - AWS Documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) + +--- + +## Architecture Diagram + +![UML Class Diagram](etc/UMLClassDiagram.png) + +This UML shows the key components: +- `RateLimiter` interface +- `TokenBucketRateLimiter`, `FixedWindowRateLimiter`, `AdaptiveRateLimiter` +- Supporting exception classes +- `FindCustomerRequest` as a rate-limited operation + +--- + +## Flowcharts + +### 1. Token Bucket Strategy + +![Token Bucket Rate Limiter](etc/TokenBucketRateLimiter.png) + +### 2. Fixed Window Strategy + +![Fixed Window Rate Limiter](etc/FixedWindowRateLimiter.png) + +### 3. Adaptive Rate Limiting Strategy + +![Adaptive Rate Limiter](etc/AdaptiveRateLimiter.png) + +--- + +### Programmatic Example of Rate Limiter Pattern in Java + +The **Rate Limiter** design pattern helps protect systems from overload by restricting the number of operations that can be performed in a given time window. It is especially useful when accessing shared resources, APIs, or services that are sensitive to spikes in traffic. + +This implementation demonstrates three strategies for rate limiting: + +- **Token Bucket Rate Limiter** +- **Fixed Window Rate Limiter** +- **Adaptive Rate Limiter** + +Let’s walk through the key components. + +--- + +#### 1. Token Bucket Rate Limiter + +The token bucket allows short bursts followed by a steady rate. Tokens are added periodically and requests are only allowed if a token is available. + +```java +public class TokenBucketRateLimiter implements RateLimiter { + private final int capacity; + private final int refillRate; + private final ConcurrentHashMap buckets = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public TokenBucketRateLimiter(int capacity, int refillRate) { + this.capacity = capacity; + this.refillRate = refillRate; + scheduler.scheduleAtFixedRate(this::refillBuckets, 1, 1, TimeUnit.SECONDS); + } + + @Override + public void check(String serviceName, String operationName) throws RateLimitException { + String key = serviceName + ":" + operationName; + TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(capacity)); + if (!bucket.tryConsume()) { + throw new ThrottlingException(serviceName, operationName, 1000); + } + } + + private void refillBuckets() { + buckets.forEach((k, b) -> b.refill(refillRate)); + } + + private static class TokenBucket { + private final int capacity; + private final AtomicInteger tokens; + + TokenBucket(int capacity) { + this.capacity = capacity; + this.tokens = new AtomicInteger(capacity); + } + + boolean tryConsume() { + while (true) { + int current = tokens.get(); + if (current <= 0) return false; + if (tokens.compareAndSet(current, current - 1)) return true; + } + } + + void refill(int amount) { + tokens.getAndUpdate(current -> Math.min(current + amount, capacity)); + } + } +} +``` + +--- + +#### 2. Fixed Window Rate Limiter + +This strategy uses a simple counter within a fixed time window. + +```java +public class FixedWindowRateLimiter implements RateLimiter { + private final int limit; + private final long windowMillis; + private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); + + public FixedWindowRateLimiter(int limit, long windowSeconds) { + this.limit = limit; + this.windowMillis = TimeUnit.SECONDS.toMillis(windowSeconds); + } + + @Override + public synchronized void check(String serviceName, String operationName) throws RateLimitException { + String key = serviceName + ":" + operationName; + WindowCounter counter = counters.computeIfAbsent(key, k -> new WindowCounter()); + + if (!counter.tryIncrement()) { + throw new RateLimitException("Rate limit exceeded for " + key, windowMillis); + } + } + + private class WindowCounter { + private AtomicInteger count = new AtomicInteger(0); + private volatile long windowStart = System.currentTimeMillis(); + + synchronized boolean tryIncrement() { + long now = System.currentTimeMillis(); + if (now - windowStart > windowMillis) { + count.set(0); + windowStart = now; + } + return count.incrementAndGet() <= limit; + } + } +} +``` + +--- + +#### 3. Adaptive Rate Limiter + +This version adjusts the rate based on system health, reducing the rate when throttling occurs and recovering periodically. + +```java +public class AdaptiveRateLimiter implements RateLimiter { + private final int initialLimit; + private final int maxLimit; + private final AtomicInteger currentLimit; + private final ConcurrentHashMap limiters = new ConcurrentHashMap<>(); + private final ScheduledExecutorService healthChecker = Executors.newScheduledThreadPool(1); + + public AdaptiveRateLimiter(int initialLimit, int maxLimit) { + this.initialLimit = initialLimit; + this.maxLimit = maxLimit; + this.currentLimit = new AtomicInteger(initialLimit); + healthChecker.scheduleAtFixedRate(this::adjustLimits, 10, 10, TimeUnit.SECONDS); + } + + @Override + public void check(String serviceName, String operationName) throws RateLimitException { + String key = serviceName + ":" + operationName; + int current = currentLimit.get(); + RateLimiter limiter = limiters.computeIfAbsent(key, k -> new TokenBucketRateLimiter(current, current)); + + try { + limiter.check(serviceName, operationName); + } catch (RateLimitException e) { + currentLimit.updateAndGet(curr -> Math.max(initialLimit, curr / 2)); + throw e; + } + } + + private void adjustLimits() { + currentLimit.updateAndGet(curr -> Math.min(maxLimit, curr + (initialLimit / 2))); + } +} +``` + +--- + +#### 4. Simulated Demo Using All Limiters + +```java +public final class App { + public static void main(String[] args) { + TokenBucketRateLimiter tb = new TokenBucketRateLimiter(2, 1); + FixedWindowRateLimiter fw = new FixedWindowRateLimiter(3, 1); + AdaptiveRateLimiter ar = new AdaptiveRateLimiter(2, 6); + + ExecutorService executor = Executors.newFixedThreadPool(3); + for (int i = 1; i <= 3; i++) { + executor.submit(createClientTask(i, tb, fw, ar)); + } + } + + private static Runnable createClientTask(int clientId, RateLimiter tb, RateLimiter fw, RateLimiter ar) { + return () -> { + String[] services = {"s3", "dynamodb", "lambda"}; + String[] operations = {"GetObject", "PutObject", "Query", "Scan", "PutItem", "Invoke", "ListFunctions"}; + Random random = new Random(); + + while (true) { + String service = services[random.nextInt(services.length)]; + String operation = operations[random.nextInt(operations.length)]; + try { + switch (service) { + case "s3" -> tb.check(service, operation); + case "dynamodb" -> fw.check(service, operation); + case "lambda" -> ar.check(service, operation); + } + System.out.printf("Client %d: %s.%s - ALLOWED%n", clientId, service, operation); + } catch (RateLimitException e) { + System.out.printf("Client %d: %s.%s - THROTTLED%n", clientId, service, operation); + } + + try { + Thread.sleep(30 + random.nextInt(50)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }; + } +} +``` + +--- + +This example highlights how the Rate Limiter pattern supports various throttling techniques and how they respond under simulated traffic pressure, making it invaluable for building scalable, resilient systems. + +## When to Use Rate Limiting + +- APIs receiving unpredictable traffic +- Shared cloud resources (e.g., DB, compute) +- Services requiring fair client usage +- Preventing DoS or abuse + +--- + +## Real-World Applications + +- **AWS API Gateway** +- **Google Cloud Functions** +- **Netflix Zuul API Gateway** +- **Stripe API Throttling** + +--- + +## Benefits and Trade-offs + +### Benefits + +- Protects backend from overload +- Fair distribution of resources +- Better user experience under load + +### Trade-offs + +- May delay valid requests +- Requires tuning of limits +- Could create bottlenecks if misused + +--- + +## Related Java Design Patterns + +- [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) +- [Retry](https://java-design-patterns.com/patterns/retry/) +- [Throttling Queue](https://java-design-patterns.com/patterns/throttling/) + +--- + +## References and Credits + +- [Microsoft Cloud Design Patterns](https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling) +- [AWS API Gateway Throttling](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) +- *Designing Data-Intensive Applications* by Martin Kleppmann +- [Resilience4j](https://resilience4j.readme.io/) +- Java Design Patterns Project: [java-design-patterns](https://github.com/iluwatar/java-design-patterns) diff --git a/rate-limiting-pattern/etc/AdaptiveRateLimiter.png b/rate-limiting-pattern/etc/AdaptiveRateLimiter.png new file mode 100644 index 000000000000..2d849ee8d057 Binary files /dev/null and b/rate-limiting-pattern/etc/AdaptiveRateLimiter.png differ diff --git a/rate-limiting-pattern/etc/FixedWindowRateLimiter.png b/rate-limiting-pattern/etc/FixedWindowRateLimiter.png new file mode 100644 index 000000000000..a81f61d7fa95 Binary files /dev/null and b/rate-limiting-pattern/etc/FixedWindowRateLimiter.png differ diff --git a/rate-limiting-pattern/etc/TokenBucketRateLimiter.png b/rate-limiting-pattern/etc/TokenBucketRateLimiter.png new file mode 100644 index 000000000000..d41701781e27 Binary files /dev/null and b/rate-limiting-pattern/etc/TokenBucketRateLimiter.png differ diff --git a/rate-limiting-pattern/etc/UMLClassDiagram.png b/rate-limiting-pattern/etc/UMLClassDiagram.png new file mode 100644 index 000000000000..9292880244e0 Binary files /dev/null and b/rate-limiting-pattern/etc/UMLClassDiagram.png differ diff --git a/rate-limiting-pattern/pom.xml b/rate-limiting-pattern/pom.xml new file mode 100644 index 000000000000..ff1cfd9cf877 --- /dev/null +++ b/rate-limiting-pattern/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + rate-limiter + + + 22 + 22 + UTF-8 + 5.11.1 + 1.11.1 + + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + org.mockito + mockito-core + 5.12.0 + test + + + + org.slf4j + slf4j-api + 2.0.9 + + + + ch.qos.logback + logback-classic + 1.4.11 + + + + org.assertj + assertj-core + 3.24.2 + test + + + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.44.2 + + + + check + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + false + + + + + diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiter.java new file mode 100644 index 000000000000..1b18a8c941b9 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiter.java @@ -0,0 +1,50 @@ +package com.iluwatar.rate.limiting.pattern; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** Adaptive rate limiter that adjusts limits based on system health. */ +public class AdaptiveRateLimiter implements RateLimiter { + private final int initialLimit; + private final int maxLimit; + private final AtomicInteger currentLimit; + private final ConcurrentHashMap limiters = new ConcurrentHashMap<>(); + private final ScheduledExecutorService healthChecker = Executors.newScheduledThreadPool(1); + + public AdaptiveRateLimiter(int initialLimit, int maxLimit) { + this.initialLimit = initialLimit; + this.maxLimit = maxLimit; + this.currentLimit = new AtomicInteger(initialLimit); + // Periodically increase limit to recover if system appears healthy + healthChecker.scheduleAtFixedRate(this::adjustLimits, 10, 10, TimeUnit.SECONDS); + } + + @Override + public void check(String serviceName, String operationName) throws RateLimitException { + String key = serviceName + ":" + operationName; + int current = currentLimit.get(); + + // Reuse or create TokenBucket for this key using currentLimit + RateLimiter limiter = + limiters.computeIfAbsent(key, k -> new TokenBucketRateLimiter(current, current)); + + try { + limiter.check(serviceName, operationName); + System.out.printf( + "[Adaptive] Allowed %s.%s - CurrentLimit: %d%n", serviceName, operationName, current); + } catch (RateLimitException e) { + // On throttling, reduce system limit to reduce load + currentLimit.updateAndGet(curr -> Math.max(initialLimit, curr / 2)); + System.out.printf( + "[Adaptive] Throttled %s.%s - Decreasing limit to %d%n", + serviceName, operationName, currentLimit.get()); + throw e; + } + } + + // Periodic recovery mechanism to raise limits when the system is under control + private void adjustLimits() { + int updated = currentLimit.updateAndGet(curr -> Math.min(maxLimit, curr + (initialLimit / 2))); + System.out.printf("[Adaptive] Health check passed - Increasing limit to %d%n", updated); + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java new file mode 100644 index 000000000000..1e13ba90efd1 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java @@ -0,0 +1,178 @@ +package com.iluwatar.rate.limiting.pattern; + +import java.security.SecureRandom; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Rate Limiter pattern is a key defensive strategy used to prevent system overload and + * ensure fair usage of shared services. This demo showcases how different rate limiting techniques + * can regulate traffic in distributed systems. + * + *

    Specifically, this simulation implements three rate limiter strategies: + * + *

      + *
    • Token Bucket – Allows short bursts followed by steady request rates. + *
    • Fixed Window – Enforces a strict limit per discrete time window (e.g., 3 + * requests/sec). + *
    • Adaptive – Dynamically scales limits based on system health, simulating elastic + * backoff. + *
    + * + *

    Each simulated service (e.g., S3, DynamoDB, Lambda) is governed by one of these limiters. + * Multiple concurrent client threads issue randomized requests to these services over a fixed + * duration. Each request is either: + * + *

      + *
    • ALLOWED – Permitted under the current rate limit + *
    • THROTTLED – Rejected due to quota exhaustion + *
    • FAILED – Dropped due to transient service failure + *
    + * + *

    Statistics are printed every few seconds, and the simulation exits gracefully after a fixed + * runtime, offering a clear view into how each limiter behaves under pressure. + * + *

    Relation to AWS API Gateway:
    + * This implementation mirrors the throttling behavior described in the + * AWS API Gateway Request Throttling documentation, where limits are applied per second and + * over longer durations (burst and rate limits). The TokenBucketRateLimiter mimics + * burst capacity, the FixedWindowRateLimiter models steady rate enforcement, and the + * AdaptiveRateLimiter reflects elasticity in real-world systems like AWS Lambda under + * variable load. + */ +public final class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private static final int RUN_DURATION_SECONDS = 10; + private static final int SHUTDOWN_TIMEOUT_SECONDS = 5; + + static final AtomicInteger successfulRequests = new AtomicInteger(); + static final AtomicInteger throttledRequests = new AtomicInteger(); + static final AtomicInteger failedRequests = new AtomicInteger(); + static final AtomicBoolean running = new AtomicBoolean(true); + private static final String DIVIDER_LINE = "===================================="; + + public static void main(String[] args) { + LOGGER.info("Starting Rate Limiter Demo"); + LOGGER.info(DIVIDER_LINE); + + ExecutorService executor = Executors.newFixedThreadPool(3); + ScheduledExecutorService statsPrinter = Executors.newSingleThreadScheduledExecutor(); + + try { + TokenBucketRateLimiter tb = new TokenBucketRateLimiter(2, 1); + FixedWindowRateLimiter fw = new FixedWindowRateLimiter(3, 1); + AdaptiveRateLimiter ar = new AdaptiveRateLimiter(2, 6); + + statsPrinter.scheduleAtFixedRate(App::printStats, 2, 2, TimeUnit.SECONDS); + + for (int i = 1; i <= 3; i++) { + executor.submit(createClientTask(i, tb, fw, ar)); + } + + Thread.sleep(RUN_DURATION_SECONDS * 1000L); + LOGGER.info("Shutting down the demo..."); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + running.set(false); + shutdownExecutor(executor, "mainExecutor"); + shutdownExecutor(statsPrinter, "statsPrinter"); + printFinalStats(); + LOGGER.info("Demo completed."); + } + } + + private static void shutdownExecutor(ExecutorService service, String name) { + service.shutdown(); + try { + if (!service.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + service.shutdownNow(); + LOGGER.warn("Forced shutdown of {}", name); + } + } catch (InterruptedException e) { + service.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + static Runnable createClientTask( + int clientId, RateLimiter s3Limiter, RateLimiter dynamoDbLimiter, RateLimiter lambdaLimiter) { + + return () -> { + String[] services = {"s3", "dynamodb", "lambda"}; + String[] operations = { + "GetObject", "PutObject", "Query", "Scan", "PutItem", "Invoke", "ListFunctions" + }; + SecureRandom random = new SecureRandom(); + + while (running.get() && !Thread.currentThread().isInterrupted()) { + try { + String service = services[random.nextInt(services.length)]; + String operation = operations[random.nextInt(operations.length)]; + + switch (service) { + case "s3" -> makeRequest(clientId, s3Limiter, service, operation); + case "dynamodb" -> makeRequest(clientId, dynamoDbLimiter, service, operation); + case "lambda" -> makeRequest(clientId, lambdaLimiter, service, operation); + default -> LOGGER.warn("Unknown service: {}", service); + } + + Thread.sleep(30L + random.nextInt(50)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }; + } + + static void makeRequest(int clientId, RateLimiter limiter, String service, String operation) { + try { + limiter.check(service, operation); + successfulRequests.incrementAndGet(); + LOGGER.info("Client {}: {}.{} - ALLOWED", clientId, service, operation); + } catch (ThrottlingException e) { + throttledRequests.incrementAndGet(); + LOGGER.warn( + "Client {}: {}.{} - THROTTLED (Retry in {}ms)", + clientId, + service, + operation, + e.getRetryAfterMillis()); + } catch (ServiceUnavailableException e) { + failedRequests.incrementAndGet(); + LOGGER.warn("Client {}: {}.{} - SERVICE UNAVAILABLE", clientId, service, operation); + } catch (Exception e) { + failedRequests.incrementAndGet(); + LOGGER.error("Client {}: {}.{} - ERROR: {}", clientId, service, operation, e.getMessage()); + } + } + + static void printStats() { + if (!running.get()) return; + LOGGER.info("=== Current Statistics ==="); + LOGGER.info("Successful Requests: {}", successfulRequests.get()); + LOGGER.info("Throttled Requests : {}", throttledRequests.get()); + LOGGER.info("Failed Requests : {}", failedRequests.get()); + LOGGER.info(DIVIDER_LINE); + } + + static void printFinalStats() { + LOGGER.info("Final Statistics"); + LOGGER.info(DIVIDER_LINE); + LOGGER.info("Successful Requests: {}", successfulRequests.get()); + LOGGER.info("Throttled Requests : {}", throttledRequests.get()); + LOGGER.info("Failed Requests : {}", failedRequests.get()); + LOGGER.info(DIVIDER_LINE); + } + + static void resetCountersForTesting() { + successfulRequests.set(0); + throttledRequests.set(0); + failedRequests.set(0); + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java new file mode 100644 index 000000000000..a61fc35f496a --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java @@ -0,0 +1,40 @@ +package com.iluwatar.rate.limiting.pattern; + +/** + * A rate-limited customer lookup operation. This class wraps the rate limiting logic and represents + * an executable business request. + */ +public class FindCustomerRequest implements RateLimitOperation { + private final String customerId; + private final RateLimiter rateLimiter; + + public FindCustomerRequest(String customerId, RateLimiter rateLimiter) { + this.customerId = customerId; + this.rateLimiter = rateLimiter; + } + + @Override + public String getServiceName() { + return "CustomerService"; + } + + @Override + public String getOperationName() { + return "FindCustomer"; + } + + @Override + public String execute() throws RateLimitException { + // Ensure the operation respects the assigned rate limiter + rateLimiter.check(getServiceName(), getOperationName()); + + // Simulate actual operation + try { + Thread.sleep(50); // Simulate processing time + return "Customer-" + customerId; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ServiceUnavailableException(getServiceName(), 1000); + } + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java new file mode 100644 index 000000000000..3a2b52888ca7 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java @@ -0,0 +1,53 @@ +package com.iluwatar.rate.limiting.pattern; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implements a fixed window rate limiter. It allows up to 'limit' number of requests within a time + * window of fixed size. + */ +public class FixedWindowRateLimiter implements RateLimiter { + private final int limit; + private final long windowMillis; + private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); + + public FixedWindowRateLimiter(int limit, long windowSeconds) { + this.limit = limit; + this.windowMillis = TimeUnit.SECONDS.toMillis(windowSeconds); + } + + @Override + public synchronized void check(String serviceName, String operationName) + throws RateLimitException { + String key = serviceName + ":" + operationName; + WindowCounter counter = counters.computeIfAbsent(key, k -> new WindowCounter()); + + if (!counter.tryIncrement()) { + System.out.printf( + "[FixedWindow] Throttled %s.%s - Limit %d reached in window%n", + serviceName, operationName, limit); + throw new RateLimitException("Rate limit exceeded for " + key, windowMillis); + } else { + System.out.printf( + "[FixedWindow] Allowed %s.%s - Count within window%n", serviceName, operationName); + } + } + + /** Tracks the count of requests within the current window. */ + private class WindowCounter { + private AtomicInteger count = new AtomicInteger(0); + private volatile long windowStart = System.currentTimeMillis(); + + synchronized boolean tryIncrement() { + long now = System.currentTimeMillis(); + // Reset window if expired + if (now - windowStart > windowMillis) { + count.set(0); + windowStart = now; + } + // Enforce the request limit within window + return count.incrementAndGet() <= limit; + } + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java new file mode 100644 index 000000000000..2b3cc1f3006b --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java @@ -0,0 +1,15 @@ +package com.iluwatar.rate.limiting.pattern; + +/** Base exception for rate limiting errors. */ +public class RateLimitException extends Exception { + private final long retryAfterMillis; + + public RateLimitException(String message, long retryAfterMillis) { + super(message); + this.retryAfterMillis = retryAfterMillis; + } + + public long getRetryAfterMillis() { + return retryAfterMillis; + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java new file mode 100644 index 000000000000..59191e81fc04 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java @@ -0,0 +1,10 @@ +package com.iluwatar.rate.limiting.pattern; + +/** Represents a business operation that needs rate limiting. Supports type-safe return values. */ +public interface RateLimitOperation { + String getServiceName(); + + String getOperationName(); + + T execute() throws RateLimitException; +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java new file mode 100644 index 000000000000..19495b401ddb --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java @@ -0,0 +1,13 @@ +package com.iluwatar.rate.limiting.pattern; + +/** Base interface for all rate limiter strategies. */ +public interface RateLimiter { + /** + * Checks if a request is allowed under current rate limits + * + * @param serviceName Service being called (e.g., "dynamodb") + * @param operationName Operation being performed (e.g., "Query") + * @throws RateLimitException if request exceeds limits + */ + void check(String serviceName, String operationName) throws RateLimitException; +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java new file mode 100644 index 000000000000..f9fc55f15d20 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java @@ -0,0 +1,15 @@ +package com.iluwatar.rate.limiting.pattern; + +/** Exception for when a service is temporarily unavailable. */ +public class ServiceUnavailableException extends RateLimitException { + private final String serviceName; + + public ServiceUnavailableException(String serviceName, long retryAfterMillis) { + super("Service temporarily unavailable: " + serviceName, retryAfterMillis); + this.serviceName = serviceName; + } + + public String getServiceName() { + return serviceName; + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java new file mode 100644 index 000000000000..e07087dfee6c --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java @@ -0,0 +1,21 @@ +package com.iluwatar.rate.limiting.pattern; + +/** Exception thrown when AWS-style throttling occurs. */ +public class ThrottlingException extends RateLimitException { + private final String serviceName; + private final String errorCode; + + public ThrottlingException(String serviceName, String operationName, long retryAfterMillis) { + super("AWS throttling error for " + serviceName + "/" + operationName, retryAfterMillis); + this.serviceName = serviceName; + this.errorCode = "ThrottlingException"; + } + + public String getServiceName() { + return serviceName; + } + + public String getErrorCode() { + return errorCode; + } +} diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java new file mode 100644 index 000000000000..3001c880aad3 --- /dev/null +++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java @@ -0,0 +1,64 @@ +package com.iluwatar.rate.limiting.pattern; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Token Bucket rate limiter implementation. Allows requests to proceed as long as there are tokens + * available in the bucket. Tokens are added at a fixed interval up to a defined capacity. + */ +public class TokenBucketRateLimiter implements RateLimiter { + private final int capacity; + private final int refillRate; + private final ConcurrentHashMap buckets = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public TokenBucketRateLimiter(int capacity, int refillRate) { + this.capacity = capacity; + this.refillRate = refillRate; + // Refill tokens in all buckets every second + scheduler.scheduleAtFixedRate(this::refillBuckets, 1, 1, TimeUnit.SECONDS); + } + + @Override + public void check(String serviceName, String operationName) throws RateLimitException { + String key = serviceName + ":" + operationName; + TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(capacity)); + + if (!bucket.tryConsume()) { + System.out.printf( + "[TokenBucket] Throttled %s.%s - No tokens available%n", serviceName, operationName); + throw new ThrottlingException(serviceName, operationName, 1000); + } else { + System.out.printf( + "[TokenBucket] Allowed %s.%s - Tokens remaining%n", serviceName, operationName); + } + } + + private void refillBuckets() { + buckets.forEach((k, b) -> b.refill(refillRate)); + } + + /** Inner class that represents the bucket holding tokens for each service-operation. */ + private static class TokenBucket { + private final int capacity; + private final AtomicInteger tokens; + + TokenBucket(int capacity) { + this.capacity = capacity; + this.tokens = new AtomicInteger(capacity); + } + + boolean tryConsume() { + while (true) { + int current = tokens.get(); + if (current <= 0) return false; + if (tokens.compareAndSet(current, current - 1)) return true; + } + } + + void refill(int amount) { + tokens.getAndUpdate(current -> Math.min(current + amount, capacity)); + } + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java new file mode 100644 index 000000000000..042d606490d0 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AdaptiveRateLimiterTest { + @Test + void shouldDecreaseLimitWhenThrottled() throws Exception { + AdaptiveRateLimiter limiter = new AdaptiveRateLimiter(10, 20); + + // Exceed initial limit + for (int i = 0; i < 11; i++) { + try { + limiter.check("test", "op"); + } catch (RateLimitException e) { + // Expected after 10 requests + } + } + + // Verify limit was reduced + assertThrows( + RateLimitException.class, + () -> { + for (int i = 0; i < 6; i++) { // New limit should be 5 (10/2) + limiter.check("test", "op"); + } + }); + } + + @Test + void shouldGraduallyIncreaseLimitWhenHealthy() throws Exception { + AdaptiveRateLimiter limiter = + new AdaptiveRateLimiter(4, 10); // Start from 4 → expect 2 → expect increase to 4 + + // Force throttling to reduce limit + for (int i = 0; i < 5; i++) { + try { + limiter.check("test", "op"); + } catch (RateLimitException e) { + // Expected to throttle and reduce limit + } + } + + // Wait for health check to increase limit + Thread.sleep(11000); // Wait slightly more than 10 seconds + + // Allow up to 4 requests again (limit should've increased to 4) + for (int i = 0; i < 4; i++) { + limiter.check("test", "op"); + } + + // 5th should throw exception again + assertThrows(RateLimitException.class, () -> limiter.check("test", "op")); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTest.java new file mode 100644 index 000000000000..11815a75de84 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTest.java @@ -0,0 +1,59 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link App}. */ +class AppTest { + + private RateLimiter mockLimiter; + + @BeforeEach + void setUp() { + mockLimiter = mock(RateLimiter.class); + AppTestUtils.resetCounters(); // Ensures counters are clean before every test + } + + @Test + void shouldAllowRequest() { + AppTestUtils.invokeMakeRequest(1, mockLimiter, "s3", "GetObject"); + assertEquals(1, AppTestUtils.getSuccessfulRequests().get(), "Successful count should be 1"); + assertEquals(0, AppTestUtils.getThrottledRequests().get(), "Throttled count should be 0"); + assertEquals(0, AppTestUtils.getFailedRequests().get(), "Failed count should be 0"); + } + + @Test + void shouldHandleThrottlingException() throws Exception { + doThrow(new ThrottlingException("s3", "PutObject", 1000)).when(mockLimiter).check(any(), any()); + AppTestUtils.invokeMakeRequest(2, mockLimiter, "s3", "PutObject"); + assertEquals(0, AppTestUtils.getSuccessfulRequests().get()); + assertEquals(1, AppTestUtils.getThrottledRequests().get()); + assertEquals(0, AppTestUtils.getFailedRequests().get()); + } + + @Test + void shouldHandleServiceUnavailableException() throws Exception { + doThrow(new ServiceUnavailableException("lambda", 500)).when(mockLimiter).check(any(), any()); + AppTestUtils.invokeMakeRequest(3, mockLimiter, "lambda", "Invoke"); + assertEquals(0, AppTestUtils.getSuccessfulRequests().get()); + assertEquals(0, AppTestUtils.getThrottledRequests().get()); + assertEquals(1, AppTestUtils.getFailedRequests().get()); + } + + @Test + void shouldHandleGenericException() throws Exception { + doThrow(new RuntimeException("Unexpected")).when(mockLimiter).check(any(), any()); + AppTestUtils.invokeMakeRequest(4, mockLimiter, "dynamodb", "Query"); + assertEquals(0, AppTestUtils.getSuccessfulRequests().get()); + assertEquals(0, AppTestUtils.getThrottledRequests().get()); + assertEquals(1, AppTestUtils.getFailedRequests().get()); + } + + @Test + void shouldRunMainMethodWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTestUtils.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTestUtils.java new file mode 100644 index 000000000000..9d652ec96c63 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AppTestUtils.java @@ -0,0 +1,27 @@ +package com.iluwatar.rate.limiting.pattern; + +import java.util.concurrent.atomic.AtomicInteger; + +public class AppTestUtils { + + public static void invokeMakeRequest( + int clientId, RateLimiter limiter, String service, String operation) { + App.makeRequest(clientId, limiter, service, operation); + } + + public static void resetCounters() { + App.resetCountersForTesting(); + } + + public static AtomicInteger getSuccessfulRequests() { + return App.successfulRequests; + } + + public static AtomicInteger getThrottledRequests() { + return App.throttledRequests; + } + + public static AtomicInteger getFailedRequests() { + return App.failedRequests; + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java new file mode 100644 index 000000000000..1bee972b4af4 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java @@ -0,0 +1,97 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class ConcurrencyTests { + @Test + void tokenBucketShouldHandleConcurrentRequests() throws Exception { + int threadCount = 10; + int requestLimit = 5; + RateLimiter limiter = new TokenBucketRateLimiter(requestLimit, requestLimit); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + + AtomicInteger successCount = new AtomicInteger(); + AtomicInteger failureCount = new AtomicInteger(); + + try { + for (int i = 0; i < threadCount; i++) { + executor.submit( + () -> { + try { + limiter.check("test", "op"); + successCount.incrementAndGet(); + } catch (RateLimitException e) { + failureCount.incrementAndGet(); + } finally { + latch.countDown(); + } + }); + } + + assertTrue(latch.await(5, TimeUnit.SECONDS), "Timed out waiting for concurrent requests"); + assertEquals(requestLimit, successCount.get()); + assertEquals(threadCount - requestLimit, failureCount.get()); + } finally { + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } + } + + @Test + void adaptiveLimiterShouldAdjustUnderLoad() throws Exception { + AdaptiveRateLimiter limiter = new AdaptiveRateLimiter(10, 20); + ExecutorService executor = Executors.newFixedThreadPool(20); + CountDownLatch latch = new CountDownLatch(30); + + try { + // Flood with requests to trigger throttling + for (int i = 0; i < 30; i++) { + executor.submit( + () -> { + try { + limiter.check("test", "op"); + } catch (RateLimitException ignored) { + } finally { + latch.countDown(); + } + }); + } + + assertTrue(latch.await(5, TimeUnit.SECONDS), "Timed out waiting for flood requests"); + + // Verify new limit is in effect, polling until adjustment is observed or timing out. + int allowed = 0; + long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(15); + do { + allowed = 0; + for (int i = 0; i < 20; i++) { + try { + limiter.check("test", "op"); + allowed++; + } catch (RateLimitException ignored) { + } + } + + if (allowed > 5 && allowed < 15) { + break; + } + + TimeUnit.MILLISECONDS.sleep(100); + } while (System.nanoTime() < deadlineNanos); + + assertTrue(allowed > 5 && allowed < 15); // Should be between initial and max + } finally { + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java new file mode 100644 index 000000000000..a7b037fbe73f --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java @@ -0,0 +1,28 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ExceptionTests { + @Test + void rateLimitExceptionShouldContainRetryInfo() { + RateLimitException exception = new RateLimitException("Test", 1000); + assertEquals(1000, exception.getRetryAfterMillis()); + assertEquals("Test", exception.getMessage()); + } + + @Test + void throttlingExceptionShouldContainServiceInfo() { + ThrottlingException exception = new ThrottlingException("dynamodb", "Query", 500); + assertEquals("dynamodb", exception.getServiceName()); + assertEquals("ThrottlingException", exception.getErrorCode()); + } + + @Test + void serviceUnavailableExceptionShouldContainRetryInfo() { + ServiceUnavailableException exception = new ServiceUnavailableException("s3", 2000); + assertEquals("s3", exception.getServiceName()); + assertEquals(2000, exception.getRetryAfterMillis()); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java new file mode 100644 index 000000000000..d0c3197289cd --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class FindCustomerRequestTest implements RateLimitOperationTest { + + @Override + public RateLimitOperation createOperation(RateLimiter limiter) { + return new FindCustomerRequest("123", limiter); + } + + @Test + void shouldExecuteWhenUnderRateLimit() throws Exception { + RateLimiter limiter = new TokenBucketRateLimiter(10, 10); + RateLimitOperation request = createOperation(limiter); + + String result = request.execute(); + assertEquals("Customer-123", result); + } + + @Test + void shouldThrowWhenRateLimitExceeded() { + RateLimiter limiter = new TokenBucketRateLimiter(0, 0); // Always throttled + RateLimitOperation request = createOperation(limiter); + + assertThrows(RateLimitException.class, request::execute); + } + + @Test + void shouldReturnCorrectServiceAndOperationNames() { + RateLimiter limiter = new TokenBucketRateLimiter(10, 10); + FindCustomerRequest request = new FindCustomerRequest("123", limiter); + + assertEquals("CustomerService", request.getServiceName()); + assertEquals("FindCustomer", request.getOperationName()); + } + + // Reuse helper logic from the interface for coverage + @Test + void shouldExecuteUsingDefaultHelper() throws Exception { + RateLimiter limiter = new TokenBucketRateLimiter(5, 5); + shouldExecuteWhenUnderLimit(createOperation(limiter)); + } + + @Test + void shouldThrowServiceUnavailableOnInterruptedException() { + RateLimiter noOpLimiter = (service, operation) -> {}; // no throttling + + FindCustomerRequest request = + new FindCustomerRequest("999", noOpLimiter) { + @Override + public String execute() throws RateLimitException { + Thread.currentThread().interrupt(); // Simulate thread interruption + return super.execute(); // Should throw ServiceUnavailableException + } + }; + + assertThrows(ServiceUnavailableException.class, request::execute); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java new file mode 100644 index 000000000000..656185b7e68e --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class FixedWindowRateLimiterTest extends RateLimiterTest { + @Override + protected RateLimiter createRateLimiter(int limit, long windowMillis) { + return new FixedWindowRateLimiter(limit, windowMillis / 1000); + } + + @Test + void shouldResetCounterAfterWindow() throws Exception { + FixedWindowRateLimiter limiter = + new FixedWindowRateLimiter(1, 1); // 1 request per 1 second window + + // First request should pass + limiter.check("test", "op"); + + // Second request in same window should be throttled + assertThrows(RateLimitException.class, () -> limiter.check("test", "op")); + + // Wait a bit more than 1 second to ensure window resets + TimeUnit.MILLISECONDS.sleep(1100); + + // After window reset, this should pass again + limiter.check("test", "op"); + } + + @Test + void shouldNotAllowMoreThanLimitInWindow() throws Exception { + FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(3, 1); + for (int i = 0; i < 3; i++) { + limiter.check("test", "op"); + } + assertThrows(RateLimitException.class, () -> limiter.check("test", "op")); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java new file mode 100644 index 000000000000..f4bba939ca54 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java @@ -0,0 +1,21 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +interface RateLimitOperationTest { + + RateLimitOperation createOperation(RateLimiter limiter); + + @Test + default void shouldThrowWhenRateLimited() { + RateLimiter limiter = new TokenBucketRateLimiter(0, 0); // Always throttled + RateLimitOperation operation = createOperation(limiter); + assertThrows(RateLimitException.class, operation::execute); + } + + default void shouldExecuteWhenUnderLimit(RateLimitOperation operation) throws Exception { + assertNotNull(operation.execute()); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java new file mode 100644 index 000000000000..7f1e6b4a2806 --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public abstract class RateLimiterTest { + protected abstract RateLimiter createRateLimiter(int limit, long windowMillis); + + @Test + void shouldAllowRequestsWithinLimit() throws Exception { + RateLimiter limiter = createRateLimiter(5, 1000); + for (int i = 0; i < 5; i++) { + limiter.check("test", "op"); + } + } + + @Test + void shouldThrowWhenLimitExceeded() throws Exception { + RateLimiter limiter = createRateLimiter(2, 1000); + limiter.check("test", "op"); + limiter.check("test", "op"); + assertThrows(RateLimitException.class, () -> limiter.check("test", "op")); + } +} diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java new file mode 100644 index 000000000000..5696299512fb --- /dev/null +++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.rate.limiting.pattern; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class TokenBucketRateLimiterTest extends RateLimiterTest { + @Override + protected RateLimiter createRateLimiter(int limit, long windowMillis) { + return new TokenBucketRateLimiter(limit, (int) (limit * 1000 / windowMillis)); + } + + @Test + void shouldAllowBurstRequests() throws Exception { + TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(10, 5); + for (int i = 0; i < 10; i++) { + limiter.check("test", "op"); + } + } + + @Test + void shouldRefillTokensAfterTime() throws Exception { + TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1, 1); + limiter.check("test", "op"); + assertThrows(RateLimitException.class, () -> limiter.check("test", "op")); + + TimeUnit.SECONDS.sleep(1); + limiter.check("test", "op"); + } + + @Test + void shouldHandleMultipleServicesSeparately() throws Exception { + TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1, 1); + limiter.check("service1", "op"); + limiter.check("service2", "op"); + assertThrows(RateLimitException.class, () -> limiter.check("service1", "op")); + } +} diff --git a/reactor/README.md b/reactor/README.md index 50ee482cb15a..ce6109111056 100644 --- a/reactor/README.md +++ b/reactor/README.md @@ -38,6 +38,10 @@ Wikipedia says > The reactor software design pattern is an event handling strategy that can respond to many potential service requests concurrently. The pattern's key component is an event loop, running in a single thread or process, which demultiplexes incoming requests and dispatches them to the correct request handler. +Sequence diagram + +![Reactor Sequence diagram](./etc/reactor-sequence-diagram.png) + ## Programmatic Example of Reactor Pattern in Java The Reactor design pattern is a concurrency model that efficiently handles multiple simultaneous I/O operations using a single or a limited number of threads. It is particularly useful in scenarios where an application needs to handle multiple clients sending service requests concurrently. @@ -157,10 +161,6 @@ Running the code produces the following output: This concludes our detailed explanation of the Reactor design pattern. The Reactor pattern allows us to handle multiple simultaneous I/O operations efficiently using a single or a limited number of threads. -## Detailed Explanation of Reactor Pattern with Real-World Examples - -![Reactor](./etc/reactor.png "Reactor") - ## When to Use the Reactor Pattern in Java Employ the Reactor pattern in scenarios requiring low-latency and high-throughput in server-side applications, making it an essential strategy for modern networking frameworks and web servers. diff --git a/reactor/etc/reactor-sequence-diagram.png b/reactor/etc/reactor-sequence-diagram.png new file mode 100644 index 000000000000..11b7cc83e5fc Binary files /dev/null and b/reactor/etc/reactor-sequence-diagram.png differ diff --git a/registry/README.md b/registry/README.md index aa40bb80941d..82834ffe8b2f 100644 --- a/registry/README.md +++ b/registry/README.md @@ -38,6 +38,10 @@ wiki.c2.com says > A registry is a global association from keys to objects, allowing the objects to be reached from anywhere. It involves two methods: one that takes a key and an object and add objects to the registry and one that takes a key and returns the object for the key. It's similar to the MultitonPattern, but doesn't restrict instances to only those in the registry. +Flowchart + +![Registry flowchart](./etc/registry-flowchart.png) + ## Programmatic Example of Registry Pattern in Java The Registry design pattern is a well-known pattern used in software design where objects are stored and provide a global point of access to them. This pattern is particularly useful when you need to manage a global collection of objects, decouple the creation of objects from their usage, ensure a controlled lifecycle for objects, and avoid redundant creation of objects. diff --git a/registry/etc/registry-flowchart.png b/registry/etc/registry-flowchart.png new file mode 100644 index 000000000000..e77c429a702b Binary files /dev/null and b/registry/etc/registry-flowchart.png differ diff --git a/repository/README.md b/repository/README.md index e2be527a2bcb..f29d0c98e61b 100644 --- a/repository/README.md +++ b/repository/README.md @@ -29,6 +29,10 @@ In plain words > Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. +Sequence diagram + +![Repository sequence diagram](./etc/repository-sequence-diagram.png) + ## Programmatic Example of Repository Pattern in Java Let's first look at the person entity that we need to persist. diff --git a/repository/etc/repository-sequence-diagram.png b/repository/etc/repository-sequence-diagram.png new file mode 100644 index 000000000000..45eae098780d Binary files /dev/null and b/repository/etc/repository-sequence-diagram.png differ diff --git a/resource-acquisition-is-initialization/README.md b/resource-acquisition-is-initialization/README.md index 5c6105dce96c..9d140ebba41d 100644 --- a/resource-acquisition-is-initialization/README.md +++ b/resource-acquisition-is-initialization/README.md @@ -33,6 +33,10 @@ Wikipedia says > Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically typed programming languages to describe a particular language behavior. Resource allocation (or acquisition) is done during object creation (specifically initialization), by the constructor, while resource deallocation (release) is done during object destruction (specifically finalization), by the destructor. +Sequence diagram + +![Resource Acquisition Is Initialization sequence diagram](./etc/raii-sequence-diagram.png) + ## Programmatic Example of RAII Pattern in Java The RAII pattern is a common idiom used in software design where the acquisition of a resource is done during object creation (initialization), and the release of the resource is done during object destruction. This pattern is particularly useful in dealing with resource leaks and is critical in writing exception-safe code in C++. In Java, RAII is achieved with try-with-resources statement and interfaces `java.io.Closeable` and `AutoCloseable`. diff --git a/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png b/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png new file mode 100644 index 000000000000..212ecdc9e596 Binary files /dev/null and b/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png differ diff --git a/retry/README.md b/retry/README.md index 193866e436b5..cb221f77c7f7 100644 --- a/retry/README.md +++ b/retry/README.md @@ -34,6 +34,10 @@ In plain words > Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application. +Flowchart + +![Retry flowchart](./etc/retry-flowchart.png) + ## Programmatic Example of Retry Pattern in Java The Retry design pattern is a resilience pattern that allows an application to transparently attempt to execute operations multiple times in the expectation that it'll succeed. This pattern is particularly useful when the application is connecting to a network service or a remote resource, where temporary failures are common. diff --git a/retry/etc/retry-flowchart.png b/retry/etc/retry-flowchart.png new file mode 100644 index 000000000000..30b3262e6a73 Binary files /dev/null and b/retry/etc/retry-flowchart.png differ diff --git a/role-object/README.md b/role-object/README.md index 98b29bb284fb..b4d79cc1f6da 100644 --- a/role-object/README.md +++ b/role-object/README.md @@ -32,6 +32,10 @@ wiki.c2.com says > Adapt an object to different client’s needs through transparently attached role objects, each one representing a role the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as individual objects, different contexts are kept separate and system configuration is simplified. +Sequence diagram + +![Role Object sequence diagram](./etc/role-object-sequence-diagram.png) + ## Programmatic Example of Role Object Pattern in Java The Role Object design pattern is a pattern that suggests modeling context-specific views of an object as separate role objects. These role objects are dynamically attached to and removed from the core object. The resulting composite object structure, consisting of the core and its role objects, is called a subject. A subject often plays several roles and the same role is likely to be played by different subjects. diff --git a/role-object/etc/role-object-sequence-diagram.png b/role-object/etc/role-object-sequence-diagram.png new file mode 100644 index 000000000000..a63319d80114 Binary files /dev/null and b/role-object/etc/role-object-sequence-diagram.png differ diff --git a/saga/README.md b/saga/README.md index 47709dd68ba4..d715f026af67 100644 --- a/saga/README.md +++ b/saga/README.md @@ -31,6 +31,10 @@ Wikipedia says > Long-running transactions (also known as the saga interaction pattern) are computer database transactions that avoid locks on non-local resources, use compensation to handle failures, potentially aggregate smaller ACID transactions (also referred to as atomic transactions), and typically use a coordinator to complete or abort the transaction. In contrast to rollback in ACID transactions, compensation restores the original state, or an equivalent, and is business-specific. For example, the compensating action for making a hotel reservation is canceling that reservation. +Flowchart + +![Saga flowchart](./etc/saga-flowchart.png) + ## Programmatic Example of Saga Pattern in Java The Saga design pattern is a sequence of local transactions where each transaction updates data within a single service. It's particularly useful in a microservices architecture where each service has its own database. The Saga pattern ensures data consistency and fault tolerance across services. Here are the key components of the Saga pattern: diff --git a/saga/etc/saga-flowchart.png b/saga/etc/saga-flowchart.png new file mode 100644 index 000000000000..5bbffc8f2d0b Binary files /dev/null and b/saga/etc/saga-flowchart.png differ diff --git a/separated-interface/README.md b/separated-interface/README.md index 1b26f40aa255..4497e360c618 100644 --- a/separated-interface/README.md +++ b/separated-interface/README.md @@ -31,6 +31,10 @@ In plain words > Defines a client interface separate from its implementation to allow for flexible and interchangeable components. +Sequence diagram + +![Separated Interface sequence diagram](./etc/separated-interface-sequence-diagram.png) + ## Programmatic Example of Separated Interface Pattern in Java The Java Separated Interface design pattern is a crucial software architecture strategy that promotes separating the interface definition from its implementation, crucial for enhancing system flexibility and scalability. This allows the client to be completely unaware of the implementation, promoting loose coupling and enhancing flexibility. diff --git a/separated-interface/etc/separated-interface-sequence-diagram.png b/separated-interface/etc/separated-interface-sequence-diagram.png new file mode 100644 index 000000000000..162690d41176 Binary files /dev/null and b/separated-interface/etc/separated-interface-sequence-diagram.png differ diff --git a/serialized-entity/README.md b/serialized-entity/README.md index f7306dcdfa50..70a700bb358b 100644 --- a/serialized-entity/README.md +++ b/serialized-entity/README.md @@ -32,6 +32,10 @@ Wikipedia says > In computing, serialization is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devices, data buffers in primary storage devices) or transmitted (e.g. data streams over computer networks) and reconstructed later (possibly in a different computer environment). When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object. For many complex objects, such as those that make extensive use of references, this process is not straightforward. Serialization of objects does not include any of their associated methods with which they were previously linked. +Flowchart + +![Serialized Entity flowchart](./etc/serialized-entity-flowchart.png) + ## Programmatic Example of Serialized Entity Pattern in Java The Serialized Entity design pattern is a way to easily persist Java objects to the database. It uses the `Serializable` interface and the DAO (Data Access Object) pattern. The pattern first uses `Serializable` to convert a Java object into a set of bytes, then it uses the DAO pattern to store this set of bytes as a BLOB (Binary Large OBject) in the database. diff --git a/serialized-entity/etc/serialized-entity-flowchart.png b/serialized-entity/etc/serialized-entity-flowchart.png new file mode 100644 index 000000000000..0f382155e9ab Binary files /dev/null and b/serialized-entity/etc/serialized-entity-flowchart.png differ diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java index 36d7baceb081..2aeac7097dfa 100644 --- a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java @@ -77,7 +77,11 @@ void testSerializable() { try { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("output.txt")); - Country country = (Country) objectInputStream.readObject(); + Object obj = objectInputStream.readObject(); + if (!(obj instanceof Country)) { + throw new ClassCastException("Deserialized object is not a Country instance"); + } + Country country = (Country) obj; objectInputStream.close(); System.out.println(country); diff --git a/serialized-lob/README.md b/serialized-lob/README.md index 06eea05f4b08..79b26ae2d184 100644 --- a/serialized-lob/README.md +++ b/serialized-lob/README.md @@ -30,6 +30,10 @@ In plain words > The Serialized LOB design pattern manages the storage of large objects, such as files or multimedia, by serializing and storing them directly within a database. +Flowchart + +![Serialized LOB flowchart](./etc/serialized-lob-flowchart.png) + ## Programmatic Example of Serialized LOB Pattern in Java The Serialized Large Object (LOB) design pattern is a way to handle large objects in a database. It involves serializing an object graph into a single large object (a BLOB or CLOB, for Binary Large Object or Character Large Object, respectively) and storing it in the database. When the object graph needs to be retrieved, it is read from the database and deserialized back into the original object graph. diff --git a/serialized-lob/etc/serialized-lob-flowchart.png b/serialized-lob/etc/serialized-lob-flowchart.png new file mode 100644 index 000000000000..075c7e238cf7 Binary files /dev/null and b/serialized-lob/etc/serialized-lob-flowchart.png differ diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java index f9fe3a7a7e70..70b025148f7a 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java @@ -76,7 +76,12 @@ public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoun InputStream bis = (InputStream) toDeserialize; Forest forest; try (ObjectInput in = new ObjectInputStream(bis)) { - forest = (Forest) in.readObject(); + Object obj = in.readObject(); + if (!(obj instanceof Forest)) { + throw new ClassCastException("Deserialized object is not a Forest instance. " + + "Expected Forest but got " + (obj != null ? obj.getClass().getName() : "null")); + } + forest = (Forest) obj; } return forest; } diff --git a/servant/README.md b/servant/README.md index 67fb496abd15..a5e4643b325a 100644 --- a/servant/README.md +++ b/servant/README.md @@ -34,6 +34,10 @@ Wikipedia says > In software engineering, the servant pattern defines an object used to offer some functionality to a group of classes without defining that functionality in each of them. A Servant is a class whose instance (or even just class) provides methods that take care of a desired service, while objects for which (or with whom) the servant does something, are taken as parameters. +Sequence diagram + +![Servant sequence diagram](./etc/servant-sequence-diagram.png) + ## Programmatic Example of Servant Pattern in Java The Servant design pattern is a behavioral design pattern that defines a class that provides some sort of service to a group of classes. This pattern is particularly useful when these classes lack some common functionality that can't be added to the superclass. The Servant class brings this common functionality to a group of classes. diff --git a/servant/etc/servant-sequence-diagram.png b/servant/etc/servant-sequence-diagram.png new file mode 100644 index 000000000000..1039676ec495 Binary files /dev/null and b/servant/etc/servant-sequence-diagram.png differ diff --git a/server-session/README.md b/server-session/README.md index 4ce452b53f7d..a6054889e4d9 100644 --- a/server-session/README.md +++ b/server-session/README.md @@ -34,6 +34,10 @@ Wikipedia says > A session token is a unique identifier that is generated and sent from a server to a client to identify the current interaction session. The client usually stores and sends the token as an HTTP cookie and/or sends it as a parameter in GET or POST queries. The reason to use session tokens is that the client only has to handle the identifier—all session data is stored on the server (usually in a database, to which the client does not have direct access) linked to that identifier. +Sequence diagram + +![Server Session sequence diagram](./etc/server-session-sequence-diagram.png) + ## Programmatic Example of Server Session Pattern in Java The Server Session design pattern is a behavioral design pattern that assigns the responsibility of storing session data on the server side. This pattern is particularly useful in the context of stateless protocols like HTTP where all requests are isolated events independent of previous requests. diff --git a/server-session/etc/server-session-sequence-diagram.png b/server-session/etc/server-session-sequence-diagram.png new file mode 100644 index 000000000000..034196da7566 Binary files /dev/null and b/server-session/etc/server-session-sequence-diagram.png differ diff --git a/service-layer/README.md b/service-layer/README.md index 1d737980e541..f2dc54115884 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -40,7 +40,6 @@ Architecture diagram ![Service Layer Architecture Diagram](./etc/service-layer-architecture-diagram.png) - ## Programmatic Example of Service Layer Pattern in Java Our Java implementation uses the Service Layer pattern to streamline interactions between data access objects (DAOs) and the business logic, ensuring a clean separation of concerns. @@ -358,10 +357,6 @@ INFO [2024-05-27 09:16:40,681] com.iluwatar.servicelayer.app.App: Find wizards INFO [2024-05-27 09:16:40,683] com.iluwatar.servicelayer.app.App: Aderlard Boud has 'Fireball' ``` -## Detailed Explanation of Service Layer Pattern with Real-World Examples - -![Service Layer](./etc/service-layer.png "Service Layer") - ## When to Use the Service Layer Pattern in Java * Use when you need to separate business logic from presentation logic. diff --git a/service-locator/README.md b/service-locator/README.md index 68c803e70a14..7e8e25d10031 100644 --- a/service-locator/README.md +++ b/service-locator/README.md @@ -33,6 +33,10 @@ Wikipedia says > The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task. Proponents of the pattern say the approach simplifies component-based applications where all dependencies are cleanly listed at the beginning of the whole application design, consequently making traditional dependency injection a more complex way of connecting objects. Critics of the pattern argue that it is an antipattern which obscures dependencies and makes software harder to test. +Sequence diagram + +![Service Locator sequence diagram](./etc/service-locator-sequence-diagram.png) + ## Programmatic Example of Service Locator Pattern in Java The Service Locator design pattern is used to abstract the processes involved in obtaining a service. It uses a central registry, the "service locator", which returns the necessary information to perform a task upon request. This Java design pattern is particularly useful in enterprise Java applications where services need centralized management. diff --git a/service-locator/etc/service-locator-sequence-diagram.png b/service-locator/etc/service-locator-sequence-diagram.png new file mode 100644 index 000000000000..3987b03927cf Binary files /dev/null and b/service-locator/etc/service-locator-sequence-diagram.png differ diff --git a/service-stub/README.md b/service-stub/README.md index 4058b96257d6..5fa4533587ea 100644 --- a/service-stub/README.md +++ b/service-stub/README.md @@ -2,29 +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: - - Testing + - API design - Decoupling - - Dummy Services - - Dependency Injection + - 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 @@ -34,23 +35,30 @@ Wikipedia says > A test stub is a dummy component used during testing to isolate behavior. +Sequence diagram + +![Service Stub sequence diagram](./etc/service-stub-sequence-diagram.png) + ## 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: + +**RealSentimentAnalysisServer**: Represents a slow, realistic sentiment analysis service, returning random sentiment results to simulate external complexity and latency. -1. **RealSentimentAnalysisServer**: Simulates a slow, random sentiment analysis system. -2. **StubSentimentAnalysisServer**: Returns a deterministic result based on input keywords. +**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 { @@ -76,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 { @@ -94,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) { @@ -115,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/etc/service-stub-sequence-diagram.png b/service-stub/etc/service-stub-sequence-diagram.png new file mode 100644 index 000000000000..de83741a52d8 Binary files /dev/null and b/service-stub/etc/service-stub-sequence-diagram.png differ 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/service-to-worker/README.md b/service-to-worker/README.md index fb234342c79f..0c1c7bd4ba4d 100644 --- a/service-to-worker/README.md +++ b/service-to-worker/README.md @@ -26,6 +26,10 @@ In plain words > Separates the processing logic from the view in web applications to improve maintainability and scalability. +Sequence diagram + +![Service to Worker sequence diagram](./etc/service-to-worker-sequence-diagram.png) + ## Programmatic Example of Service to Worker Pattern in Java The Service to Worker design pattern separates the processing logic from the view in web applications to improve maintainability and scalability. It combines the Dispatcher View and Service Locator patterns to facilitate the separation of processing, control flow, and view management in web applications. diff --git a/service-to-worker/etc/service-to-worker-sequence-diagram.png b/service-to-worker/etc/service-to-worker-sequence-diagram.png new file mode 100644 index 000000000000..e33a04be8b2f Binary files /dev/null and b/service-to-worker/etc/service-to-worker-sequence-diagram.png differ diff --git a/session-facade/README.md b/session-facade/README.md index 20a093334e92..fb8fe74d7f50 100644 --- a/session-facade/README.md +++ b/session-facade/README.md @@ -5,45 +5,51 @@ description: "Learn how to implement the Session Facade Design Pattern in Java t category: Structural language: en tag: - - Abstraction - - API design - - Code simplification - - Decoupling - - Encapsulation - - Gang Of Four - - Interface + - 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 + +![Session Facade sequence diagram](./etc/session-facade-sequence-diagram.png) ## 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, 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 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 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. -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. +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) { @@ -55,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.")); @@ -72,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 @@ -117,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/etc/session-facade-sequence-diagram.png b/session-facade/etc/session-facade-sequence-diagram.png new file mode 100644 index 000000000000..b70d461ce0c4 Binary files /dev/null and b/session-facade/etc/session-facade-sequence-diagram.png differ 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/sharding/README.md b/sharding/README.md index fef24abee7b6..19915038d546 100644 --- a/sharding/README.md +++ b/sharding/README.md @@ -35,6 +35,10 @@ Wikipedia says > > There are numerous advantages to the horizontal partitioning approach. Since the tables are divided and distributed into multiple servers, the total number of rows in each table in each database is reduced. This reduces index size, which generally improves search performance. A database shard can be placed on separate hardware, and multiple shards can be placed on multiple machines. This enables a distribution of the database over a large number of machines, greatly improving performance. In addition, if the database shard is based on some real-world segmentation of the data (e.g., European customers v. American customers) then it may be possible to infer the appropriate shard membership easily and automatically, and query only the relevant shard. +Flowchart + +![Sharding flowchart](./etc/sharding-flowchart.png) + ## Programmatic Example of Sharding Pattern in Java Sharding is a type of database partitioning that separates very large databases into smaller, faster, more easily managed parts called data shards. The word shard means a small part of a whole. In software architecture, it refers to a horizontal partition in a database or search engine. Each individual partition is referred to as a shard or database shard. diff --git a/sharding/etc/sharding-flowchart.png b/sharding/etc/sharding-flowchart.png new file mode 100644 index 000000000000..c43f100b308d Binary files /dev/null and b/sharding/etc/sharding-flowchart.png differ diff --git a/single-table-inheritance/README.md b/single-table-inheritance/README.md index 0ed1f6eadae1..632b93bf3200 100644 --- a/single-table-inheritance/README.md +++ b/single-table-inheritance/README.md @@ -36,6 +36,10 @@ Wikipedia says > Single table inheritance is a way to emulate object-oriented inheritance in a relational database. When mapping from a database table to an object in an object-oriented language, a field in the database identifies what class in the hierarchy the object belongs to. All fields of all the classes are stored in the same table, hence the name "Single Table Inheritance". +Flowchart + +![Single Table Inheritance flowchart](./etc/single-table-inheritance-flowchart.png) + ## Programmatic Example of Single Table Inheritance Pattern in Java Single Table Inheritance is a design pattern that maps an inheritance hierarchy of classes to a single database table. Each row in the table represents an instance of a class in the hierarchy. A special discriminator column is used to identify the class to which each row belongs. diff --git a/single-table-inheritance/etc/single-table-inheritance-flowchart.png b/single-table-inheritance/etc/single-table-inheritance-flowchart.png new file mode 100644 index 000000000000..e70b6c6627d5 Binary files /dev/null and b/single-table-inheritance/etc/single-table-inheritance-flowchart.png differ diff --git a/singleton/README.md b/singleton/README.md index 626a2ed659bf..92505061399e 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -33,6 +33,10 @@ Wikipedia says > In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. +Sequence diagram + +![Singleton Pattern sequence diagram](./etc/singleton-sequence-diagram.png) + ## Programmatic Example of Singleton Pattern in Java Joshua Bloch, Effective Java 2nd Edition p.18 diff --git a/singleton/etc/singleton-sequence-diagram.png b/singleton/etc/singleton-sequence-diagram.png new file mode 100644 index 000000000000..0de3569a50b6 Binary files /dev/null and b/singleton/etc/singleton-sequence-diagram.png differ diff --git a/spatial-partition/README.md b/spatial-partition/README.md index 26f070d5b6c0..0a13b6f26970 100644 --- a/spatial-partition/README.md +++ b/spatial-partition/README.md @@ -39,6 +39,10 @@ Wikipedia says > > For example, in ray tracing, space partitioning helps quickly determine the objects a ray might intersect by narrowing down the search space, leading to faster rendering times. Similarly, in game development, Quadtrees can efficiently manage 2D game environments by segmenting the space into smaller regions, facilitating quicker collision detection and rendering. +Flowchart + +![Spatial Partition flowchart](./etc/spatial-partition-flowchart.png) + ## Programmatic Example of Spatial Partition Pattern in Java The Spatial Partition design pattern in Java is a strategic approach for handling multiple objects in expansive game worlds or detailed simulation environments, boosting query efficiency and operational speed. It allows us to efficiently manage these objects and perform operations like collision detection or range queries. The pattern works by dividing the space into smaller, manageable regions, and each object is associated with the region it belongs to. This way, we can limit our operations to a specific region, instead of checking every object against every other object. diff --git a/spatial-partition/etc/spatial-partition-flowchart.png b/spatial-partition/etc/spatial-partition-flowchart.png new file mode 100644 index 000000000000..0e287932d97f Binary files /dev/null and b/spatial-partition/etc/spatial-partition-flowchart.png differ diff --git a/special-case/README.md b/special-case/README.md index eda6ad0ff797..dd460276759e 100644 --- a/special-case/README.md +++ b/special-case/README.md @@ -37,6 +37,10 @@ In [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) Ma > If you’ll pardon the unresistable pun, I see [Null Object](https://java-design-patterns.com/patterns/null-object/) as special case of Special Case. +Sequence diagram + +![Special Case sequence diagram](./etc/special-case-sequence-diagram.png) + ## Programmatic Example of Special Case Pattern in Java The Special Case Pattern is a software design pattern that is used to handle a specific, often uncommon, case separately from the general case in the code. This pattern is useful when a class has behavior that requires conditional logic based on its state. Instead of cluttering the class with conditional logic, we can encapsulate the special behavior in a subclass. diff --git a/special-case/etc/special-case-sequence-diagram.png b/special-case/etc/special-case-sequence-diagram.png new file mode 100644 index 000000000000..87842ec4cfa1 Binary files /dev/null and b/special-case/etc/special-case-sequence-diagram.png differ diff --git a/specification/README.md b/specification/README.md index 4ccb68cc55de..9310555ecbeb 100644 --- a/specification/README.md +++ b/specification/README.md @@ -37,6 +37,10 @@ Wikipedia says > In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. +Flowchart + +![Specification Pattern flowchart](./etc/specification-flowchart.png) + ## Programmatic Example of Specification Pattern in Java Let's consider a creature pool example. We have a collection of creatures with specific properties. These properties might belong to a predefined, limited set (represented by enums like `Size`, `Movement`, and `Color`) or they might be continuous values (e.g., the mass of a `Creature`). In cases with continuous values, it's better to use a "parameterized specification," where the property value is provided as an argument when the `Creature` is instantiated, allowing for greater flexibility. Additionally, predefined and/or parameterized properties can be combined using boolean logic, offering almost limitless selection possibilities (this is known as a "composite specification," explained further below). The advantages and disadvantages of each approach are detailed in the table at the end of this document. diff --git a/specification/etc/specification-flowchart.png b/specification/etc/specification-flowchart.png new file mode 100644 index 000000000000..73bc446f7646 Binary files /dev/null and b/specification/etc/specification-flowchart.png differ diff --git a/state/README.md b/state/README.md index 7fdc242aefef..b35768834bf5 100644 --- a/state/README.md +++ b/state/README.md @@ -38,6 +38,10 @@ Wikipedia says > The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface. +Flowchart + +![State flowchart](./etc/state-flowchart.png) + ## Programmatic Example of State Pattern in Java In our programmatic example there is a mammoth with alternating moods. diff --git a/state/etc/state-flowchart.png b/state/etc/state-flowchart.png new file mode 100644 index 000000000000..fb49c9e0ace2 Binary files /dev/null and b/state/etc/state-flowchart.png differ diff --git a/step-builder/README.md b/step-builder/README.md index a651cff45642..3b0f4c479720 100644 --- a/step-builder/README.md +++ b/step-builder/README.md @@ -35,6 +35,10 @@ Wikipedia says > The Step Builder pattern is a variation of the Builder design pattern, designed to provide a flexible solution for constructing complex objects step-by-step. This pattern is particularly useful when an object requires multiple initialization steps, which can be done incrementally to ensure clarity and flexibility in the creation process. +Sequence diagram + +![Step Builder sequence diagram](./etc/step-builder-sequence-diagram.png) + ## Programmatic Example of Step Builder Pattern in Java The Step Builder pattern in Java is an extension of the Builder pattern that guides the user through the creation of an object in a step-by-step manner. This pattern improves the user experience by only showing the next step methods available, and not showing the build method until it's the right time to build the object. @@ -162,10 +166,6 @@ Console output: 12:58:13.889 [main] INFO com.iluwatar.stepbuilder.App -- This is a Rogue named Desmond armed with a with nothing. ``` -## Detailed Explanation of Step Builder Pattern with Real-World Examples - -![Step Builder](./etc/step-builder.png "Step Builder") - ## When to Use the Step Builder Pattern in Java The Step Builder pattern in Java is used diff --git a/step-builder/etc/step-builder-sequence-diagram.png b/step-builder/etc/step-builder-sequence-diagram.png new file mode 100644 index 000000000000..c2c45821963a Binary files /dev/null and b/step-builder/etc/step-builder-sequence-diagram.png differ diff --git a/strangler/README.md b/strangler/README.md index 569a2151f38d..22baf44ec541 100644 --- a/strangler/README.md +++ b/strangler/README.md @@ -32,6 +32,10 @@ Wikipedia says > The Strangler Design Pattern involves incrementally migrating a legacy system by gradually replacing it with a new system. It wraps old code with new code, redirecting or logging uses of the old code to ensure a seamless transition. This pattern is named after the strangler fig plant, which grows around a host tree and eventually replaces it entirely. It's particularly useful for modernizing monolithic applications and transitioning them to microservices architecture with minimal risk and disruption. +Flowchart + +![Strangler flowchart](./etc/strangler-flowchart.png) + ## Programmatic Example of Strangler Pattern in Java The Strangler design pattern in Java is a software design pattern that incrementally migrates a legacy system by gradually replacing specific pieces of functionality with new applications and services. As features from the legacy system are replaced, the new system eventually replaces all the old system's features, strangling the old system and allowing you to decommission it. diff --git a/strangler/etc/strangler-flowchart.png b/strangler/etc/strangler-flowchart.png new file mode 100644 index 000000000000..20574690967b Binary files /dev/null and b/strangler/etc/strangler-flowchart.png differ diff --git a/strategy/README.md b/strategy/README.md index 781463d20af8..9f9148ccd842 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -34,6 +34,10 @@ Wikipedia says > In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. +Flowchart + +![Strategy flowchart](./etc/strategy-flowchart.png) + ## Programmatic Example of Strategy Pattern in Java Slaying dragons is a dangerous job. With experience, it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons. diff --git a/strategy/etc/strategy-flowchart.png b/strategy/etc/strategy-flowchart.png new file mode 100644 index 000000000000..18623f248e00 Binary files /dev/null and b/strategy/etc/strategy-flowchart.png differ diff --git a/subclass-sandbox/README.md b/subclass-sandbox/README.md index 1dbea163ed19..ffe2727492d2 100644 --- a/subclass-sandbox/README.md +++ b/subclass-sandbox/README.md @@ -35,6 +35,10 @@ In plain words > A base class defines an abstract sandbox method and several provided operations. Marking them protected makes it clear that they are for use by derived classes. Each derived sandboxed subclass implements the sandbox method using the provided operations. +Flowchart + +![Subclass Sandbox flowchart](./etc/subclass-sandbox-flowchart.png) + ## Programmatic Example of Subclass Sandbox Pattern in Java Using the Subclass Sandbox pattern, developers can create distinct functionalities within Java applications, enhancing game development and software design. diff --git a/subclass-sandbox/etc/subclass-sandbox-flowchart.png b/subclass-sandbox/etc/subclass-sandbox-flowchart.png new file mode 100644 index 000000000000..580d2c03018c Binary files /dev/null and b/subclass-sandbox/etc/subclass-sandbox-flowchart.png differ diff --git a/table-inheritance/README.md b/table-inheritance/README.md index 3e3ad4f53bf9..fc9f7500286c 100644 --- a/table-inheritance/README.md +++ b/table-inheritance/README.md @@ -2,200 +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: Data Access Pattern, Structural Pattern +category: Data access language: en tag: -- Decoupling -- Inheritance -- Polymorphism -- Object Mapping -- Persistence -- Data Transformation + - Data access + - Database + - Inheritance + - Persistence + - Polymorphism --- -## Also Known As -- Class Table Inheritance ---- +## 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 +![Table Inheritance Pattern Mind Map](./etc/table-inheritance-mind-map.png) ## 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(); } diff --git a/table-module/README.md b/table-module/README.md index e69892bb7227..a613d687901e 100644 --- a/table-module/README.md +++ b/table-module/README.md @@ -28,6 +28,10 @@ In plain words > The Table Module pattern centralizes and encapsulates database access logic for a specific table, simplifying data retrieval and manipulation while hiding database complexities. +Flowchart + +![Table Module flowchart](./etc/table-module-flowchart.png) + ## Programmatic Example of Table Module Pattern in Java In the user system example, the domain logic for user login and registration needs to be managed. By using the Table Module pattern, we can create an instance of the `UserTableModule` class to encapsulate and handle all business logic associated with the rows in the user table. diff --git a/table-module/etc/table-module-flowchart.png b/table-module/etc/table-module-flowchart.png new file mode 100644 index 000000000000..489d4c8bed59 Binary files /dev/null and b/table-module/etc/table-module-flowchart.png differ diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/User.java b/table-module/src/main/java/com/iluwatar/tablemodule/User.java index 4af0f73dcee1..220172d10323 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/User.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/User.java @@ -39,5 +39,12 @@ public class User { private int id; private String username; - private String password; + private char[] password; + + /** Secure password cleanup. Must be called when done with authentication. */ + public void clearPassword() { + if (password != null) { + java.util.Arrays.fill(password, '\0'); + } + } } diff --git a/template-method/README.md b/template-method/README.md index a05d77ff03be..187b5cd6dd0c 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -34,6 +34,10 @@ Wikipedia says > In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method. +Sequence diagram + +![Template Method Pattern Sequence Diagram](./etc/template-method-sequence-diagram.png) + ## Programmatic Example of Template Method Pattern in Java Our programmatic example is about thieves and stealing. The general steps in stealing an item are the same. First, you pick the target, next you confuse him somehow and finally, you steal the item. However, there are many ways to implement these steps. diff --git a/template-method/etc/template-method-sequence-diagram.png b/template-method/etc/template-method-sequence-diagram.png new file mode 100644 index 000000000000..57cd0809f4ee Binary files /dev/null and b/template-method/etc/template-method-sequence-diagram.png differ diff --git a/templateview/README.md b/templateview/README.md index 3a53447c2cff..6708fa2274a7 100644 --- a/templateview/README.md +++ b/templateview/README.md @@ -21,18 +21,22 @@ Separate the structure and static parts of a webpage (or view) from its dynamic ## Detailed Explanation of Template View Pattern with Real-World Examples -### Real-World Example +Real-World Example > Think of a blog website where each post page follows the same layout with a header, footer, and main content area. While the header and footer remain consistent, the main content differs for each blog post. The Template View pattern encapsulates the shared layout (header and footer) in a base class while delegating the rendering of the main content to subclasses. -### In Plain Words +In Plain Words > The Template View pattern provides a way to define a consistent layout in a base class while letting subclasses implement the specific, dynamic content for different views. -### Wikipedia Says +Wikipedia Says > While not a classic Gang of Four pattern, Template View aligns closely with the Template Method pattern, applied specifically to rendering webpages or views. It defines a skeleton for rendering, delegating dynamic parts to subclasses while keeping the structure consistent. +Flowchart + +![Template View Pattern Flowchart](./etc/template-view-flowchart.png) + ## Programmatic Example of Template View Pattern in Java Our example involves rendering different types of views (`HomePageView` and `ContactPageView`) with a common structure consisting of a header, dynamic content, and a footer. diff --git a/templateview/etc/template-view-flowchart.png b/templateview/etc/template-view-flowchart.png new file mode 100644 index 000000000000..0887ac8bce4e Binary files /dev/null and b/templateview/etc/template-view-flowchart.png differ diff --git a/thread-pool-executor/README.md b/thread-pool-executor/README.md new file mode 100644 index 000000000000..fa1d5a2c8062 --- /dev/null +++ b/thread-pool-executor/README.md @@ -0,0 +1,200 @@ +--- +title: "Thread-Pool Executor Pattern in Java: Efficient Concurrent Task Management" +shortTitle: Thread-Pool Executor +description: "Learn the Thread-Pool Executor pattern in Java with practical examples, class +diagrams, and implementation details. Understand how to manage concurrent tasks efficiently, +improving resource utilization and application performance." +category: Concurrency +language: en +tag: + +- Performance +- Resource Management +- Concurrency +- Multithreading +- Scalability + +--- + +## Intent of Thread-Pool Executor Design Pattern + +The Thread-Pool Executor pattern maintains a pool of worker threads to execute tasks concurrently, +optimizing resource usage by reusing existing threads instead of creating new ones for each task. + +## Detailed Explanation of Thread-Pool Executor Pattern with Real-World Examples + +### Real-world example + +> Imagine a busy airport security checkpoint where instead of opening a new lane for each traveler, +> a fixed number of security lanes (threads) are open to process all passengers. Each security +> officer (thread) processes one passenger (task) at a time, and when finished, immediately calls the +> next passenger in line. During peak travel times, passengers wait in a queue, but the system is much +> more efficient than trying to open a new security lane for each individual traveler. The airport can +> handle fluctuating passenger traffic throughout the day with consistent staffing, optimizing both +> resource utilization and passenger throughput. + +### In plain words + +> Thread-Pool Executor keeps a set of reusable threads that process multiple tasks throughout their +> lifecycle, rather than creating a new thread for each task. + +### Wikipedia says + +> A thread pool is a software design pattern for achieving concurrency of execution in a computer +> program. Often also called a replicated workers or worker-crew model, a thread pool maintains +> multiple threads waiting for tasks to be allocated for concurrent execution by the supervising +> program. + +### Class diagram + +![Thread-pool-executor Class diagram](./etc/thread-pool-executor.urm.png) + +## Programmatic Example of Thread-Pool Executor Pattern in Java + +Imagine a hotel front desk. + +The number of employees (thread pool) is limited, but guests (tasks) keep arriving endlessly. + +The Thread-Pool Executor pattern efficiently handles a large number of requests by reusing a small +set of threads. + +```java +@Slf4j +public class HotelFrontDesk { + public static void main(String[] args) throws InterruptedException, ExecutionException { + // Hire 3 front desk employees (threads) + ExecutorService frontDesk = Executors.newFixedThreadPool(3); + + LOGGER.info("Hotel front desk operation started!"); + + // 7 regular guests checking in (Runnable) + for (int i = 1; i <= 7; i++) { + String guestName = "Guest-" + i; + frontDesk.submit(() -> { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in {}...", employeeName, guestName); + try { + Thread.sleep(2000); // Simulate check-in time + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + LOGGER.info("{} has been successfully checked in!", guestName); + }); + } + + // 3 VIP guests checking in (Callable with result) + Callable vipGuest1 = createVipGuest("VIP-Guest-1"); + Callable vipGuest2 = createVipGuest("VIP-Guest-2"); + Callable vipGuest3 = createVipGuest("VIP-Guest-3"); + + Future vipResult1 = frontDesk.submit(vipGuest1); + Future vipResult2 = frontDesk.submit(vipGuest2); + Future vipResult3 = frontDesk.submit(vipGuest3); + + // Shutdown after submitting all tasks + frontDesk.shutdown(); + + if (frontDesk.awaitTermination(1, TimeUnit.HOURS)) { + // Print VIP guests' check-in results + LOGGER.info("VIP Check-in Results:"); + LOGGER.info(vipResult1.get()); + LOGGER.info(vipResult2.get()); + LOGGER.info(vipResult3.get()); + LOGGER.info("All guests have been successfully checked in. Front desk is now closed."); + } else { + LOGGER.info("Check-in timeout. Forcefully shutting down the front desk."); + } + } + + private static Callable createVipGuest(String vipGuestName) { + return () -> { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in VIP guest {}...", employeeName, vipGuestName); + Thread.sleep(1000); // VIPs are faster to check in + return vipGuestName + " has been successfully checked in!"; + }; + } +} +``` + +Here's the console output: + +```markdown +Hotel front desk operation started! +pool-1-thread-3 is checking in Guest-3... +pool-1-thread-2 is checking in Guest-2... +pool-1-thread-1 is checking in Guest-1... +Guest-2 has been successfully checked in! +Guest-1 has been successfully checked in! +Guest-3 has been successfully checked in! +pool-1-thread-2 is checking in Guest-5... +pool-1-thread-3 is checking in Guest-4... +pool-1-thread-1 is checking in Guest-6... +Guest-5 has been successfully checked in! +pool-1-thread-2 is checking in Guest-7... +Guest-4 has been successfully checked in! +pool-1-thread-3 is checking in VIP guest VIP-Guest-1... +Guest-6 has been successfully checked in! +pool-1-thread-1 is checking in VIP guest VIP-Guest-2... +pool-1-thread-3 is checking in VIP guest VIP-Guest-3... +Guest-7 has been successfully checked in! +VIP Check-in Results: +VIP-Guest-1 has been successfully checked in! +VIP-Guest-2 has been successfully checked in! +VIP-Guest-3 has been successfully checked in! +All guests have been successfully checked in. Front desk is now closed. +``` + +**Note:** Since this example demonstrates asynchronous thread execution, **the actual output may vary between runs**. The order of execution and timing can differ due to thread scheduling, system load, and other factors that affect concurrent processing. The core behavior of the thread pool (limiting concurrent tasks to the number of threads and reusing threads) will remain consistent, but the exact sequence of log messages may change with each execution. + +## When to Use the Thread-Pool Executor Pattern in Java + +* When you need to limit the number of threads running simultaneously to avoid resource exhaustion +* For applications that process a large number of short-lived independent tasks +* To improve performance by reducing thread creation/destruction overhead +* When implementing server applications that handle multiple client requests concurrently +* To execute recurring tasks at fixed rates or with fixed delays + +## Thread-Pool Executor Pattern Java Tutorial + +* [Thread-Pool Executor Pattern Tutorial (Baeldung)](https://www.baeldung.com/thread-pool-java-and-guava) + +## Real-World Applications of Thread-Pool Executor Pattern in Java + +* Application servers like Tomcat and Jetty use thread pools to handle HTTP requests +* Database connection pools in JDBC implementations +* Background job processing frameworks like Spring Batch +* Task scheduling systems like Quartz Scheduler +* Java EE's Managed Executor Service for enterprise applications + +## Benefits and Trade-offs of Thread-Pool Executor Pattern + +### Benefits + +* Improves performance by reusing existing threads instead of creating new ones +* Provides better resource management by limiting the number of active threads +* Simplifies thread lifecycle management and cleanup +* Facilitates easy implementation of task prioritization and scheduling +* Enhances application stability by preventing resource exhaustion + +### Trade-offs + +* May lead to thread starvation if improperly configured (too few threads) +* Potential for resource underutilization if improperly sized (too many threads) +* Requires careful shutdown handling to prevent task loss or resource leaks + +## Related Java Design Patterns + +* [Master-Worker Pattern](https://java-design-patterns.com/patterns/master-worker/): Tasks between a + master and multiple workers. +* [Producer-Consumer Pattern](https://java-design-patterns.com/patterns/producer-consumer/): + Separates task production and task consumption, typically using a blocking queue. +* [Object Pool Pattern](https://java-design-patterns.com/patterns/object-pool/): Reuses a set of + objects (e.g., threads) instead of creating/destroying them repeatedly. + +## References and Credits + +* [Java Documentation for ThreadPoolExecutor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html) +* [Java Concurrency in Practice](https://jcip.net/) by Brian Goetz +* [Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) by Joshua + Bloch diff --git a/thread-pool-executor/etc/thread-pool-executor.urm.png b/thread-pool-executor/etc/thread-pool-executor.urm.png new file mode 100644 index 000000000000..d426343f8171 Binary files /dev/null and b/thread-pool-executor/etc/thread-pool-executor.urm.png differ diff --git a/thread-pool-executor/etc/thread-pool-executor.urm.puml b/thread-pool-executor/etc/thread-pool-executor.urm.puml new file mode 100644 index 000000000000..ca83f40c1b40 --- /dev/null +++ b/thread-pool-executor/etc/thread-pool-executor.urm.puml @@ -0,0 +1,66 @@ +@startuml + +interface Runnable { + +run(): void +} + +interface Callable { + +call(): T +} + +interface ExecutorService { + +submit(task: Runnable): Future + +submit(task: Callable): Future + +shutdown(): void + +awaitTermination(timeout: long, unit: TimeUnit): boolean +} + +class ThreadPoolExecutor { + -corePoolSize: int + -maximumPoolSize: int + -keepAliveTime: long + -workQueue: BlockingQueue + +execute(task: Runnable): void + +submit(task: Callable): Future +} + +class ThreadPoolManager { + -executorService: ExecutorService + +ThreadPoolManager(numThreads: int) + +submitTask(task: Runnable): void + +submitCallable(task: Callable): Future + +shutdown(): void + +awaitTermination(timeout: long, unit: TimeUnit): boolean +} + +class Task { + -id: int + -name: String + -processingTime: long + +Task(id: int, name: String, processingTime: long) + +run(): void + +call(): TaskResult +} + +class TaskResult { + -taskId: int + -taskName: String + -executionTime: long + +TaskResult(taskId: int, taskName: String, executionTime: long) +} + +class App { + +main(args: String[]): void + -executeRunnableTasks(poolManager: ThreadPoolManager): void + -executeCallableTasks(poolManager: ThreadPoolManager): void +} + +ExecutorService <|-- ThreadPoolExecutor : implements +Task ..|> Runnable : implements +Task ..|> Callable : implements +Task --> TaskResult : produces +ThreadPoolManager --> ExecutorService : wraps +App --> ThreadPoolManager : uses +App --> Task : creates + +@enduml \ No newline at end of file diff --git a/thread-pool-executor/pom.xml b/thread-pool-executor/pom.xml new file mode 100644 index 000000000000..f77cd92c67aa --- /dev/null +++ b/thread-pool-executor/pom.xml @@ -0,0 +1,83 @@ + + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + thread-pool-executor + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.threadpoolexecutor.App + + + + + + + + + diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java new file mode 100644 index 000000000000..0c1292b89c3a --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java @@ -0,0 +1,90 @@ +/* + * 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.threadpoolexecutor; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * The Thread-Pool Executor pattern demonstrates how a pool of worker threads can be used to execute + * tasks concurrently. This pattern is particularly useful in scenarios where you need to execute a + * large number of independent tasks and want to limit the number of threads used. + * + *

    In this example, a hotel front desk with a fixed number of employees processes guest + * check-ins. Each employee is represented by a thread, and each check-in is a task. + * + *

    Key benefits demonstrated: + * + *

      + *
    • Resource management - Limiting the number of concurrent threads + *
    • Efficiency - Reusing threads instead of creating new ones for each task + *
    • Responsiveness - Handling many requests with limited resources + *
    + */ +@Slf4j +public class App { + + /** + * Program main entry point. + * + * @param args program runtime arguments + */ + public static void main(String[] args) throws InterruptedException, ExecutionException { + + FrontDeskService frontDesk = new FrontDeskService(5); + LOGGER.info("Hotel front desk operation started!"); + + LOGGER.info("Processing 30 regular guest check-ins..."); + for (int i = 1; i <= 30; i++) { + frontDesk.submitGuestCheckIn(new GuestCheckInTask("Guest-" + i)); + Thread.sleep(100); + } + + LOGGER.info("Processing 3 VIP guest check-ins..."); + List> vipResults = new ArrayList<>(); + + for (int i = 1; i <= 3; i++) { + Future result = + frontDesk.submitVipGuestCheckIn(new VipGuestCheckInTask("VIP-Guest-" + i)); + vipResults.add(result); + } + + frontDesk.shutdown(); + + if (frontDesk.awaitTermination(1, TimeUnit.HOURS)) { + LOGGER.info("VIP Check-in Results:"); + for (Future result : vipResults) { + LOGGER.info(result.get()); + } + LOGGER.info("All guests have been successfully checked in. Front desk is now closed."); + } else { + LOGGER.warn("Check-in timeout. Forcefully shutting down the front desk."); + } + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java new file mode 100644 index 000000000000..b80236ee5ecf --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java @@ -0,0 +1,108 @@ +/* + * 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.threadpoolexecutor; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * FrontDeskService represents the hotel's front desk with a fixed number of employees. This class + * demonstrates the Thread-Pool Executor pattern using Java's ExecutorService. + */ +@Slf4j +public class FrontDeskService { + + private final ExecutorService executorService; + private final int numberOfEmployees; + + /** + * Creates a new front desk with the specified number of employees. + * + * @param numberOfEmployees the number of employees (threads) at the front desk + */ + public FrontDeskService(int numberOfEmployees) { + this.numberOfEmployees = numberOfEmployees; + this.executorService = Executors.newFixedThreadPool(numberOfEmployees); + LOGGER.info("Front desk initialized with {} employees.", numberOfEmployees); + } + + /** + * Submits a regular guest check-in task to an available employee. + * + * @param task the check-in task to submit + * @return a Future representing pending completion of the task + */ + public Future submitGuestCheckIn(Runnable task) { + LOGGER.debug("Submitting regular guest check-in task"); + return executorService.submit(task, null); + } + + /** + * Submits a VIP guest check-in task to an available employee. + * + * @param task the VIP check-in task to submit + * @param the type of the task's result + * @return a Future representing pending completion of the task + */ + public Future submitVipGuestCheckIn(Callable task) { + LOGGER.debug("Submitting VIP guest check-in task"); + return executorService.submit(task); + } + + /** + * Closes the front desk after all currently checked-in guests are processed. No new check-ins + * will be accepted. + */ + public void shutdown() { + LOGGER.info("Front desk is closing - no new guests will be accepted."); + executorService.shutdown(); + } + + /** + * Waits for all check-in processes to complete or until timeout. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return true if all tasks completed, false if timeout elapsed + * @throws InterruptedException if interrupted while waiting + */ + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + LOGGER.info("Waiting for all check-ins to complete (max wait: {} {})", timeout, unit); + return executorService.awaitTermination(timeout, unit); + } + + /** + * Gets the number of employees at the front desk. + * + * @return the number of employees + */ + public int getNumberOfEmployees() { + return numberOfEmployees; + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java new file mode 100644 index 000000000000..d8a33fdfc8d2 --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java @@ -0,0 +1,52 @@ +/* + * 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.threadpoolexecutor; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * GuestCheckInTask represents a regular guest check-in process. Implements Runnable because it + * performs an action without returning a result. + */ +@Slf4j +@AllArgsConstructor +public class GuestCheckInTask implements Runnable { + + private final String guestName; + + @Override + public void run() { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in {}...", employeeName, guestName); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Check-in for {} was interrupted", guestName); + } + LOGGER.info("{} has been successfully checked in!", guestName); + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java new file mode 100644 index 000000000000..3948c114f0d6 --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java @@ -0,0 +1,52 @@ +/* + * 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.threadpoolexecutor; + +import java.util.concurrent.Callable; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * VipGuestCheckInTask represents a VIP guest check-in process. Implements Callable because it + * returns a result (check-in confirmation). + */ +@Slf4j +@AllArgsConstructor +public class VipGuestCheckInTask implements Callable { + + private final String vipGuestName; + + @Override + public String call() throws Exception { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in VIP guest {}...", employeeName, vipGuestName); + + Thread.sleep(1000); + + String result = vipGuestName + " has been successfully checked in!"; + LOGGER.info("VIP check-in completed: {}", result); + return result; + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java new file mode 100644 index 000000000000..13e3a5beec3c --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java @@ -0,0 +1,38 @@ +/* + * 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.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class AppTest { + + @Test + void appStartsWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java new file mode 100644 index 000000000000..8d0396bf0541 --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java @@ -0,0 +1,248 @@ +/* + * 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.threadpoolexecutor; + +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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class FrontDeskServiceTest { + + /** + * Tests that the constructor correctly sets the number of employees (threads). This verifies the + * basic initialization of the thread pool. + */ + @Test + void testConstructorSetsCorrectNumberOfEmployees() { + int expectedEmployees = 3; + + FrontDeskService frontDesk = new FrontDeskService(expectedEmployees); + + assertEquals(expectedEmployees, frontDesk.getNumberOfEmployees()); + } + + /** + * Tests that the submitGuestCheckIn method returns a non-null Future object. This verifies the + * basic task submission functionality. + */ + @Test + void testSubmitGuestCheckInReturnsNonNullFuture() { + FrontDeskService frontDesk = new FrontDeskService(1); + + Runnable task = + () -> { + // Task that completes quickly + }; + + Future future = frontDesk.submitGuestCheckIn(task); + + assertNotNull(future); + } + + /** + * Tests that the submitVipGuestCheckIn method returns a non-null Future object. This verifies + * that tasks with return values can be submitted correctly. + */ + @Test + void testSubmitVipGuestCheckInReturnsNonNullFuture() { + FrontDeskService frontDesk = new FrontDeskService(1); + Callable task = () -> "VIP Check-in complete"; + + Future future = frontDesk.submitVipGuestCheckIn(task); + + assertNotNull(future); + } + + /** + * Tests that the shutdown and awaitTermination methods work correctly. This verifies the basic + * shutdown functionality of the thread pool. + */ + @Test + void testShutdownAndAwaitTermination() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + CountDownLatch taskLatch = new CountDownLatch(1); + + Runnable task = taskLatch::countDown; + + frontDesk.submitGuestCheckIn(task); + frontDesk.shutdown(); + boolean terminated = frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(terminated); + assertTrue(taskLatch.await(100, TimeUnit.MILLISECONDS)); + } + + /** + * Tests the thread pool's behavior under load with multiple tasks. This verifies that the thread + * pool limits concurrent execution to the number of threads, all submitted tasks are eventually + * completed, and threads are reused for multiple tasks. + */ + @Test + void testMultipleTasksUnderLoad() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + int taskCount = 10; + CountDownLatch tasksCompletedLatch = new CountDownLatch(taskCount); + AtomicInteger concurrentTasks = new AtomicInteger(0); + AtomicInteger maxConcurrentTasks = new AtomicInteger(0); + + for (int i = 0; i < taskCount; i++) { + frontDesk.submitGuestCheckIn( + () -> { + try { + int current = concurrentTasks.incrementAndGet(); + maxConcurrentTasks.updateAndGet(max -> Math.max(max, current)); + + Thread.sleep(100); + + concurrentTasks.decrementAndGet(); + tasksCompletedLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + boolean allTasksCompleted = tasksCompletedLatch.await(2, TimeUnit.SECONDS); + + frontDesk.shutdown(); + frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allTasksCompleted); + assertEquals(2, maxConcurrentTasks.get()); + assertEquals(0, concurrentTasks.get()); + } + + /** + * Tests proper shutdown behavior under load. This verifies that after shutdown no new tasks are + * accepted, all previously submitted tasks are completed, and the executor terminates properly + * after all tasks complete. + */ + @Test + void testProperShutdownUnderLoad() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + int taskCount = 5; + CountDownLatch startedTasksLatch = new CountDownLatch(2); + CountDownLatch tasksCompletionLatch = new CountDownLatch(taskCount); + + for (int i = 0; i < taskCount; i++) { + frontDesk.submitGuestCheckIn( + () -> { + try { + startedTasksLatch.countDown(); + Thread.sleep(100); + tasksCompletionLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + assertTrue(startedTasksLatch.await(1, TimeUnit.SECONDS)); + + frontDesk.shutdown(); + + assertThrows( + RejectedExecutionException.class, + () -> { + frontDesk.submitGuestCheckIn(() -> {}); + }); + + boolean allTasksCompleted = tasksCompletionLatch.await(2, TimeUnit.SECONDS); + + boolean terminated = frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allTasksCompleted); + assertTrue(terminated); + } + + /** + * Tests concurrent execution of different task types (regular and VIP). This verifies that both + * Runnable and Callable tasks can be processed concurrently, all tasks complete successfully, and + * Callable tasks return their results correctly. + */ + @Test + void testConcurrentRegularAndVipTasks() throws Exception { + FrontDeskService frontDesk = new FrontDeskService(3); + int regularTaskCount = 4; + int vipTaskCount = 3; + CountDownLatch allTasksLatch = new CountDownLatch(regularTaskCount + vipTaskCount); + + List> regularResults = new ArrayList<>(); + for (int i = 0; i < regularTaskCount; i++) { + Future result = + frontDesk.submitGuestCheckIn( + () -> { + try { + Thread.sleep(50); + allTasksLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + regularResults.add(result); + } + + List> vipResults = new ArrayList<>(); + for (int i = 0; i < vipTaskCount; i++) { + final int guestNum = i; + Future result = + frontDesk.submitVipGuestCheckIn( + () -> { + Thread.sleep(25); + allTasksLatch.countDown(); + return "VIP-" + guestNum + " checked in"; + }); + vipResults.add(result); + } + + boolean allCompleted = allTasksLatch.await(2, TimeUnit.SECONDS); + + frontDesk.shutdown(); + frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allCompleted); + + for (Future result : regularResults) { + assertTrue(result.isDone()); + } + + for (int i = 0; i < vipTaskCount; i++) { + Future result = vipResults.get(i); + assertTrue(result.isDone()); + assertEquals("VIP-" + i + " checked in", result.get()); + } + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java new file mode 100644 index 000000000000..27bb75efd2db --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java @@ -0,0 +1,55 @@ +/* + * 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.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +class GuestCheckInTaskTest { + + /** + * Tests that the task executes in the current thread when called directly. This verifies that the + * thread name inside the task matches the calling thread. + */ + @Test + void testThreadNameInTask() { + String guestName = "TestGuest"; + AtomicReference capturedThreadName = new AtomicReference<>(); + + GuestCheckInTask task = + new GuestCheckInTask(guestName) { + @Override + public void run() { + capturedThreadName.set(Thread.currentThread().getName()); + } + }; + + task.run(); + + assertEquals(Thread.currentThread().getName(), capturedThreadName.get()); + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java new file mode 100644 index 000000000000..d76d90625c95 --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java @@ -0,0 +1,48 @@ +/* + * 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.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +class VipGuestCheckInTaskTest { + + /** + * Tests that the call method returns the expected result string. This verifies that the VIP + * check-in task correctly formats its result message. + */ + @Test + void testCallReturnsExpectedResult() throws Exception { + String vipGuestName = "TestVipGuest"; + VipGuestCheckInTask task = new VipGuestCheckInTask(vipGuestName); + + String result = task.call(); + + assertNotNull(result); + assertEquals("TestVipGuest has been successfully checked in!", result); + } +} diff --git a/throttling/README.md b/throttling/README.md index d35c346deea0..ab5f92fbeeeb 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -34,6 +34,10 @@ In plain words > Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources. +Flowchart + +![Throttling Pattern Flowchart](./etc/throttling-flowchart.png) + ## Programmatic Example of Throttling Pattern in Java In this Java example, we demonstrate throttling. A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. The bartender immediately sees that the young human shouldn't consume too many drinks too fast and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can be higher. diff --git a/throttling/etc/throttling-flowchart.png b/throttling/etc/throttling-flowchart.png new file mode 100644 index 000000000000..0c7d6cc329b0 Binary files /dev/null and b/throttling/etc/throttling-flowchart.png differ diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index 720764214dd4..a6c21a64a44a 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -33,6 +33,10 @@ In plain words > Be conservative in what you do, be liberal in what you accept from others. +Sequence diagram + +![Tolerant Reader sequence diagrams](./etc/tolerant-reader-sequence-diagram.png) + ## Programmatic Example of Tolerant Reader Pattern in Java We are persisting `RainbowFish` objects to file. Later on they need to be restored. What makes it problematic is that `RainbowFish` data structure is versioned and evolves over time. New version of `RainbowFish` needs to be able to restore old versions as well. diff --git a/tolerant-reader/etc/tolerant-reader-sequence-diagram.png b/tolerant-reader/etc/tolerant-reader-sequence-diagram.png new file mode 100644 index 000000000000..26e68ccffcaf Binary files /dev/null and b/tolerant-reader/etc/tolerant-reader-sequence-diagram.png differ diff --git a/trampoline/README.md b/trampoline/README.md index cb4162bafe6d..706b58d24eb1 100644 --- a/trampoline/README.md +++ b/trampoline/README.md @@ -36,6 +36,10 @@ Wikipedia says > In Java, trampoline refers to using reflection to avoid using inner classes, for example in event listeners. The time overhead of a reflection call is traded for the space overhead of an inner class. Trampolines in Java usually involve the creation of a GenericListener to pass events to an outer class. +Flowchart + +![Trampoline Pattern Flowchart](./etc/trampoline-flowchart.png) + ## Programmatic Example of Trampoline Pattern in Java Here's the `Trampoline` implementation in Java. diff --git a/trampoline/etc/trampoline-flowchart.png b/trampoline/etc/trampoline-flowchart.png new file mode 100644 index 000000000000..99387567ba16 Binary files /dev/null and b/trampoline/etc/trampoline-flowchart.png differ diff --git a/transaction-script/README.md b/transaction-script/README.md index dacc95a0449a..2e4b89a64577 100644 --- a/transaction-script/README.md +++ b/transaction-script/README.md @@ -34,6 +34,10 @@ Wikipedia says > The Transaction Script design pattern is a straightforward way to organize business logic in applications, particularly suitable for scenarios where each request from the presentation layer can be handled by a single procedure. This pattern is often used in simple applications or in systems where rapid development and ease of understanding are crucial. Each transaction script is responsible for a particular task, such as processing an order or calculating a result, and typically interacts directly with the database. +Flowchart + +![Transaction Script Pattern Flowchart](./etc/transaction-script-flowchart.png) + ## Programmatic Example of Transaction Script Pattern in Java Our Transaction Script pattern in Java example is about booking hotel rooms. diff --git a/transaction-script/etc/transaction-script-flowchart.png b/transaction-script/etc/transaction-script-flowchart.png new file mode 100644 index 000000000000..33e7319eb3b9 Binary files /dev/null and b/transaction-script/etc/transaction-script-flowchart.png differ diff --git a/twin/README.md b/twin/README.md index b2913e0a963f..f6fb6923c069 100644 --- a/twin/README.md +++ b/twin/README.md @@ -31,6 +31,10 @@ Wikipedia says > The Twin pattern is a software design pattern that allows developers to simulate multiple inheritance in languages that don't support it. Instead of creating a single class inheriting from multiple parents, two closely linked subclasses are created, each inheriting from one of the parents. These subclasses are mutually dependent, working together as a pair to achieve the desired functionality. This approach avoids the complications and inefficiencies often associated with multiple inheritance, while still allowing the reuse of functionalities from different classes. +Sequence diagram + +![Twin Pattern Sequence Diagram](./etc/twin-sequence-diagram.png) + ## Programmatic Example of Twin Pattern in Java Consider a game where a ball needs to function as both a `GameItem` and a `Thread`. Instead of inheriting from both, we use the Twin pattern with two closely linked objects: `BallItem` and `BallThread`. diff --git a/twin/etc/twin-sequence-diagram.png b/twin/etc/twin-sequence-diagram.png new file mode 100644 index 000000000000..9dc73a8a08d2 Binary files /dev/null and b/twin/etc/twin-sequence-diagram.png differ diff --git a/type-object/README.md b/type-object/README.md index dbdd6a405c65..e28c495e0364 100644 --- a/type-object/README.md +++ b/type-object/README.md @@ -32,12 +32,16 @@ Real-world example In plain words -> Explore how the Java Type Object pattern enables dynamic creation and management of flexible and extensible sets of related classes, ideal for Java developers seeking modularity without modifying existing codebase. +> Type Object pattern enables dynamic creation and management of flexible and extensible sets of related classes, ideal for Java developers seeking modularity without modifying existing codebase. gameprogrammingpatterns.com says > Define a type object class and a typed object class. Each type object instance represents a different logical type. Each typed object stores a reference to the type object that describes its type. +Flowchart + +![Type Object Pattern Flowchart](./etc/type-object-flowchart.png) + ## Programmatic Example of Type Object Pattern in Java The Type Object pattern is a design pattern that allows for the creation of flexible and reusable objects by creating a class with a field that represents the 'type' of the object. This design pattern proves invaluable for scenarios where anticipated Java types are undefined upfront, or when modifications or additions are required, ensuring efficient Java development without frequent recompilations. diff --git a/type-object/etc/type-object-flowchart.png b/type-object/etc/type-object-flowchart.png new file mode 100644 index 000000000000..6c38e929fcf4 Binary files /dev/null and b/type-object/etc/type-object-flowchart.png differ diff --git a/unit-of-work/README.md b/unit-of-work/README.md index 4b82b7c8ce05..ab2a16ec659e 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -29,6 +29,10 @@ In plain words > Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. +Flowchart + +![Unit of Work Pattern Flowchart](./etc/unit-of-work-flowchart.png) + ## Programmatic Example of Unit of Work Pattern in Java Arms dealer has a database containing weapon information. Merchants all over the town are constantly updating this information causing a high load on the database server. To make the load more manageable we apply to Unit of Work pattern to send many small updates in batches. diff --git a/unit-of-work/etc/unit-of-work-flowchart.png b/unit-of-work/etc/unit-of-work-flowchart.png new file mode 100644 index 000000000000..3d94c5a17424 Binary files /dev/null and b/unit-of-work/etc/unit-of-work-flowchart.png differ diff --git a/update-method/README.md b/update-method/README.md index bd1fa2f75995..918abbaecec2 100644 --- a/update-method/README.md +++ b/update-method/README.md @@ -35,6 +35,10 @@ gameprogrammingpatterns.com says > The game world maintains a collection of objects. Each object implements an update method that simulates one frame of the object’s behavior. Each frame, the game updates every object in the collection. +Sequence diagram + +![Update Method Sequence Diagram](./etc/update-method-sequence-diagram.png) + ## Programmatic Example of Update Method Pattern in Java The Update Method design pattern is a behavioral pattern that simulates a collection of independent game or application objects by telling each to process one frame of behavior at a time. This pattern is commonly used in game development, where each object in the game world needs to be updated once per frame. diff --git a/update-method/etc/update-method-sequence-diagram.png b/update-method/etc/update-method-sequence-diagram.png new file mode 100644 index 000000000000..d8543d8db800 Binary files /dev/null and b/update-method/etc/update-method-sequence-diagram.png differ diff --git a/value-object/README.md b/value-object/README.md index 88478b9314bf..84b8e029debd 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -43,6 +43,10 @@ Wikipedia says > In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object. +Flowchart + +![Value Object Pattern Flowchart](./etc/value-object-flowchart.png) + ## Programmatic Example of Value Object Pattern in Java There is a class for hero statistics in a role-playing game. The statistics contain attributes such as strength, intelligence, and luck. The statistics of different heroes should be equal when all the attributes are equal. diff --git a/value-object/etc/value-object-flowchart.png b/value-object/etc/value-object-flowchart.png new file mode 100644 index 000000000000..b2feb6959cb2 Binary files /dev/null and b/value-object/etc/value-object-flowchart.png differ diff --git a/version-number/README.md b/version-number/README.md index 6178f2317a89..8a18046505b9 100644 --- a/version-number/README.md +++ b/version-number/README.md @@ -35,6 +35,10 @@ Wikipedia says > The Version Number pattern is a technique used to manage concurrent access to data in databases and other data stores. It involves associating a version number with each record, which is incremented every time the record is updated. This pattern helps ensure that when multiple users or processes attempt to update the same data simultaneously, conflicts can be detected and resolved. +Flowchart + +![Version Number Pattern Flowchart](./etc/version-number-flowchart.png) + ## Programmatic Example of Version Number Pattern in Java Alice and Bob are working on the book, which stored in the database. Our heroes are making changes simultaneously, and we need some mechanism to prevent them from overwriting each other. diff --git a/version-number/etc/version-number-flowchart.png b/version-number/etc/version-number-flowchart.png new file mode 100644 index 000000000000..2d60aa30f7bf Binary files /dev/null and b/version-number/etc/version-number-flowchart.png differ diff --git a/view-helper/README.md b/view-helper/README.md new file mode 100644 index 000000000000..fbc93acba6fd --- /dev/null +++ b/view-helper/README.md @@ -0,0 +1,98 @@ +--- +title: "View Helper Pattern in Java: Simplifying Presentation Logic in MVC Applications" +shortTitle: View Helper +description: "Discover the View Helper Design Pattern in Java, a powerful technique for separating view-related logic from business logic in MVC-based web applications. This pattern enhances maintainability, reusability, and testability by delegating complex UI operations to reusable helper components. Ideal for developers aiming to keep views clean and focused on presentation." +category: Architectural +language: en +tag: + - Architecture + - Presentation + - Decoupling + - Code reuse +--- + +## Intent of View Helper Design Pattern +The View Helper Design Pattern separates presentation logic from the view by delegating complex UI tasks — like formatting or conditional display — to reusable helper components. This keeps views clean, promotes reuse, and aligns with the MVC principle of separating concerns between the view and the business logic. + +## Detailed Explanation with Real‑World Analogy +Real‑world example +> Imagine you're putting together a slideshow for a business presentation. You focus on arranging the slides, choosing the layout, and telling the story. But for tasks like resizing images, formatting charts, or converting data into visual form, you use tools or templates that automate those parts. +> +> In this analogy, you are the view, and the tools/templates are the helpers. They handle the heavy lifting behind the scenes so you can concentrate on the presentation. Similarly, in the View Helper pattern, the view delegates logic-heavy tasks—such as formatting or conditionally displaying data—to helper classes, keeping the view layer clean and presentation-focused. + +### In plain words +> The View Helper pattern is about keeping your UI code clean by moving any logic—like formatting, calculations, or decision-making—into separate helper classes. Instead of stuffing all the logic into the HTML or template files, you delegate it to helpers, so the view just focuses on showing the final result. + +### Sequence diagram +![Sequence diagram for View Helper](etc/view-helper-sequence-diagram.png) + +## Programmatic Example of View Helper Pattern in Java +Raw domain object +```java +public record Product(String name, BigDecimal price, LocalDate releaseDate, boolean discounted) {} +``` + +View model object for display +```java +public record ProductViewModel(String name, String price, String releasedDate) {} +``` + +View Helper formats data for display +```java +class ProductViewHelper implements ViewHelper { + + private static final String DISCOUNT_TAG = " (ON SALE)"; + + public ProductViewModel prepare(Product product) { + var displayName = product.name() + (product.discounted() ? DISCOUNT_TAG : ""); + var priceWithCurrency = NumberFormat.getCurrencyInstance(US).format(product.price()); + var formattedDate = product.releaseDate().format(ISO_DATE); + + return new ProductViewModel(displayName, priceWithCurrency, formattedDate); + } +} +``` + +View renders the formatted data +```java +public class ConsoleProductView implements View { + + @Override + public void render(ProductViewModel productViewModel) { + LOGGER.info(productViewModel.toString()); + } +} +``` +The `App.java` class simulates how the View Helper pattern works in a real application. It starts with a raw `Product` object containing unformatted data. +Then it: +1. Initializes a helper (`ProductViewHelper`) to format the product data for display. +1. Creates a view (`ConsoleProductView`) to render the formatted data. +1. Uses a controller (`ProductController`) to coordinate the flow between raw data, helper logic, and view rendering. + +Finally, it simulates a user request by passing the product to the controller, which prepares the view model using the helper and displays it using the view. This demonstrates a clean separation between data, presentation logic, and rendering. + +## When to Use the View Helper Pattern in Java +Use the View Helper pattern when your view layer starts containing logic such as formatting data, applying conditional styles, or transforming domain objects for display. It's especially useful in MVC architectures where you want to keep views clean and focused on rendering, while delegating non-trivial presentation logic to reusable helper classes. This pattern helps improve maintainability, testability, and separation of concerns in your application's UI layer. + +## Real‑World Uses of View Helper Pattern in Java +The View Helper pattern is widely used in web frameworks that follow the MVC architecture. In Java-based web applications (e.g., JSP, Spring MVC), it's common to use helper classes or utility methods to format dates, currencies, or apply conditional logic before rendering views. Technologies like Thymeleaf or JSF often rely on custom tags or expression helpers to achieve the same effect. + +## Benefits and Trade‑offs +Benefits: +* Separation of concerns: Keeps view templates clean by moving logic into dedicated helpers. +* Reusability: Common formatting and display logic can be reused across multiple views. +* Improved maintainability: Easier to update presentation logic without touching the view. +* Testability: Helpers can be unit tested independently from the UI layer. + +Trade‑offs: +* Added complexity: Introduces extra classes, which may feel unnecessary for very simple views. +* Overuse risk: Excessive use of helpers can spread logic thinly across many files, making it harder to trace behavior. +* Tight coupling risk: If not designed carefully, helpers can become tightly coupled to specific views or data formats. + +## Related Java Design Patterns +* [Model-View-Controller (MVC)](https://java-design-patterns.com/patterns/model-view-controller/): View Helper supports the View layer in MVC by offloading logic from the view to helper classes. +* [Template Method](https://java-design-patterns.com/patterns/template-method/): Can structure the steps of rendering or data transformation, with helpers handling specific formatting tasks. +* [Data Transfer Object (DTO)](https://java-design-patterns.com/patterns/data-transfer-object/): Often used alongside View Helper when transferring raw data that needs formatting before being displayed. + +## References & Credits +* [Core J2EE Patterns: View Helper.](https://www.oracle.com/java/technologies/viewhelper.html) diff --git a/view-helper/etc/view-helper-sequence-diagram.png b/view-helper/etc/view-helper-sequence-diagram.png new file mode 100644 index 000000000000..22e53c6b8afe Binary files /dev/null and b/view-helper/etc/view-helper-sequence-diagram.png differ diff --git a/view-helper/etc/view-helper-sequence-diagram.puml b/view-helper/etc/view-helper-sequence-diagram.puml new file mode 100644 index 000000000000..d5be27e51510 --- /dev/null +++ b/view-helper/etc/view-helper-sequence-diagram.puml @@ -0,0 +1,15 @@ +@startuml +actor Client +participant Controller +participant ViewHelper +participant View +participant Product +participant ProductViewModel + +Client -> Controller : handle(product) +Controller -> ViewHelper : prepare(product) +ViewHelper -> Product : access data +ViewHelper -> ProductViewModel : return formatted view model +Controller -> View : render(viewModel) +View -> Console : display output +@enduml \ No newline at end of file diff --git a/view-helper/etc/view-helper.png b/view-helper/etc/view-helper.png new file mode 100644 index 000000000000..9550b80d7acb Binary files /dev/null and b/view-helper/etc/view-helper.png differ diff --git a/view-helper/etc/view-helper.puml b/view-helper/etc/view-helper.puml new file mode 100644 index 000000000000..cbc4a57d31ff --- /dev/null +++ b/view-helper/etc/view-helper.puml @@ -0,0 +1,45 @@ +@startuml +package com.iluwatar.viewhelper { + interface View { + +render(T model) + } + + interface ViewHelper { + +prepare(S source): T + } + + class Product { + -name: String + -price: BigDecimal + -releaseDate: LocalDate + -discounted: boolean + } + + class ProductViewModel { + -name: String + -price: String + -releasedDate: String + } + + class ProductViewHelper { + +prepare(Product): ProductViewModel + } + + class ConsoleProductView { + +render(ProductViewModel) + } + + class ProductController { + -helper: ViewHelper + -view: View + +handle(Product) + } +} +Product --> ProductViewHelper +ProductViewHelper ..|> ViewHelper +ConsoleProductView ..|> View +ProductViewHelper --> ProductViewModel +ProductController --> ProductViewHelper +ProductController --> ConsoleProductView + +@enduml \ No newline at end of file diff --git a/view-helper/pom.xml b/view-helper/pom.xml new file mode 100644 index 000000000000..e2f3afca3a08 --- /dev/null +++ b/view-helper/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + view-helper + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.value.object.App + + + + + + + + + diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/App.java b/view-helper/src/main/java/com/iluwatar/viewhelper/App.java new file mode 100644 index 000000000000..e427bc8f5e9b --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/App.java @@ -0,0 +1,52 @@ +/* + * 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.viewhelper; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** The main application class that sets up and runs the View Helper pattern demo. */ +public class App { + /** + * The entry point of the application. + * + * @param args the command line arguments + */ + public static void main(String[] args) { + // Raw Product data (no formatting, no UI tags) + var product = + new Product( + "Design patterns book", new BigDecimal("18.90"), LocalDate.of(2025, 4, 19), true); + + // Create view, viewHelper and viewHelper + var helper = new ProductViewHelper(); + var view = new ConsoleProductView(); + var controller = new ProductController(helper, view); + + // Handle “request” + controller.handle(product); + } +} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/ConsoleProductView.java b/view-helper/src/main/java/com/iluwatar/viewhelper/ConsoleProductView.java new file mode 100644 index 000000000000..7f215a7514f6 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/ConsoleProductView.java @@ -0,0 +1,36 @@ +/* + * 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.viewhelper; + +import lombok.extern.slf4j.Slf4j; + +/** Renders {@link ProductViewModel} to the console. */ +@Slf4j +public class ConsoleProductView implements View { + @Override + public void render(ProductViewModel productViewModel) { + LOGGER.info(productViewModel.toString()); + } +} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/Product.java b/view-helper/src/main/java/com/iluwatar/viewhelper/Product.java new file mode 100644 index 000000000000..a0a75cf24188 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/Product.java @@ -0,0 +1,31 @@ +/* + * 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.viewhelper; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** Definition of product. */ +public record Product(String name, BigDecimal price, LocalDate releaseDate, boolean discounted) {} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/ProductController.java b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductController.java new file mode 100644 index 000000000000..836f34a33167 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductController.java @@ -0,0 +1,49 @@ +/* + * 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.viewhelper; + +/** + * Controller delegates a {@link Product} to {@link ProductViewHelper} and then to {@link + * ConsoleProductView}. + */ +public class ProductController { + + private final ViewHelper viewHelper; + private final View view; + + public ProductController( + ViewHelper viewHelper, View view) { + this.viewHelper = viewHelper; + this.view = view; + } + + /** + * Passes the product to the helper for formatting and then forwards formatted product to the + * view. + */ + public void handle(Product product) { + view.render(viewHelper.prepare(product)); + } +} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewHelper.java b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewHelper.java new file mode 100644 index 000000000000..4c739e9f14a8 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewHelper.java @@ -0,0 +1,45 @@ +/* + * 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.viewhelper; + +import static java.time.format.DateTimeFormatter.ISO_DATE; +import static java.util.Locale.US; + +import java.text.NumberFormat; + +/** Formats a {@link Product} into a {@link ProductViewModel}. */ +public class ProductViewHelper implements ViewHelper { + + private static final String DISCOUNT_TAG = " ON SALE"; + + @Override + public ProductViewModel prepare(Product product) { + var displayName = product.name() + (product.discounted() ? DISCOUNT_TAG : ""); + var priceWithCurrency = NumberFormat.getCurrencyInstance(US).format(product.price()); + var formattedDate = product.releaseDate().format(ISO_DATE); + + return new ProductViewModel(displayName, priceWithCurrency, formattedDate); + } +} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewModel.java b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewModel.java new file mode 100644 index 000000000000..b133fc071da1 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/ProductViewModel.java @@ -0,0 +1,28 @@ +/* + * 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.viewhelper; + +/** Class defining formatted display data of a {@link Product}. */ +public record ProductViewModel(String name, String price, String releasedDate) {} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/View.java b/view-helper/src/main/java/com/iluwatar/viewhelper/View.java new file mode 100644 index 000000000000..7bc822166885 --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/View.java @@ -0,0 +1,30 @@ +/* + * 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.viewhelper; + +public interface View { + + void render(V data); +} diff --git a/view-helper/src/main/java/com/iluwatar/viewhelper/ViewHelper.java b/view-helper/src/main/java/com/iluwatar/viewhelper/ViewHelper.java new file mode 100644 index 000000000000..68756f46585d --- /dev/null +++ b/view-helper/src/main/java/com/iluwatar/viewhelper/ViewHelper.java @@ -0,0 +1,29 @@ +/* + * 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.viewhelper; + +public interface ViewHelper { + V prepare(M source); +} diff --git a/view-helper/src/test/java/com/iluwatar/viewhelper/AppTest.java b/view-helper/src/test/java/com/iluwatar/viewhelper/AppTest.java new file mode 100644 index 000000000000..bc4a31cc43a8 --- /dev/null +++ b/view-helper/src/test/java/com/iluwatar/viewhelper/AppTest.java @@ -0,0 +1,39 @@ +/* + * 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.viewhelper; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application test */ +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/view-helper/src/test/java/com/iluwatar/viewhelper/ProductViewHelperTest.java b/view-helper/src/test/java/com/iluwatar/viewhelper/ProductViewHelperTest.java new file mode 100644 index 000000000000..49cc48556f47 --- /dev/null +++ b/view-helper/src/test/java/com/iluwatar/viewhelper/ProductViewHelperTest.java @@ -0,0 +1,60 @@ +/* + * 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.viewhelper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ProductViewHelperTest { + + private ProductViewHelper helper; + + @BeforeEach + void setUp() { + helper = new ProductViewHelper(); + } + + @Test + void shouldFormatProductWithoutDiscount() { + var product = new Product("X", new BigDecimal("10.00"), LocalDate.of(2025, 1, 1), false); + ProductViewModel viewModel = helper.prepare(product); + + assertEquals("X", viewModel.name()); + assertEquals("$10.00", viewModel.price()); + assertEquals("2025-01-01", viewModel.releasedDate()); + } + + @Test + void shouldFormatProductWithDiscount() { + var product = new Product("X", new BigDecimal("10.00"), LocalDate.of(2025, 1, 1), true); + ProductViewModel viewModel = helper.prepare(product); + + assertEquals("X ON SALE", viewModel.name()); // locale follows JVM default + } +} diff --git a/virtual-proxy/README.md b/virtual-proxy/README.md index e5b32fd096a4..187eeec1dc55 100644 --- a/virtual-proxy/README.md +++ b/virtual-proxy/README.md @@ -35,6 +35,10 @@ Wikipedia says > A proxy that controls access to a resource that is expensive to create. +Sequence diagram + +![Virtual Proxy Sequence Diagram](./etc/virtual-proxy-sequence-diagram.png) + ## Programmatic Example of Virtual Proxy Pattern in Java The Virtual Proxy design pattern in Java can optimize resource utilization and system performance. diff --git a/virtual-proxy/etc/virtual-proxy-sequence-diagram.png b/virtual-proxy/etc/virtual-proxy-sequence-diagram.png new file mode 100644 index 000000000000..9ff38c0ad056 Binary files /dev/null and b/virtual-proxy/etc/virtual-proxy-sequence-diagram.png differ diff --git a/visitor/README.md b/visitor/README.md index 674062606a31..bb5ee4e445ee 100644 --- a/visitor/README.md +++ b/visitor/README.md @@ -32,6 +32,10 @@ Wikipedia says > In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. +Sequence diagram + +![Visitor sequence diagram](./etc/visitor-sequence-diagram.png) + ## Programmatic Example of Visitor Pattern in Java Consider a tree structure with army units. Commander has two sergeants under it and each sergeant has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can easily create new objects that interact with the commander, sergeants, soldiers, or all of them. @@ -221,10 +225,6 @@ Program output: 14:58:06.118 [main] INFO com.iluwatar.visitor.CommanderVisitor -- Good to see you commander ``` -## Detailed Explanation of Visitor Pattern with Real-World Examples - -![Visitor](./etc/visitor_1.png "Visitor") - ## When to Use the Visitor Pattern in Java Use the Visitor pattern when diff --git a/visitor/etc/visitor-sequence-diagram.png b/visitor/etc/visitor-sequence-diagram.png new file mode 100644 index 000000000000..61264826d771 Binary files /dev/null and b/visitor/etc/visitor-sequence-diagram.png differ