From df3a42fb2561364ad756b5c9e4e0781184fa26ec Mon Sep 17 00:00:00 2001 From: Ahmed Khaled Date: Mon, 9 Dec 2024 23:10:41 +0200 Subject: [PATCH] Add Domain Event Pattern with Java examples --- Microservice-pattern-Domain-event/README.md | 153 ++++++++++ Microservice-pattern-Domain-event/pom.xml | 73 +++++ .../main/java/com/iluwatar/DomainEvent.java | 15 + .../java/com/iluwatar/EventPublisher.java | 12 + .../src/main/java/com/iluwatar/Main.java | 15 + .../java/com/iluwatar/OrderCreatedEvent.java | 14 + .../com/iluwatar/OrderCreatedListener.java | 21 ++ .../main/java/com/iluwatar/OrderService.java | 20 ++ .../main/java/com/iluwatar/SpringContext.java | 17 ++ .../java/com/iluwatar/EventPublisher.java | 15 + .../iluwatar/OrderCreatedListenerTest.java | 36 +++ .../test/java/com/iluwatar/OrderService.java | 34 +++ .../etc/fluent-interface.urm.puml | 72 +++++ .../etc/function-composition.urm.puml | 12 + .../etc/hexagonal-architecture.urm.puml | 282 ++++++++++++++++++ .../etc/marker-interface.urm.puml | 2 + .../etc/microservices-aggregrator.urm.puml | 2 + .../etc/microservices-api-gateway.urm.puml | 2 + ...microservices-idempotent-consumer.urm.puml | 49 +++ .../iluwatar/idempotentconsumer/AppTest.java | 24 ++ .../RequestStateMachineTests.java | 24 ++ .../microservices-log-aggregation.urm.puml | 68 +++++ pom.xml | 1 + .../etc/queue-based-load-leveling.urm.puml | 44 +++ .../java/com/iluwatar/typeobject/App.java | 3 +- update-header.sh | 25 ++ virtual-proxy/etc/virtual-proxy.urm.puml | 26 ++ 27 files changed, 1059 insertions(+), 2 deletions(-) create mode 100644 Microservice-pattern-Domain-event/README.md create mode 100644 Microservice-pattern-Domain-event/pom.xml create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/DomainEvent.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/EventPublisher.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/Main.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedEvent.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedListener.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderService.java create mode 100644 Microservice-pattern-Domain-event/src/main/java/com/iluwatar/SpringContext.java create mode 100644 Microservice-pattern-Domain-event/src/test/java/com/iluwatar/EventPublisher.java create mode 100644 Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderCreatedListenerTest.java create mode 100644 Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderService.java create mode 100644 fluent-interface/etc/fluent-interface.urm.puml create mode 100644 function-composition/etc/function-composition.urm.puml create mode 100644 hexagonal-architecture/etc/hexagonal-architecture.urm.puml create mode 100644 marker-interface/etc/marker-interface.urm.puml create mode 100644 microservices-aggregrator/etc/microservices-aggregrator.urm.puml create mode 100644 microservices-api-gateway/etc/microservices-api-gateway.urm.puml create mode 100644 microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml create mode 100644 microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml create mode 100644 queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml create mode 100644 virtual-proxy/etc/virtual-proxy.urm.puml diff --git a/Microservice-pattern-Domain-event/README.md b/Microservice-pattern-Domain-event/README.md new file mode 100644 index 000000000000..ed7e9d37c7c5 --- /dev/null +++ b/Microservice-pattern-Domain-event/README.md @@ -0,0 +1,153 @@ +# Microservice Pattern: Domain Event + +## Overview +The Domain Event design pattern enables reliable communication between microservices in a decoupled manner. This pattern allows services to react to state changes in other services without tightly coupling them, enhancing scalability and maintainability. + +## Intent of the Domain Event Pattern +The Domain Event pattern decouples services by allowing one service to notify others of significant state changes. When an important change occurs, a domain event is published, and other services can listen and react to it. + +## Detailed Explanation + +### Real-World Example +In an e-commerce system, when a customer places an order, the `OrderService` creates an `OrderCreatedEvent`. This event is published, and other services like Inventory and Payment can listen for it and act accordingly (e.g., updating inventory, processing payment). + +### In Plain Words +When an important event happens in one service (e.g., order creation), it publishes a Domain Event. Other services can subscribe to these events and react without directly communicating with each other. + +--- + +## Example of the Domain Event Pattern in Action + +Consider a system with the following services: +- **OrderService**: Creates orders. +- **InventoryService**: Updates stock based on orders. +- **PaymentService**: Processes payments after order creation. + +When an order is created in the `OrderService`, an `OrderCreatedEvent` is published. Other services listen for this event and act accordingly. + +--- + +## Implementation in Java + +### 1. Domain Event Class +This class serves as the base for all domain events and includes the timestamp. + +```java +public abstract class DomainEvent { + private final LocalDateTime timestamp; + + public DomainEvent() { + this.timestamp = LocalDateTime.now(); + } + + public LocalDateTime getTimestamp() { + return timestamp; + } +} +``` +### 2. Event Publisher +The `EventPublisher` component is responsible for publishing domain events. + +```java +@Component +public class EventPublisher { + private final ApplicationEventPublisher applicationEventPublisher; + + public EventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + public void publish(DomainEvent event) { + applicationEventPublisher.publishEvent(event); + } +} + +``` +### 3. Event Listener +The `OrderCreatedListener` listens for the `OrderCreatedEvent` and processes it when it occurs. + +```java +@Component +public class OrderCreatedListener { + @EventListener + public void handleOrderCreatedEvent(OrderCreatedEvent event) { + System.out.println("Handling Order Created Event: " + event.getOrderId()); + // Example logic: Notify another service or update the database + } +} +``` + +### 4. Order Service +The `OrderService` publishes the `OrderCreatedEvent` when an order is created. + +```java +@Service +public class OrderService { + private final EventPublisher eventPublisher; + + public OrderService(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + public void createOrder(String orderId) { + System.out.println("Order created with ID: " + orderId); + OrderCreatedEvent event = new OrderCreatedEvent(orderId); + eventPublisher.publish(event); + } +} +``` + +### 5. Order Created Event +The `OrderCreatedEvent` extends `DomainEvent` and represents the creation of an order. + +```java +public class OrderCreatedEvent extends DomainEvent { + private final String orderId; + + public OrderCreatedEvent(String orderId) { + this.orderId = orderId; + } + + public String getOrderId() { + return orderId; + } +} + +``` +### 6. When to Use the Domain Event Pattern +This pattern is ideal for scenarios where: +- Services need to react to state changes in other services without tight coupling. +- Asynchronous communication between services is preferred. +- Scalability is crucial, allowing services to independently scale and evolve. + +--- + +### 7. Benefits and Trade-offs of the Domain Event Pattern + +#### Benefits: +1. **Decoupling**: Services are decoupled, making maintenance and independent scaling easier. +2. **Scalability**: Services scale independently, based on individual requirements. +3. **Asynchronous Processing**: Event-driven communication supports asynchronous workflows, enhancing system responsiveness. + +#### Trade-offs: +1. **Eventual Consistency**: As events are processed asynchronously, eventual consistency must be handled carefully. +2. **Complexity**: Adding events and listeners increases system complexity, particularly for failure handling and retries. +3. **Debugging**: Asynchronous and decoupled communication can make tracing data flow across services more challenging. + +--- + +### 8. Example Flow + +1. **Order Created**: The `OrderService` creates a new order. +2. **Publish Event**: The `OrderCreatedEvent` is published by the `EventPublisher`. +3. **Event Listener**: The `OrderCreatedListener` listens for the event and executes relevant logic (e.g., notifying other services). +4. **Outcome**: Other services (e.g., Inventory, Payment) react to the event accordingly. + +--- + +### 9. References and Credits +- *Domain-Driven Design* by Eric Evans +- *Building Microservices* by Sam Newman +- *Microservices Patterns* by Chris Richardson + +For more information on Domain Event patterns and best practices, refer to the above books. diff --git a/Microservice-pattern-Domain-event/pom.xml b/Microservice-pattern-Domain-event/pom.xml new file mode 100644 index 000000000000..46350473166d --- /dev/null +++ b/Microservice-pattern-Domain-event/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + + com.iluwatar + Microservice-pattern-Domain-event + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + + 17 + ${java.version} + ${java.version} + + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework + spring-context + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.junit.jupiter + junit-jupiter + test + + + + + org.mockito + mockito-core + test + + + org.mockito + mockito-junit-jupiter + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/DomainEvent.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/DomainEvent.java new file mode 100644 index 000000000000..3e3c61f9c418 --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/DomainEvent.java @@ -0,0 +1,15 @@ +package com.iluwatar; + +import java.time.LocalDateTime; + +public abstract class DomainEvent { + private final LocalDateTime timestamp; + + public DomainEvent() { + this.timestamp = LocalDateTime.now(); + } + + public LocalDateTime getTimestamp() { + return timestamp; + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/EventPublisher.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/EventPublisher.java new file mode 100644 index 000000000000..ea74d3b9373a --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/EventPublisher.java @@ -0,0 +1,12 @@ +package com.iluwatar; + +import org.springframework.stereotype.Component; + +@Component +public class EventPublisher { + + public void publishEvent(OrderCreatedEvent event) { + // Simulate publishing the event + System.out.println("Event published: " + event.getOrderId()); + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/Main.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/Main.java new file mode 100644 index 000000000000..df91ebd713bf --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/Main.java @@ -0,0 +1,15 @@ +package com.iluwatar; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + + // Simulate creating an order + OrderService orderService = SpringContext.getBean(OrderService.class); + orderService.createOrder("12345"); + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedEvent.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedEvent.java new file mode 100644 index 000000000000..ad6c165606be --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedEvent.java @@ -0,0 +1,14 @@ +package com.iluwatar; + +public class OrderCreatedEvent { + + private final String orderId; + + public OrderCreatedEvent(String orderId) { + this.orderId = orderId; + } + + public String getOrderId() { + return orderId; + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedListener.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedListener.java new file mode 100644 index 000000000000..812c0849322e --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderCreatedListener.java @@ -0,0 +1,21 @@ +package com.iluwatar; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class OrderCreatedListener { + + private final EventPublisher eventPublisher; + + @Autowired + public OrderCreatedListener(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + public void handleOrderCreatedEvent(OrderCreatedEvent event) { + // Process the event + System.out.println("Processing order: " + event.getOrderId()); + eventPublisher.publishEvent(event); + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderService.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderService.java new file mode 100644 index 000000000000..6066242d0c61 --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/OrderService.java @@ -0,0 +1,20 @@ +package com.iluwatar; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class OrderService { + + private final OrderCreatedListener orderCreatedListener; + + @Autowired + public OrderService(OrderCreatedListener orderCreatedListener) { + this.orderCreatedListener = orderCreatedListener; + } + + public void createOrder(String orderId) { + OrderCreatedEvent event = new OrderCreatedEvent(orderId); + orderCreatedListener.handleOrderCreatedEvent(event); + } +} diff --git a/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/SpringContext.java b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/SpringContext.java new file mode 100644 index 000000000000..a18290590a2f --- /dev/null +++ b/Microservice-pattern-Domain-event/src/main/java/com/iluwatar/SpringContext.java @@ -0,0 +1,17 @@ +package com.iluwatar; + +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class SpringContext { + private static ApplicationContext context; + + public SpringContext(ApplicationContext applicationContext) { + context = applicationContext; + } + + public static T getBean(Class beanClass) { + return context.getBean(beanClass); + } +} diff --git a/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/EventPublisher.java b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/EventPublisher.java new file mode 100644 index 000000000000..7dc5f93b86b1 --- /dev/null +++ b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/EventPublisher.java @@ -0,0 +1,15 @@ +package com.iluwatar; + +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +class EventPublisherTest { + + @Test + void testPublishEvent() { + EventPublisher eventPublisher = new EventPublisher(); + OrderCreatedEvent event = new OrderCreatedEvent("12345"); + eventPublisher.publishEvent(event); + } +} diff --git a/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderCreatedListenerTest.java b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderCreatedListenerTest.java new file mode 100644 index 000000000000..7057a283dfa8 --- /dev/null +++ b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderCreatedListenerTest.java @@ -0,0 +1,36 @@ +package com.iluwatar; + +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@SpringBootTest +public class OrderCreatedListenerTest { + + @InjectMocks + private OrderCreatedListener orderCreatedListener; + + @MockBean + private EventPublisher eventPublisher; + + @Test + public void testHandleOrderCreatedEvent() { + // Arrange + String orderId = "order123"; + OrderCreatedEvent event = new OrderCreatedEvent(orderId); + + // Act + orderCreatedListener.handleOrderCreatedEvent(event); + + // Assert + verify(eventPublisher, times(1)).publishEvent(event); + } +} diff --git a/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderService.java b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderService.java new file mode 100644 index 000000000000..e330b1d6301b --- /dev/null +++ b/Microservice-pattern-Domain-event/src/test/java/com/iluwatar/OrderService.java @@ -0,0 +1,34 @@ +package com.iluwatar; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.*; + +class OrderServiceTest { + + @Mock + private EventPublisher eventPublisher; + + private OrderService orderService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); // Initialize mocks + orderService = new OrderService(new OrderCreatedListener(eventPublisher)); + } + + @Test + void testCreateOrderPublishesEvent() { + // Arrange + String orderId = "123"; + + // Act + orderService.createOrder(orderId); + + // Assert + verify(eventPublisher, times(1)).publishEvent(any(OrderCreatedEvent.class)); + } +} diff --git a/fluent-interface/etc/fluent-interface.urm.puml b/fluent-interface/etc/fluent-interface.urm.puml new file mode 100644 index 000000000000..d343a478bff0 --- /dev/null +++ b/fluent-interface/etc/fluent-interface.urm.puml @@ -0,0 +1,72 @@ +@startuml +package com.iluwatar.fluentinterface.fluentiterable.simple { + class SimpleFluentIterable { + - iterable : Iterable + + SimpleFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + forEach(action : Consumer) + + from(iterable : Iterable) : FluentIterable {static} + + fromCopyOf(iterable : Iterable) : FluentIterable {static} + + getRemainingElementsCount() : int + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + + spliterator() : Spliterator + + toList(iterator : Iterator) : List {static} + } +} +package com.iluwatar.fluentinterface.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + - negatives() : Predicate {static} + - positives() : Predicate {static} + - prettyPrint(delimiter : String, prefix : String, iterable : Iterable) {static} + - prettyPrint(prefix : String, iterable : Iterable) {static} + - transformToString() : Function {static} + } +} +package com.iluwatar.fluentinterface.fluentiterable.lazy { + abstract class DecoratingIterator { + # fromIterator : Iterator + - next : E + + DecoratingIterator(fromIterator : Iterator) + + computeNext() : E {abstract} + + hasNext() : boolean + + next() : E + } + class LazyFluentIterable { + - iterable : Iterable + # LazyFluentIterable() + + LazyFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + from(iterable : Iterable) : FluentIterable {static} + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + } +} +package com.iluwatar.fluentinterface.fluentiterable { + interface FluentIterable { + + asList() : List {abstract} + + copyToList(iterable : Iterable) : List {static} + + filter(Predicate) : FluentIterable {abstract} + + first() : Optional {abstract} + + first(int) : FluentIterable {abstract} + + last() : Optional {abstract} + + last(int) : FluentIterable {abstract} + + map(Function) : FluentIterable {abstract} + } +} +LazyFluentIterable ..|> FluentIterable +SimpleFluentIterable ..|> FluentIterable +@enduml \ No newline at end of file diff --git a/function-composition/etc/function-composition.urm.puml b/function-composition/etc/function-composition.urm.puml new file mode 100644 index 000000000000..79b2a898fd12 --- /dev/null +++ b/function-composition/etc/function-composition.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.function.composition { + class App { + + App() + + main(args : String[]) {static} + } + class FunctionComposer { + + FunctionComposer() + + composeFunctions(f1 : Function, f2 : Function) : Function {static} + } +} +@enduml \ No newline at end of file diff --git a/hexagonal-architecture/etc/hexagonal-architecture.urm.puml b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml new file mode 100644 index 000000000000..5849b24adbae --- /dev/null +++ b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml @@ -0,0 +1,282 @@ +@startuml +package com.iluwatar.hexagonal.sampledata { + class SampleData { + - PLAYERS : List {static} + - RANDOM : SecureRandom {static} + + SampleData() + - getRandomPlayerDetails() : PlayerDetails {static} + + submitTickets(lotteryService : LotteryService, numTickets : int) {static} + } +} +package com.iluwatar.hexagonal.service { + class ConsoleLottery { + - LOGGER : Logger {static} + + ConsoleLottery() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface LotteryConsoleService { + + addFundsToLotteryAccount(WireTransfers, Scanner) {abstract} + + checkTicket(LotteryService, Scanner) {abstract} + + queryLotteryAccountFunds(WireTransfers, Scanner) {abstract} + + submitTicket(LotteryService, Scanner) {abstract} + } + class LotteryConsoleServiceImpl { + - logger : Logger + + LotteryConsoleServiceImpl(logger : Logger) + + addFundsToLotteryAccount(bank : WireTransfers, scanner : Scanner) + + checkTicket(service : LotteryService, scanner : Scanner) + + queryLotteryAccountFunds(bank : WireTransfers, scanner : Scanner) + - readString(scanner : Scanner) : String + + submitTicket(service : LotteryService, scanner : Scanner) + } +} +package com.iluwatar.hexagonal.mongo { + class MongoConnectionPropertiesLoader { + - DEFAULT_HOST : String {static} + - DEFAULT_PORT : int {static} + - LOGGER : Logger {static} + + MongoConnectionPropertiesLoader() + + load() {static} + } +} +package com.iluwatar.hexagonal.domain { + class LotteryAdministration { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryAdministration(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + getAllSubmittedTickets() : Map + + performLottery() : LotteryNumbers + + resetLottery() + } + class LotteryConstants { + + PLAYER_MAX_BALANCE : int {static} + + PRIZE_AMOUNT : int {static} + + SERVICE_BANK_ACCOUNT : String {static} + + SERVICE_BANK_ACCOUNT_BALANCE : int {static} + + TICKET_PRIZE : int {static} + - LotteryConstants() + } + class LotteryNumbers { + + MAX_NUMBER : int {static} + + MIN_NUMBER : int {static} + + NUM_NUMBERS : int {static} + - numbers : Set + - LotteryNumbers() + - LotteryNumbers(givenNumbers : Set) + # canEqual(other : Object) : boolean + + create(givenNumbers : Set) : LotteryNumbers {static} + + createRandom() : LotteryNumbers {static} + + equals(o : Object) : boolean + - generateRandomNumbers() + + getNumbers() : Set + + getNumbersAsString() : String + + hashCode() : int + + toString() : String + } + -class RandomNumberGenerator { + - randomIterator : OfInt + + RandomNumberGenerator(min : int, max : int) + + nextInt() : int + } + class LotteryService { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryService(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + checkTicketForPrize(id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult + + submitTicket(ticket : LotteryTicket) : Optional + } + class LotteryTicketCheckResult { + - prizeAmount : int + - result : CheckResult + + LotteryTicketCheckResult(result : CheckResult) + + LotteryTicketCheckResult(result : CheckResult, prizeAmount : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getPrizeAmount() : int + + getResult() : CheckResult + + hashCode() : int + } + enum CheckResult { + + NO_PRIZE {static} + + TICKET_NOT_SUBMITTED {static} + + WIN_PRIZE {static} + + valueOf(name : String) : CheckResult {static} + + values() : CheckResult[] {static} + } + class LotteryTicketId { + - id : int + - numAllocated : AtomicInteger {static} + + LotteryTicketId() + + LotteryTicketId(id : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : int + + hashCode() : int + + toString() : String + } + class LotteryUtils { + - LotteryUtils() + + checkTicketForPrize(repository : LotteryTicketRepository, id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult {static} + } +} +package com.iluwatar.hexagonal.banking { + class InMemoryBank { + - accounts : Map {static} + + InMemoryBank() + + getFunds(bankAccount : String) : int + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + class MongoBank { + - DEFAULT_ACCOUNTS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - accountsCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + + MongoBank() + + MongoBank(dbName : String, accountsCollectionName : String) + + connect() + + connect(dbName : String, accountsCollectionName : String) + + getAccountsCollection() : MongoCollection + + getDatabase() : MongoDatabase + + getFunds(bankAccount : String) : int + + getMongoClient() : MongoClient + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + interface WireTransfers { + + getFunds(String) : int {abstract} + + setFunds(String, int) {abstract} + + transferFunds(int, String, String) : boolean {abstract} + } +} +package com.iluwatar.hexagonal.database { + class InMemoryTicketRepository { + - tickets : Map {static} + + InMemoryTicketRepository() + + deleteAll() + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + save(ticket : LotteryTicket) : Optional + } + interface LotteryTicketRepository { + + deleteAll() {abstract} + + findAll() : Map {abstract} + + findById(LotteryTicketId) : Optional {abstract} + + save(LotteryTicket) : Optional {abstract} + } + class MongoTicketRepository { + - DEFAULT_COUNTERS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - DEFAULT_TICKETS_COLLECTION : String {static} + - TICKET_ID : String {static} + - countersCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + - ticketsCollection : MongoCollection + + MongoTicketRepository() + + MongoTicketRepository(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + connect() + + connect(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + deleteAll() + - docToTicket(doc : Document) : LotteryTicket + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + getCountersCollection() : MongoCollection + + getNextId() : int + + getTicketsCollection() : MongoCollection + - initCounters() + + save(ticket : LotteryTicket) : Optional + } +} +package com.iluwatar.hexagonal { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.hexagonal.administration { + class ConsoleAdministration { + - LOGGER : Logger {static} + + ConsoleAdministration() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface ConsoleAdministrationSrv { + + getAllSubmittedTickets() {abstract} + + performLottery() {abstract} + + resetLottery() {abstract} + } + class ConsoleAdministrationSrvImpl { + - administration : LotteryAdministration + - logger : Logger + + ConsoleAdministrationSrvImpl(administration : LotteryAdministration, logger : Logger) + + getAllSubmittedTickets() + + performLottery() + + resetLottery() + } +} +package com.iluwatar.hexagonal.eventlog { + interface LotteryEventLog { + + prizeError(PlayerDetails, int) {abstract} + + ticketDidNotWin(PlayerDetails) {abstract} + + ticketSubmitError(PlayerDetails) {abstract} + + ticketSubmitted(PlayerDetails) {abstract} + + ticketWon(PlayerDetails, int) {abstract} + } + class MongoEventLog { + - DEFAULT_DB : String {static} + - DEFAULT_EVENTS_COLLECTION : String {static} + - EMAIL : String {static} + + MESSAGE : String {static} + - PHONE : String {static} + - database : MongoDatabase + - eventsCollection : MongoCollection + - mongoClient : MongoClient + - stdOutEventLog : StdOutEventLog + + MongoEventLog() + + MongoEventLog(dbName : String, eventsCollectionName : String) + + connect() + + connect(dbName : String, eventsCollectionName : String) + + getDatabase() : MongoDatabase + + getEventsCollection() : MongoCollection + + getMongoClient() : MongoClient + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } + class StdOutEventLog { + - LOGGER : Logger {static} + + StdOutEventLog() + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } +} +LotteryAdministration --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-repository" LotteryTicketRepository +LotteryService --> "-notifications" LotteryEventLog +MongoEventLog --> "-stdOutEventLog" StdOutEventLog +LotteryService --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-notifications" LotteryEventLog +ConsoleAdministrationSrvImpl --> "-administration" LotteryAdministration +LotteryService --> "-repository" LotteryTicketRepository +LotteryTicketCheckResult --> "-result" CheckResult +ConsoleAdministrationSrvImpl ..|> ConsoleAdministrationSrv +InMemoryBank ..|> WireTransfers +MongoBank ..|> WireTransfers +InMemoryTicketRepository ..|> LotteryTicketRepository +MongoTicketRepository ..|> LotteryTicketRepository +MongoEventLog ..|> LotteryEventLog +StdOutEventLog ..|> LotteryEventLog +LotteryConsoleServiceImpl ..|> LotteryConsoleService +@enduml \ No newline at end of file diff --git a/marker-interface/etc/marker-interface.urm.puml b/marker-interface/etc/marker-interface.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/marker-interface/etc/marker-interface.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-aggregrator/etc/microservices-aggregrator.urm.puml b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-api-gateway/etc/microservices-api-gateway.urm.puml b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml new file mode 100644 index 000000000000..43fe77181375 --- /dev/null +++ b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml @@ -0,0 +1,49 @@ +@startuml +package com.iluwatar.idempotentconsumer { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + + run(requestService : RequestService, requestRepository : RequestRepository) : CommandLineRunner + } + class Request { + - status : Status + - uuid : UUID + + Request() + + Request(uuid : UUID) + + Request(uuid : UUID, status : Status) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getStatus() : Status + + getUuid() : UUID + + hashCode() : int + + setStatus(status : Status) + + setUuid(uuid : UUID) + + toString() : String + } + ~enum Status { + + COMPLETED {static} + + PENDING {static} + + STARTED {static} + + valueOf(name : String) : Status {static} + + values() : Status[] {static} + } + interface RequestRepository { + } + class RequestService { + ~ requestRepository : RequestRepository + ~ requestStateMachine : RequestStateMachine + + RequestService(requestRepository : RequestRepository, requestStateMachine : RequestStateMachine) + + complete(uuid : UUID) : Request + + create(uuid : UUID) : Request + + start(uuid : UUID) : Request + } + class RequestStateMachine { + + RequestStateMachine() + + next(req : Request, nextStatus : Status) : Request + } +} +RequestService --> "-requestRepository" RequestRepository +Request --> "-status" Status +RequestService --> "-requestStateMachine" RequestStateMachine +@enduml \ No newline at end of file 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 e161b2edc30d..b72f5bffaac5 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 @@ -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.idempotentconsumer; import org.junit.jupiter.api.Test; 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 af779648c8a3..083cff7ad05e 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 @@ -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.idempotentconsumer; import org.junit.jupiter.api.BeforeEach; diff --git a/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml new file mode 100644 index 000000000000..1d4551ed025f --- /dev/null +++ b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml @@ -0,0 +1,68 @@ +@startuml +package com.iluwatar.logaggregation { + class App { + + App() + + main(args : String[]) {static} + } + class CentralLogStore { + - LOGGER : Logger {static} + - logs : ConcurrentLinkedQueue + + CentralLogStore() + + displayLogs() + + storeLog(logEntry : LogEntry) + } + class LogAggregator { + - BUFFER_THRESHOLD : int {static} + - LOGGER : Logger {static} + - buffer : ConcurrentLinkedQueue + - centralLogStore : CentralLogStore + - executorService : ExecutorService + - logCount : AtomicInteger + - minLogLevel : LogLevel + + LogAggregator(centralLogStore : CentralLogStore, minLogLevel : LogLevel) + + collectLog(logEntry : LogEntry) + - flushBuffer() + - startBufferFlusher() + + stop() + } + class LogEntry { + - level : LogLevel + - message : String + - serviceName : String + - timestamp : LocalDateTime + + LogEntry(serviceName : String, level : LogLevel, message : String, timestamp : LocalDateTime) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getLevel() : LogLevel + + getMessage() : String + + getServiceName() : String + + getTimestamp() : LocalDateTime + + hashCode() : int + + setLevel(level : LogLevel) + + setMessage(message : String) + + setServiceName(serviceName : String) + + setTimestamp(timestamp : LocalDateTime) + + toString() : String + } + enum LogLevel { + + DEBUG {static} + + ERROR {static} + + INFO {static} + + valueOf(name : String) : LogLevel {static} + + values() : LogLevel[] {static} + } + class LogProducer { + - LOGGER : Logger {static} + - aggregator : LogAggregator + - serviceName : String + + LogProducer(serviceName : String, aggregator : LogAggregator) + + generateLog(level : LogLevel, message : String) + } +} +LogAggregator --> "-centralLogStore" CentralLogStore +LogEntry --> "-level" LogLevel +CentralLogStore --> "-logs" LogEntry +LogAggregator --> "-buffer" LogEntry +LogAggregator --> "-minLogLevel" LogLevel +LogProducer --> "-aggregator" LogAggregator +@enduml \ No newline at end of file diff --git a/pom.xml b/pom.xml index db06c59a9479..a1478015cd44 100644 --- a/pom.xml +++ b/pom.xml @@ -218,6 +218,7 @@ function-composition microservices-distributed-tracing microservices-idempotent-consumer + Microservice-pattern-Domain-event diff --git a/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml b/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml new file mode 100644 index 000000000000..ca90842d92dd --- /dev/null +++ b/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml @@ -0,0 +1,44 @@ +@startuml +package com.iluwatar.queue.load.leveling { + class App { + - LOGGER : Logger {static} + - SHUTDOWN_TIME : int {static} + + App() + + main(args : String[]) {static} + } + class Message { + - msg : String + + Message(msg : String) + + getMsg() : String + + toString() : String + } + class MessageQueue { + - LOGGER : Logger {static} + - blkQueue : BlockingQueue + + MessageQueue() + + retrieveMsg() : Message + + submitMsg(msg : Message) + } + class ServiceExecutor { + - LOGGER : Logger {static} + - msgQueue : MessageQueue + + ServiceExecutor(msgQueue : MessageQueue) + + run() + } + interface Task { + + submit(Message) {abstract} + } + class TaskGenerator { + - LOGGER : Logger {static} + - msgCount : int + - msgQueue : MessageQueue + + TaskGenerator(msgQueue : MessageQueue, msgCount : int) + + run() + + submit(msg : Message) + } +} +MessageQueue --> "-blkQueue" Message +ServiceExecutor --> "-msgQueue" MessageQueue +TaskGenerator --> "-msgQueue" MessageQueue +TaskGenerator ..|> Task +@enduml \ No newline at end of file diff --git a/type-object/src/main/java/com/iluwatar/typeobject/App.java b/type-object/src/main/java/com/iluwatar/typeobject/App.java index 02058f05fdd2..2fd80a54ee3a 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/App.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/App.java @@ -1,6 +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/update-header.sh b/update-header.sh index 48da4dcd6125..568d00d52a03 100755 --- a/update-header.sh +++ b/update-header.sh @@ -1,4 +1,29 @@ #!/bin/bash +# +# 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. +# + # Find all README.md files in subdirectories one level deep # and replace "### " with "## " at the beginning of lines diff --git a/virtual-proxy/etc/virtual-proxy.urm.puml b/virtual-proxy/etc/virtual-proxy.urm.puml new file mode 100644 index 000000000000..e30149e3809e --- /dev/null +++ b/virtual-proxy/etc/virtual-proxy.urm.puml @@ -0,0 +1,26 @@ +@startuml +package com.iluwatar.virtual.proxy { + class App { + + App() + + main(args : String[]) {static} + } + interface ExpensiveObject { + + process() {abstract} + } + class RealVideoObject { + - LOGGER : Logger {static} + + RealVideoObject() + - heavyInitialConfiguration() + + process() + } + class VideoObjectProxy { + - realVideoObject : RealVideoObject + + VideoObjectProxy() + + getRealVideoObject() : RealVideoObject + + process() + } +} +VideoObjectProxy --> "-realVideoObject" RealVideoObject +RealVideoObject ..|> ExpensiveObject +VideoObjectProxy ..|> ExpensiveObject +@enduml \ No newline at end of file