docs: updates to several patterns

This commit is contained in:
Ilkka Seppälä
2024-05-28 19:46:39 +03:00
parent 4652842c94
commit 584e949714
70 changed files with 347 additions and 479 deletions
+91 -43
View File
@@ -29,64 +29,112 @@ In plain words
**Programmatic Example**
Managing transactions across different services in a distributed system, such as an e-commerce platform with separate Payment and Shipping microservices, requires careful coordination to avoid issues. When a user places an order but one service (e.g., Payment) is unavailable while the other (e.g., Shipping) is ready, we need a robust solution to handle this discrepancy.
Managing transactions across different services in a distributed system, such as an e-commerce platform with separate `Payment` and `Shipping` microservices, requires careful coordination to avoid issues. When a user places an order but one service (e.g., `Payment`) is unavailable while the other (e.g., `Shipping`) is ready, we need a robust solution to handle this discrepancy.
A strategy to address this involves using a Commander component that orchestrates the process. Initially, the order is processed by the available service (Shipping in this case). The commander then attempts to synchronize the order with the currently unavailable service (Payment) by storing the order details in a database or queueing it for future processing. This queueing system must also account for possible failures in adding requests to the queue.
A strategy to address this involves using a `Commander` component that orchestrates the process. Initially, the order is processed by the available service (`Shipping` in this case). The commander then attempts to synchronize the order with the currently unavailable service (`Payment`) by storing the order details in a database or queueing it for future processing. This queueing system must also account for possible failures in adding requests to the queue.
The commander repeatedly tries to process the queued orders to ensure both services eventually reflect the same transaction data. This process involves ensuring idempotence, meaning that even if the same order synchronization request is made multiple times, it will only be executed once, preventing duplicate transactions. The goal is to achieve eventual consistency across services, where all systems are synchronized over time despite initial failures or delays.
The `Commander` repeatedly tries to process the queued orders to ensure both services eventually reflect the same transaction data. This process involves ensuring idempotence, meaning that even if the same order synchronization request is made multiple times, it will only be executed once, preventing duplicate transactions. The goal is to achieve eventual consistency across services, where all systems are synchronized over time despite initial failures or delays.
In the provided code, the Commander pattern is used to handle distributed transactions across multiple services (PaymentService, ShippingService, MessagingService, EmployeeHandle). Each service has its own database and can throw exceptions to simulate failures.
The Commander class is the central part of this pattern. It takes instances of all services and their databases, along with some configuration parameters. The placeOrder method in the Commander class is used to place an order, which involves interacting with all the services.
To get a grasp of how this works in practice, let's see `AppShippingFailCases` class and explain afterward how it works.
```java
public class Commander {
// ... constructor and other methods ...
public class AppShippingFailCases {
public void placeOrder(Order order) {
// ... implementation ...
private static final RetryParams retryParams = RetryParams.DEFAULT;
private static final TimeLimits timeLimits = TimeLimits.DEFAULT;
void itemUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
c.placeOrder(order);
}
void shippingNotPossibleCase() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new ShippingNotPossibleException());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
c.placeOrder(order);
}
void shippingDatabaseUnavailableCase() {
//rest is successful
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
c.placeOrder(order);
}
void shippingSuccessCase() {
//goes to payment after 2 retries maybe - rest is successful for now
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
c.placeOrder(order);
}
public static void main(String[] args) {
var asfc = new AppShippingFailCases();
asfc.shippingSuccessCase();
}
}
```
The User and Order classes represent a user and an order respectively. An order is placed by a user.
The `AppShippingFailCases` class is designed to simulate different scenarios where the Shipping service is available or unavailable. It uses the Commander pattern to handle distributed transactions across multiple services.
```java
public class User {
// ... constructor and other methods ...
}
Here's a breakdown of the methods in the `AppShippingFailCases` class:
1. `itemUnavailableCase`: This method simulates a scenario where the item to be shipped is unavailable. It creates instances of the `Commander` class with the `ShippingService` throwing an `ItemUnavailableException`. An order is placed and the system tries to handle this failure.
2. `shippingNotPossibleCase`: This method simulates a scenario where shipping is not possible. It creates instances of the `Commander` class with the `ShippingService` throwing a `ShippingNotPossibleException`. An order is placed and the system tries to handle this failure.
3. `shippingDatabaseUnavailableCase`: This method simulates a scenario where the `ShippingService` and `ShippingDatabase` are unavailable. It creates instances of the `Commander` class with the `ShippingService` throwing multiple `DatabaseUnavailableException`. An order is placed and the system tries to handle this failure.
4. `shippingSuccessCase`: This method simulates a successful scenario where all services are available except for some temporary unavailability of the `PaymentService`, `ShippingService`, and `MessagingService`. An order is placed and the system handles this situation.
In each of these methods, a `Commander` instance is created with the respective services and their databases. Then, a `User` and an `Order` are created, and the `placeOrder` method of the `Commander` instance is called with the order. This triggers the process of placing the order and handling any failures according to the Commander pattern.
In the `main` method, the `shippingSuccessCase` method is called to simulate a successful scenario.
Finally, let's execute the `main` method see the program output.
public class Order {
// ... constructor and other methods ...
}
```
Each service (e.g., PaymentService, ShippingService, MessagingService, EmployeeHandle) has its own database and can throw exceptions to simulate failures. For example, the PaymentService might throw a DatabaseUnavailableException if its database is unavailable.
```java
public class PaymentService {
// ... constructor and other methods ...
}
18:01:07.738 [main] DEBUG com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Error in connecting to shipping service, trying again..
18:01:10.536 [main] DEBUG com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Error in connecting to shipping service, trying again..
18:01:15.401 [main] INFO com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Shipping placed successfully, transaction id: RCM0PO9N9B6J
18:01:15.401 [main] INFO com.iluwatar.commander.Commander -- Order has been placed and will be shipped to you. Please wait while we make your payment...
18:01:15.407 [Thread-0] DEBUG com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Error in connecting to payment service, trying again..
18:01:18.327 [Thread-0] INFO com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Payment successful, transaction Id: UWS72C00JN9Q
18:01:18.328 [Thread-0] INFO com.iluwatar.commander.Commander -- Payment made successfully, thank you for shopping with us!!
18:01:18.332 [Thread-1] DEBUG com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Error in connecting to messaging service (Payment Success msg), trying again..
18:01:20.693 [Thread-1] INFO com.iluwatar.commander.messagingservice.MessagingService -- Msg: Your order has been placed and paid for successfully! Thank you for shopping with us!
18:01:20.694 [Thread-1] INFO com.iluwatar.commander.Commander -- Order I07V78ZOB8RZ: Payment Success message sent, request Id: 72DOWH1D0WYS
```
The DatabaseUnavailableException, ItemUnavailableException, and ShippingNotPossibleException classes represent different types of exceptions that can occur.
```java
public class DatabaseUnavailableException extends Exception {
// ... constructor and other methods ...
}
public class ItemUnavailableException extends Exception {
// ... constructor and other methods ...
}
public class ShippingNotPossibleException extends Exception {
// ... constructor and other methods ...
}
```
In the main method of each class (AppQueueFailCases, AppShippingFailCases), different scenarios are simulated by creating instances of the Commander class with different configurations and calling the placeOrder method.
## Applicability
Use the Commander pattern for distributed transactions when:
@@ -134,4 +134,4 @@ public class AppQueueFailCases {
var aqfc = new AppQueueFailCases();
aqfc.queueSuccessCase();
}
}
}
@@ -43,8 +43,8 @@ import com.iluwatar.commander.shippingservice.ShippingService;
*/
public class AppShippingFailCases {
private static final RetryParams retryParams = RetryParams.DEFAULT;
private static final RetryParams retryParams = RetryParams.DEFAULT;
private static final TimeLimits timeLimits = TimeLimits.DEFAULT;
void itemUnavailableCase() {
@@ -111,4 +111,4 @@ public class AppShippingFailCases {
var asfc = new AppShippingFailCases();
asfc.shippingSuccessCase();
}
}
}