deps: Refactor dependencies (#3224)

* remove spring dep
move junit, logging, mockito under dep mgmt

* upgrade anti-corruption-layer deps

* async method invocation

* balking, bloc

* bridge to bytecode

* caching

* callback - cqrs

* component - health check

* hexagonal - metadata mapping

* rest of the patterns

* remove checkstyle, take spotless into use
This commit is contained in:
Ilkka Seppälä
2025-03-29 19:34:27 +02:00
committed by GitHub
parent 371439aeaa
commit 0ca162a55c
1863 changed files with 14408 additions and 17637 deletions
@@ -38,71 +38,86 @@ import com.iluwatar.commander.shippingservice.ShippingDatabase;
import com.iluwatar.commander.shippingservice.ShippingService;
/**
* The {@code AppAllCases} class tests various scenarios for the microservices involved
* in the order placement process. This class consolidates previously separated cases
* into a single class to manage different success and failure scenarios for each service.
* The {@code AppAllCases} class tests various scenarios for the microservices involved in the order
* placement process. This class consolidates previously separated cases into a single class to
* manage different success and failure scenarios for each service.
*
* <p>The application consists of abstract classes {@link Database} and {@link Service} which are
* extended by all the databases and services. Each service has a corresponding database to be
* updated and receives requests from an external user through the {@link Commander} class. There
* are 5 microservices:
*
* <p>The application consists of abstract classes {@link Database} and {@link Service}
* which are extended by all the databases and services. Each service has a corresponding
* database to be updated and receives requests from an external user through the
* {@link Commander} class. There are 5 microservices:
* <ul>
* <li>{@link ShippingService}</li>
* <li>{@link PaymentService}</li>
* <li>{@link MessagingService}</li>
* <li>{@link EmployeeHandle}</li>
* <li>{@link QueueDatabase}</li>
* <li>{@link ShippingService}
* <li>{@link PaymentService}
* <li>{@link MessagingService}
* <li>{@link EmployeeHandle}
* <li>{@link QueueDatabase}
* </ul>
*
* <p>Retries are managed using the {@link Retry} class, ensuring idempotence by performing
* checks before making requests to services and updating the {@link Order} class fields
* upon request success or definitive failure.
* <p>Retries are managed using the {@link Retry} class, ensuring idempotence by performing checks
* before making requests to services and updating the {@link Order} class fields upon request
* success or definitive failure.
*
* <p>This class tests the following scenarios:
*
* <ul>
* <li>Employee database availability and unavailability</li>
* <li>Payment service success and failures</li>
* <li>Messaging service database availability and unavailability</li>
* <li>Queue database availability and unavailability</li>
* <li>Shipping service success and failures</li>
* <li>Employee database availability and unavailability
* <li>Payment service success and failures
* <li>Messaging service database availability and unavailability
* <li>Queue database availability and unavailability
* <li>Shipping service success and failures
* </ul>
*
* <p>Each scenario is encapsulated in a corresponding method that sets up the service
* conditions and tests the order placement process.
* <p>Each scenario is encapsulated in a corresponding method that sets up the service conditions
* and tests the order placement process.
*
* <p>The main method executes all success and failure cases to verify the application's
* behavior under different conditions.
* <p>The main method executes all success and failure cases to verify the application's behavior
* under different conditions.
*
* <p><strong>Usage:</strong>
* <pre>
* {@code
*
* <pre>{@code
* public static void main(String[] args) {
* AppAllCases app = new AppAllCases();
* app.testAllScenarios();
* }
* }
* </pre>
* }</pre>
*/
public class AppAllCases {
private static final RetryParams retryParams = RetryParams.DEFAULT;
private static final TimeLimits timeLimits = TimeLimits.DEFAULT;
// Employee Database Fail Case
void employeeDatabaseUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var eh =
new EmployeeHandle(
new EmployeeDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb =
new QueueDatabase(
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
@@ -114,8 +129,11 @@ public class AppAllCases {
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(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh =
new EmployeeHandle(
new EmployeeDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
@@ -127,10 +145,15 @@ public class AppAllCases {
void messagingDatabaseUnavailableCasePaymentSuccess() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ms =
new MessagingService(
new MessagingDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
@@ -140,19 +163,34 @@ public class AppAllCases {
}
void messagingDatabaseUnavailableCasePaymentError() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var ms =
new MessagingService(
new MessagingDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
@@ -161,21 +199,35 @@ public class AppAllCases {
c.placeOrder(order);
}
void messagingDatabaseUnavailableCasePaymentFailure() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ms =
new MessagingService(
new MessagingDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var qdb =
new QueueDatabase(
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
@@ -184,8 +236,11 @@ public class AppAllCases {
// Messaging Database Success Case
void messagingSuccessCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
@@ -198,8 +253,11 @@ public class AppAllCases {
// Payment Database Fail Cases
void paymentNotPossibleCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new PaymentDetailsErrorException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new PaymentDetailsErrorException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
@@ -211,10 +269,15 @@ public class AppAllCases {
}
void paymentDatabaseUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
@@ -227,8 +290,11 @@ public class AppAllCases {
// Payment Database Success Case
void paymentSuccessCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
@@ -241,16 +307,26 @@ public class AppAllCases {
// Queue Database Fail Cases
void queuePaymentTaskDatabaseUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ps =
new PaymentService(
new PaymentDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var qdb =
new QueueDatabase(
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
@@ -260,14 +336,24 @@ public class AppAllCases {
void queueMessageTaskDatabaseUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var ms =
new MessagingService(
new MessagingDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase());
var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var qdb =
new QueueDatabase(
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
@@ -278,20 +364,35 @@ public class AppAllCases {
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(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh =
new EmployeeHandle(
new EmployeeDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb =
new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
new QueueDatabase(
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
@@ -303,8 +404,11 @@ public class AppAllCases {
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(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh =
new EmployeeHandle(
new EmployeeDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
@@ -327,10 +431,16 @@ public class AppAllCases {
void shippingDatabaseUnavailableCase() {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException(),
new DatabaseUnavailableException(), new DatabaseUnavailableException());
var ss =
new ShippingService(
new ShippingDatabase(),
new DatabaseUnavailableException(),
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();
@@ -357,8 +467,11 @@ public class AppAllCases {
var ps = new PaymentService(new PaymentDatabase());
var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException());
var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException());
var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var eh =
new EmployeeHandle(
new EmployeeDatabase(),
new DatabaseUnavailableException(),
new DatabaseUnavailableException());
var qdb = new QueueDatabase();
var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits);
var user = new User("Jim", "ABCD");
@@ -368,6 +481,7 @@ public class AppAllCases {
/**
* Program entry point.
*
* @param args command line arguments
*/
public static void main(String[] args) {
@@ -383,7 +497,7 @@ public class AppAllCases {
app.messagingDatabaseUnavailableCasePaymentFailure();
app.messagingSuccessCase();
//Payment Database cases
// Payment Database cases
app.paymentNotPossibleCase();
app.paymentDatabaseUnavailableCase();
app.paymentSuccessCase();
@@ -400,4 +514,4 @@ public class AppAllCases {
app.shippingItemNotPossibleCase();
app.shippingSuccessCase();
}
}
}
@@ -42,36 +42,34 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>Commander pattern is used to handle all issues that can come up while making a
* distributed transaction. The idea is to have a commander, which coordinates the execution of all
* instructions and ensures proper completion using retries and taking care of idempotence. By
* queueing instructions while they haven't been done, we can ensure a state of 'eventual
* consistency'.</p>
* <p>In our example, we have an e-commerce application. When the user places an order,
* the shipping service is intimated first. If the service does not respond for some reason, the
* order is not placed. If response is received, the commander then calls for the payment service to
* be intimated. If this fails, the shipping still takes place (order converted to COD) and the item
* is queued. If the queue is also found to be unavailable, the payment is noted to be not done and
* Commander pattern is used to handle all issues that can come up while making a distributed
* transaction. The idea is to have a commander, which coordinates the execution of all instructions
* and ensures proper completion using retries and taking care of idempotence. By queueing
* instructions while they haven't been done, we can ensure a state of 'eventual consistency'.
*
* <p>In our example, we have an e-commerce application. When the user places an order, the shipping
* service is intimated first. If the service does not respond for some reason, the order is not
* placed. If response is received, the commander then calls for the payment service to be
* intimated. If this fails, the shipping still takes place (order converted to COD) and the item is
* queued. If the queue is also found to be unavailable, the payment is noted to be not done and
* this is added to an employee database. Three types of messages are sent to the user - one, if
* payment succeeds; two, if payment fails definitively; and three, if payment fails in the first
* attempt. If the message is not sent, this is also queued and is added to employee db. We also
* have a time limit for each instruction to be completed, after which, the instruction is not
* executed, thereby ensuring that resources are not held for too long. In the rare occasion in
* which everything fails, an individual would have to step in to figure out how to solve the
* issue.</p>
* <p>We have abstract classes {@link Database} and {@link Service} which are extended
* by all the databases and services. Each service has a database to be updated, and receives
* request from an outside user (the {@link Commander} class here). There are 5 microservices -
* {@link ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle}
* and a {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class,
* and idempotence is ensured by going through some checks before making requests to services and
* making change in {@link Order} class fields if request succeeds or definitively fails. There is
* a single class {@link AppAllCases} that looks at the different scenarios that may be encountered
* during the placing of an order, including both success and failure cases for each service.</p>
* which everything fails, an individual would have to step in to figure out how to solve the issue.
*
* <p>We have abstract classes {@link Database} and {@link Service} which are extended by all the
* databases and services. Each service has a database to be updated, and receives request from an
* outside user (the {@link Commander} class here). There are 5 microservices - {@link
* ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} and a
* {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, and
* idempotence is ensured by going through some checks before making requests to services and making
* change in {@link Order} class fields if request succeeds or definitively fails. There is a single
* class {@link AppAllCases} that looks at the different scenarios that may be encountered during
* the placing of an order, including both success and failure cases for each service.
*/
public class Commander {
private final QueueDatabase queue;
@@ -79,7 +77,8 @@ public class Commander {
private final PaymentService paymentService;
private final ShippingService shippingService;
private final MessagingService messagingService;
private int queueItems = 0; //keeping track here only so don't need access to queue db to get this
private int queueItems =
0; // keeping track here only so don't need access to queue db to get this
private final int numOfRetries;
private final long retryDuration;
private final long queueTime;
@@ -90,19 +89,24 @@ public class Commander {
private boolean finalSiteMsgShown;
private static final Logger LOG = LoggerFactory.getLogger(Commander.class);
//we could also have another db where it stores all orders
// we could also have another db where it stores all orders
private static final String ORDER_ID = "Order {}";
private static final String REQUEST_ID = " request Id: {}";
private static final String ERROR_CONNECTING_MSG_SVC =
": Error in connecting to messaging service ";
private static final String TRY_CONNECTING_MSG_SVC =
": Trying to connect to messaging service..";
": Error in connecting to messaging service ";
private static final String TRY_CONNECTING_MSG_SVC = ": Trying to connect to messaging service..";
private static final String DEFAULT_EXCEPTION_MESSAGE = "An exception occurred";
Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService,
MessagingService messagingService, QueueDatabase qdb, RetryParams retryParams, TimeLimits timeLimits) {
Commander(
EmployeeHandle empDb,
PaymentService paymentService,
ShippingService shippingService,
MessagingService messagingService,
QueueDatabase qdb,
RetryParams retryParams,
TimeLimits timeLimits) {
this.paymentService = paymentService;
this.shippingService = shippingService;
this.messagingService = messagingService;
@@ -124,47 +128,68 @@ public class Commander {
private void sendShippingRequest(Order order) {
var list = shippingService.exceptionsList;
Retry.Operation op = l -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to shipping service, "
+ "trying again..", order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id);
}
throw l.remove(0);
}
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
//could save this transaction id in a db too
LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}",
order.id, transactionId);
LOG.info("Order has been placed and will be shipped to you. Please wait while we make your"
+ " payment... ");
sendPaymentRequest(order);
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) {
LOG.info("Shipping is currently not possible to your address. We are working on the problem"
+ " and will get back to you asap.");
finalSiteMsgShown = true;
LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem "
+ "to employee db..", order.id);
employeeHandleIssue(o);
} else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) {
LOG.info("This item is currently unavailable. We will inform you as soon as the item "
+ "becomes available again.");
finalSiteMsgShown = true;
LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add "
+ "problem to employee handle..", order.id, order.item);
employeeHandleIssue(o);
} else {
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id);
finalSiteMsgShown = true;
}
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
Retry.Operation op =
l -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(
ORDER_ID + ": Error in connecting to shipping service, " + "trying again..",
order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id);
}
throw l.remove(0);
}
String transactionId = shippingService.receiveRequest(order.item, order.user.address);
// could save this transaction id in a db too
LOG.info(
ORDER_ID + ": Shipping placed successfully, transaction id: {}",
order.id,
transactionId);
LOG.info(
"Order has been placed and will be shipped to you. Please wait while we make your"
+ " payment... ");
sendPaymentRequest(order);
};
Retry.HandleErrorIssue<Order> handleError =
(o, err) -> {
if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) {
LOG.info(
"Shipping is currently not possible to your address. We are working on the problem"
+ " and will get back to you asap.");
finalSiteMsgShown = true;
LOG.info(
ORDER_ID
+ ": Shipping not possible to address, trying to add problem "
+ "to employee db..",
order.id);
employeeHandleIssue(o);
} else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) {
LOG.info(
"This item is currently unavailable. We will inform you as soon as the item "
+ "becomes available again.");
finalSiteMsgShown = true;
LOG.info(
ORDER_ID
+ ": Item {}"
+ " unavailable, trying to add "
+ "problem to employee handle..",
order.id,
order.item);
employeeHandleIssue(o);
} else {
LOG.info("Sorry, there was a problem in creating your order. Please try later.");
LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id);
finalSiteMsgShown = true;
}
};
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
r.perform(list, order);
}
@@ -174,23 +199,30 @@ public class Commander {
order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(order);
LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id);
} //if succeeded or failed, would have been dequeued, no attempt to make payment
} // if succeeded or failed, would have been dequeued, no attempt to make payment
return;
}
var list = paymentService.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = getRetryOperation(order);
var t =
new Thread(
() -> {
Retry.Operation op = getRetryOperation(order);
Retry.HandleErrorIssue<Order> handleError = getRetryHandleErrorIssue(order);
Retry.HandleErrorIssue<Order> handleError = getRetryHandleErrorIssue(order);
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
@@ -203,8 +235,8 @@ public class Commander {
if (o.messageSent.equals(MessageSent.NONE_SENT)) {
handlePaymentError(order.id, o);
}
if (o.paid.equals(PaymentStatus.TRYING) && System
.currentTimeMillis() - o.createdTime < paymentTime) {
if (o.paid.equals(PaymentStatus.TRYING)
&& System.currentTimeMillis() - o.createdTime < paymentTime) {
var qt = new QueueTask(o, TaskType.PAYMENT, -1);
updateQueue(qt);
}
@@ -214,8 +246,9 @@ public class Commander {
private void handlePaymentError(String orderId, Order o) {
if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. We are on it, and will get back to you "
+ "asap. Don't worry, your order has been placed and will be shipped.");
LOG.info(
"There was an error in payment. We are on it, and will get back to you "
+ "asap. Don't worry, your order has been placed and will be shipped.");
finalSiteMsgShown = true;
}
LOG.warn(ORDER_ID + ": Payment error, going to queue..", orderId);
@@ -224,9 +257,10 @@ public class Commander {
private void handlePaymentDetailsError(String orderId, Order o) {
if (!finalSiteMsgShown) {
LOG.info("There was an error in payment. Your account/card details "
+ "may have been incorrect. "
+ "Meanwhile, your order has been converted to COD and will be shipped.");
LOG.info(
"There was an error in payment. Your account/card details "
+ "may have been incorrect. "
+ "Meanwhile, your order has been converted to COD and will be shipped.");
finalSiteMsgShown = true;
}
LOG.error(ORDER_ID + ": Payment details incorrect, failed..", orderId);
@@ -238,8 +272,8 @@ public class Commander {
return l -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ": Error in connecting to payment service,"
+ " trying again..", order.id);
LOG.debug(
ORDER_ID + ": Error in connecting to payment service," + " trying again..", order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id);
}
@@ -248,8 +282,7 @@ public class Commander {
if (order.paid.equals(PaymentStatus.TRYING)) {
var transactionId = paymentService.receiveRequest(order.price);
order.paid = PaymentStatus.DONE;
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}",
order.id, transactionId);
LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", order.id, transactionId);
if (!finalSiteMsgShown) {
LOG.info("Payment made successfully, thank you for shopping with us!!");
finalSiteMsgShown = true;
@@ -266,92 +299,122 @@ public class Commander {
LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id);
return;
} else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING)
|| qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1
&& !qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|| qt.taskType.equals(TaskType.MESSAGING)
&& (qt.messageType == 1 && !qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL)
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL))
|| qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) {
LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id);
return;
}
var list = queue.exceptionsList;
Thread t = new Thread(() -> {
Retry.Operation op = list1 -> {
if (!list1.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id);
throw list1.remove(0);
}
queue.add(qt);
queueItems++;
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
tryDoingTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (qt1, err) -> {
if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order);
LOG.error(ORDER_ID + ": Unable to enqueue payment task,"
+ " payment failed..", qt1.order.id);
}
LOG.error(ORDER_ID + ": Unable to enqueue task of type {}"
+ ", trying to add to employee handle..", qt1.order.id, qt1.getType());
employeeHandleIssue(qt1.order);
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, qt);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
Thread t =
new Thread(
() -> {
Retry.Operation op =
list1 -> {
if (!list1.isEmpty()) {
LOG.warn(
ORDER_ID + ": Error in connecting to queue db, trying again..",
qt.order.id);
throw list1.remove(0);
}
queue.add(qt);
queueItems++;
LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType());
tryDoingTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError =
(qt1, err) -> {
if (qt1.taskType.equals(TaskType.PAYMENT)) {
qt1.order.paid = PaymentStatus.NOT_DONE;
sendPaymentFailureMessage(qt1.order);
LOG.error(
ORDER_ID + ": Unable to enqueue payment task," + " payment failed..",
qt1.order.id);
}
LOG.error(
ORDER_ID
+ ": Unable to enqueue task of type {}"
+ ", trying to add to employee handle..",
qt1.order.id,
qt1.getType());
employeeHandleIssue(qt1.order);
};
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, qt);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
private void tryDoingTasksInQueue() { //commander controls operations done to queue
private void tryDoingTasksInQueue() { // commander controls operations done to queue
var list = queue.exceptionsList;
var t2 = new Thread(() -> {
Retry.Operation op = list1 -> {
if (!list1.isEmpty()) {
LOG.warn("Error in accessing queue db to do tasks, trying again..");
throw list1.remove(0);
}
doTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (o, err) -> {
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, null);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var t2 =
new Thread(
() -> {
Retry.Operation op =
list1 -> {
if (!list1.isEmpty()) {
LOG.warn("Error in accessing queue db to do tasks, trying again..");
throw list1.remove(0);
}
doTasksInQueue();
};
Retry.HandleErrorIssue<QueueTask> handleError = (o, err) -> {};
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, null);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t2.start();
}
private void tryDequeue() {
var list = queue.exceptionsList;
var t3 = new Thread(() -> {
Retry.Operation op = list1 -> {
if (!list1.isEmpty()) {
LOG.warn("Error in accessing queue db to dequeue task, trying again..");
throw list1.remove(0);
}
queue.dequeue();
queueItems--;
};
Retry.HandleErrorIssue<QueueTask> handleError = (o, err) -> {
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, null);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var t3 =
new Thread(
() -> {
Retry.Operation op =
list1 -> {
if (!list1.isEmpty()) {
LOG.warn("Error in accessing queue db to dequeue task, trying again..");
throw list1.remove(0);
}
queue.dequeue();
queueItems--;
};
Retry.HandleErrorIssue<QueueTask> handleError = (o, err) -> {};
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, null);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t3.start();
}
@@ -361,28 +424,39 @@ public class Commander {
return;
}
var list = messagingService.exceptionsList;
Thread t = new Thread(() -> {
Retry.Operation op = handleSuccessMessageRetryOperation(order);
Retry.HandleErrorIssue<Order> handleError = (o, err) -> handleSuccessMessageErrorIssue(order, o);
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
Thread t =
new Thread(
() -> {
Retry.Operation op = handleSuccessMessageRetryOperation(order);
Retry.HandleErrorIssue<Order> handleError =
(o, err) -> handleSuccessMessageErrorIssue(order, o);
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
private void handleSuccessMessageErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
if ((o.messageSent.equals(MessageSent.NONE_SENT)
|| o.messageSent.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 2);
updateQueue(qt);
LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..", order.id);
LOG.info(
ORDER_ID
+ ": Error in sending Payment Success message, trying to"
+ " queue task and add to employee handle..",
order.id);
employeeHandleIssue(order);
}
}
@@ -391,11 +465,12 @@ public class Commander {
return l -> {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Success msg), trying again..", order.id);
LOG.debug(
ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Success msg), trying again..",
order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Success"
+ " messaging request..", order.id);
LOG.debug(
ORDER_ID + ": Error in creating Payment Success" + " messaging request..", order.id);
}
throw l.remove(0);
}
@@ -403,8 +478,7 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(2);
order.messageSent = MessageSent.PAYMENT_SUCCESSFUL;
LOG.info(ORDER_ID + ": Payment Success message sent,"
+ REQUEST_ID, order.id, requestId);
LOG.info(ORDER_ID + ": Payment Success message sent," + REQUEST_ID, order.id, requestId);
}
};
}
@@ -415,38 +489,53 @@ public class Commander {
return;
}
var list = messagingService.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = l -> handlePaymentFailureRetryOperation(order, l);
Retry.HandleErrorIssue<Order> handleError = (o, err) -> handlePaymentErrorIssue(order, o);
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var t =
new Thread(
() -> {
Retry.Operation op = l -> handlePaymentFailureRetryOperation(order, l);
Retry.HandleErrorIssue<Order> handleError =
(o, err) -> handlePaymentErrorIssue(order, o);
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
private void handlePaymentErrorIssue(Order order, Order o) {
if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent
.equals(MessageSent.PAYMENT_TRYING))
if ((o.messageSent.equals(MessageSent.NONE_SENT)
|| o.messageSent.equals(MessageSent.PAYMENT_TRYING))
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 0);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..", order.id);
LOG.warn(
ORDER_ID
+ ": Error in sending Payment Failure message, "
+ "trying to queue task and add to employee handle..",
order.id);
employeeHandleIssue(o);
}
}
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l) throws IndexOutOfBoundsException, DatabaseUnavailableException {
private void handlePaymentFailureRetryOperation(Order order, List<Exception> l)
throws IndexOutOfBoundsException, DatabaseUnavailableException {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Failure msg), trying again..", order.id);
LOG.debug(
ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Failure msg), trying again..",
order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Failure" + " message request..", order.id);
LOG.debug(
ORDER_ID + ": Error in creating Payment Failure" + " message request..", order.id);
}
throw new IndexOutOfBoundsException();
}
@@ -454,8 +543,10 @@ public class Commander {
&& !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
var requestId = messagingService.receiveRequest(0);
order.messageSent = MessageSent.PAYMENT_FAIL;
LOG.info(ORDER_ID + ": Payment Failure message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info(
ORDER_ID + ": Payment Failure message sent successfully," + REQUEST_ID,
order.id,
requestId);
}
}
@@ -465,28 +556,37 @@ public class Commander {
return;
}
var list = messagingService.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = l -> handlePaymentPossibleErrorMsgRetryOperation(order, l);
Retry.HandleErrorIssue<Order> handleError = (o, err) -> handlePaymentPossibleErrorMsgErrorIssue(order, o);
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var t =
new Thread(
() -> {
Retry.Operation op = l -> handlePaymentPossibleErrorMsgRetryOperation(order, l);
Retry.HandleErrorIssue<Order> handleError =
(o, err) -> handlePaymentPossibleErrorMsgErrorIssue(order, o);
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) {
if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid
.equals(PaymentStatus.TRYING)
if (o.messageSent.equals(MessageSent.NONE_SENT)
&& order.paid.equals(PaymentStatus.TRYING)
&& System.currentTimeMillis() - o.createdTime < messageTime) {
var qt = new QueueTask(order, TaskType.MESSAGING, 1);
updateQueue(qt);
LOG.warn("Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..",
order.id);
LOG.warn(
"Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..",
order.id);
employeeHandleIssue(o);
}
}
@@ -495,20 +595,22 @@ public class Commander {
throws IndexOutOfBoundsException, DatabaseUnavailableException {
if (!l.isEmpty()) {
if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) {
LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC
+ "(Payment Error msg), trying again..", order.id);
LOG.debug(
ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Error msg), trying again..", order.id);
} else {
LOG.debug(ORDER_ID + ": Error in creating Payment Error"
+ " messaging request..", order.id);
LOG.debug(
ORDER_ID + ": Error in creating Payment Error" + " messaging request..", order.id);
}
throw new IndexOutOfBoundsException();
}
if (order.paid.equals(PaymentStatus.TRYING) && order.messageSent
.equals(MessageSent.NONE_SENT)) {
if (order.paid.equals(PaymentStatus.TRYING)
&& order.messageSent.equals(MessageSent.NONE_SENT)) {
var requestId = messagingService.receiveRequest(1);
order.messageSent = MessageSent.PAYMENT_TRYING;
LOG.info(ORDER_ID + ": Payment Error message sent successfully,"
+ REQUEST_ID, order.id, requestId);
LOG.info(
ORDER_ID + ": Payment Error message sent successfully," + REQUEST_ID,
order.id,
requestId);
}
}
@@ -518,51 +620,70 @@ public class Commander {
return;
}
var list = employeeDb.exceptionsList;
var t = new Thread(() -> {
Retry.Operation op = l -> {
if (!l.isEmpty()) {
LOG.warn(ORDER_ID + ": Error in connecting to employee handle,"
+ " trying again..", order.id);
throw l.remove(0);
}
if (!order.addedToEmployeeHandle) {
employeeDb.receiveRequest(order);
order.addedToEmployeeHandle = true;
LOG.info(ORDER_ID + ": Added order to employee database", order.id);
}
};
Retry.HandleErrorIssue<Order> handleError = (o, err) -> {
if (!o.addedToEmployeeHandle && System
.currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt);
LOG.warn(ORDER_ID + ": Error in adding to employee db,"
+ " trying to queue task..", order.id);
}
};
var r = new Retry<>(op, handleError, numOfRetries, retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
var t =
new Thread(
() -> {
Retry.Operation op =
l -> {
if (!l.isEmpty()) {
LOG.warn(
ORDER_ID
+ ": Error in connecting to employee handle,"
+ " trying again..",
order.id);
throw l.remove(0);
}
if (!order.addedToEmployeeHandle) {
employeeDb.receiveRequest(order);
order.addedToEmployeeHandle = true;
LOG.info(ORDER_ID + ": Added order to employee database", order.id);
}
};
Retry.HandleErrorIssue<Order> handleError =
(o, err) -> {
if (!o.addedToEmployeeHandle
&& System.currentTimeMillis() - order.createdTime < employeeTime) {
var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1);
updateQueue(qt);
LOG.warn(
ORDER_ID
+ ": Error in adding to employee db,"
+ " trying to queue task..",
order.id);
}
};
var r =
new Retry<>(
op,
handleError,
numOfRetries,
retryDuration,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
try {
r.perform(list, order);
} catch (Exception e1) {
LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1);
}
});
t.start();
}
private void doTasksInQueue() throws IsEmptyException, InterruptedException {
if (queueItems != 0) {
var qt = queue.peek(); //this should probably be cloned here
//this is why we have retry for doTasksInQueue
var qt = queue.peek(); // this should probably be cloned here
// this is why we have retry for doTasksInQueue
LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType());
if (qt.isFirstAttempt()) {
qt.setFirstAttemptTime(System.currentTimeMillis());
}
if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) {
tryDequeue();
LOG.trace(ORDER_ID + ": This queue task of type {}"
+ " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType());
LOG.trace(
ORDER_ID
+ ": This queue task of type {}"
+ " does not need to be done anymore (timeout), dequeue..",
qt.order.id,
qt.getType());
} else {
switch (qt.taskType) {
case PAYMENT -> doPaymentTask(qt);
@@ -583,8 +704,7 @@ public class Commander {
private void doEmployeeDbTask(QueueTask qt) {
if (qt.order.addedToEmployeeHandle) {
tryDequeue();
LOG.trace(ORDER_ID + ": This employee handle task already done,"
+ " dequeue..", qt.order.id);
LOG.trace(ORDER_ID + ": This employee handle task already done," + " dequeue..", qt.order.id);
} else {
employeeHandleIssue(qt.order);
LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id);
@@ -596,11 +716,12 @@ public class Commander {
|| qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id);
} else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
} else if (qt.messageType == 1
&& (!qt.order.messageSent.equals(MessageSent.NONE_SENT)
|| !qt.order.paid.equals(PaymentStatus.TRYING))) {
tryDequeue();
LOG.trace(ORDER_ID + ": This messaging task does not need to be done,"
+ " dequeue..", qt.order.id);
LOG.trace(
ORDER_ID + ": This messaging task does not need to be done," + " dequeue..", qt.order.id);
} else if (qt.messageType == 0) {
sendPaymentFailureMessage(qt.order);
LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id);
@@ -622,5 +743,4 @@ public class Commander {
LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id);
}
}
}
}
@@ -32,7 +32,6 @@ import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
*
* @param <T> T is the type of object being held by database.
*/
public abstract class Database<T> {
public abstract T add(T obj) throws DatabaseUnavailableException;
@@ -28,11 +28,8 @@ import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
/**
* Order class holds details of the order.
*/
public class Order { //can store all transactions ids also
/** Order class holds details of the order. */
public class Order { // can store all transactions ids also
enum PaymentStatus {
NOT_DONE,
@@ -56,8 +53,8 @@ public class Order { //can store all transactions ids also
private static final String ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
private static final Map<String, Boolean> USED_IDS = new HashMap<>();
PaymentStatus paid;
MessageSent messageSent; //to avoid sending error msg on page and text more than once
boolean addedToEmployeeHandle; //to avoid creating more to enqueue
MessageSent messageSent; // to avoid sending error msg on page and text more than once
boolean addedToEmployeeHandle; // to avoid creating more to enqueue
Order(User user, String item, float price) {
this.createdTime = System.currentTimeMillis();
@@ -85,5 +82,4 @@ public class Order { //can store all transactions ids also
}
return random.toString();
}
}
@@ -36,13 +36,9 @@ import java.util.function.Predicate;
*
* @param <T> is the type of object passed into HandleErrorIssue as a parameter.
*/
public class Retry<T> {
/**
* Operation Interface will define method to be implemented.
*/
/** Operation Interface will define method to be implemented. */
public interface Operation {
void operation(List<Exception> list) throws Exception;
}
@@ -52,7 +48,6 @@ public class Retry<T> {
*
* @param <T> is the type of object to be passed into the method as parameter.
*/
public interface HandleErrorIssue<T> {
void handleIssue(T obj, Exception e);
}
@@ -67,8 +62,12 @@ public class Retry<T> {
private final Predicate<Exception> test;
private final List<Exception> errors;
Retry(Operation op, HandleErrorIssue<T> handleError, int maxAttempts,
long maxDelay, Predicate<Exception>... ignoreTests) {
Retry(
Operation op,
HandleErrorIssue<T> handleError,
int maxAttempts,
long maxDelay,
Predicate<Exception>... ignoreTests) {
this.op = op;
this.handleError = handleError;
this.maxAttempts = maxAttempts;
@@ -82,9 +81,8 @@ public class Retry<T> {
* Performing the operation with retries.
*
* @param list is the exception list
* @param obj is the parameter to be passed into handleIsuue method
* @param obj is the parameter to be passed into handleIsuue method
*/
public void perform(List<Exception> list, T obj) {
do {
try {
@@ -94,7 +92,7 @@ public class Retry<T> {
this.errors.add(e);
if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
this.handleError.handleIssue(obj, e);
return; //return here... don't go further
return; // return here... don't go further
}
try {
long testDelay =
@@ -102,10 +100,9 @@ public class Retry<T> {
long delay = Math.min(testDelay, this.maxDelay);
Thread.sleep(delay);
} catch (InterruptedException f) {
//ignore
// ignore
}
}
} while (true);
}
}
@@ -26,9 +26,10 @@ package com.iluwatar.commander;
/**
* Record to hold the parameters related to retries.
*
* @param numOfRetries number of retries
* @param retryDuration retry duration
*/
public record RetryParams(int numOfRetries, long retryDuration) {
public static final RetryParams DEFAULT = new RetryParams(3, 30000L);
}
}
@@ -38,7 +38,6 @@ import java.util.List;
* for the transactions/requests, which are then sent back. These could be stored by the {@link
* Commander} class in a separate database for reference (though we are not doing that here).
*/
public abstract class Service {
protected final Database database;
@@ -25,16 +25,17 @@
package com.iluwatar.commander;
/**
* Record to hold parameters related to time limit
* for various tasks.
* Record to hold parameters related to time limit for various tasks.
*
* @param queueTime time limit for queue
* @param queueTaskTime time limit for queuing task
* @param paymentTime time limit for payment error message
* @param messageTime time limit for message time order
* @param employeeTime time limit for employee handle time
*/
public record TimeLimits(long queueTime, long queueTaskTime, long paymentTime,
long messageTime, long employeeTime) {
public record TimeLimits(
long queueTime, long queueTaskTime, long paymentTime, long messageTime, long employeeTime) {
public static final TimeLimits DEFAULT = new TimeLimits(240000L, 60000L, 120000L, 150000L, 240000L);
}
public static final TimeLimits DEFAULT =
new TimeLimits(240000L, 60000L, 120000L, 150000L, 240000L);
}
@@ -26,9 +26,7 @@ package com.iluwatar.commander;
import lombok.AllArgsConstructor;
/**
* User class contains details of user who places order.
*/
/** User class contains details of user who places order. */
@AllArgsConstructor
public class User {
String name;
@@ -30,10 +30,7 @@ import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
import java.util.HashMap;
import java.util.Map;
/**
* The Employee Database is where orders which have encountered some issue(s) are added.
*/
/** The Employee Database is where orders which have encountered some issue(s) are added. */
public class EmployeeDatabase extends Database<Order> {
private final Map<String, Order> data = new HashMap<>();
@@ -32,7 +32,6 @@ import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
* The EmployeeHandle class is the middle-man between {@link com.iluwatar.commander.Commander} and
* {@link EmployeeDatabase}.
*/
public class EmployeeHandle extends Service {
public EmployeeHandle(EmployeeDatabase db, Exception... exc) {
@@ -47,9 +46,8 @@ public class EmployeeHandle extends Service {
var o = (Order) parameters[0];
if (database.get(o.id) == null) {
database.add(o);
return o.id; //true rcvd - change addedToEmployeeHandle to true else don't do anything
return o.id; // true rcvd - change addedToEmployeeHandle to true else don't do anything
}
return null;
}
}
@@ -28,7 +28,6 @@ package com.iluwatar.commander.exceptions;
* DatabaseUnavailableException is thrown when database is unavailable and nothing can be added or
* retrieved.
*/
public class DatabaseUnavailableException extends Exception {
private static final long serialVersionUID = 2459603L;
}
@@ -24,10 +24,7 @@
*/
package com.iluwatar.commander.exceptions;
/**
* IsEmptyException is thrown when it is attempted to dequeue from an empty queue.
*/
/** IsEmptyException is thrown when it is attempted to dequeue from an empty queue. */
public class IsEmptyException extends Exception {
private static final long serialVersionUID = 123546L;
}
@@ -24,10 +24,7 @@
*/
package com.iluwatar.commander.exceptions;
/**
* ItemUnavailableException is thrown when item is not available for shipping.
*/
/** ItemUnavailableException is thrown when item is not available for shipping. */
public class ItemUnavailableException extends Exception {
private static final long serialVersionUID = 575940L;
}
@@ -28,7 +28,6 @@ package com.iluwatar.commander.exceptions;
* PaymentDetailsErrorException is thrown when the details entered are incorrect or payment cannot
* be made with the details given.
*/
public class PaymentDetailsErrorException extends Exception {
private static final long serialVersionUID = 867203L;
}
@@ -28,7 +28,6 @@ package com.iluwatar.commander.exceptions;
* ShippingNotPossibleException is thrown when the address entered cannot be shipped to by service
* currently for some reason.
*/
public class ShippingNotPossibleException extends Exception {
private static final long serialVersionUID = 342055L;
}
@@ -29,10 +29,7 @@ import com.iluwatar.commander.messagingservice.MessagingService.MessageRequest;
import java.util.Hashtable;
import java.util.Map;
/**
* The MessagingDatabase is where the MessageRequest is added.
*/
/** The MessagingDatabase is where the MessageRequest is added. */
public class MessagingDatabase extends Database<MessageRequest> {
private final Map<String, MessageRequest> data = new Hashtable<>();
@@ -45,5 +42,4 @@ public class MessagingDatabase extends Database<MessageRequest> {
public MessageRequest get(String requestId) {
return data.get(requestId);
}
}
@@ -26,7 +26,6 @@ package com.iluwatar.commander.messagingservice;
import com.iluwatar.commander.Service;
import com.iluwatar.commander.exceptions.DatabaseUnavailableException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
@@ -34,7 +33,6 @@ import lombok.extern.slf4j.Slf4j;
* In case an error is encountered in payment and this service is found to be unavailable, the order
* is added to the {@link com.iluwatar.commander.employeehandle.EmployeeDatabase}.
*/
@Slf4j
public class MessagingService extends Service {
@@ -50,9 +48,7 @@ public class MessagingService extends Service {
super(db, exc);
}
/**
* Public method which will receive request from {@link com.iluwatar.commander.Commander}.
*/
/** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */
public String receiveRequest(Object... parameters) throws DatabaseUnavailableException {
var messageToSend = (int) parameters[0];
var id = generateId();
@@ -61,7 +57,7 @@ public class MessagingService extends Service {
msg = MessageToSend.PAYMENT_FAIL;
} else if (messageToSend == 1) {
msg = MessageToSend.PAYMENT_TRYING;
} else { //messageToSend == 2
} else { // messageToSend == 2
msg = MessageToSend.PAYMENT_SUCCESSFUL;
}
var req = new MessageRequest(id, msg);
@@ -70,8 +66,8 @@ public class MessagingService extends Service {
protected String updateDb(Object... parameters) throws DatabaseUnavailableException {
var req = (MessageRequest) parameters[0];
if (this.database.get(req.reqId) == null) { //idempotence, in case db fails here
database.add(req); //if successful:
if (this.database.get(req.reqId) == null) { // idempotence, in case db fails here
database.add(req); // if successful:
LOGGER.info(sendMessage(req.msg));
return req.reqId;
}
@@ -29,12 +29,10 @@ import com.iluwatar.commander.paymentservice.PaymentService.PaymentRequest;
import java.util.Hashtable;
import java.util.Map;
/**
* PaymentDatabase is where the PaymentRequest is added, along with details.
*/
/** PaymentDatabase is where the PaymentRequest is added, along with details. */
public class PaymentDatabase extends Database<PaymentRequest> {
//0-fail, 1-error, 2-success
// 0-fail, 1-error, 2-success
private final Map<String, PaymentRequest> data = new Hashtable<>();
@Override
@@ -46,5 +44,4 @@ public class PaymentDatabase extends Database<PaymentRequest> {
public PaymentRequest get(String requestId) {
return data.get(requestId);
}
}
@@ -32,7 +32,6 @@ import lombok.RequiredArgsConstructor;
* The PaymentService class receives request from the {@link com.iluwatar.commander.Commander} and
* adds to the {@link PaymentDatabase}.
*/
public class PaymentService extends Service {
@RequiredArgsConstructor
@@ -46,12 +45,9 @@ public class PaymentService extends Service {
super(db, exc);
}
/**
* Public method which will receive request from {@link com.iluwatar.commander.Commander}.
*/
/** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */
public String receiveRequest(Object... parameters) throws DatabaseUnavailableException {
//it could also be sending an userid, payment details here or something, not added here
// it could also be sending an userid, payment details here or something, not added here
var id = generateId();
var req = new PaymentRequest(id, (float) parameters[0]);
return updateDb(req);
@@ -29,10 +29,7 @@ import com.iluwatar.commander.exceptions.IsEmptyException;
import java.util.ArrayList;
import java.util.List;
/**
* QueueDatabase id where the instructions to be implemented are queued.
*/
/** QueueDatabase id where the instructions to be implemented are queued. */
public class QueueDatabase extends Database<QueueTask> {
private final Queue<QueueTask> data;
@@ -47,16 +44,15 @@ public class QueueDatabase extends Database<QueueTask> {
public QueueTask add(QueueTask t) {
data.enqueue(t);
return t;
//even if same thing queued twice, it is taken care of in other dbs
// even if same thing queued twice, it is taken care of in other dbs
}
/**
* peek method returns object at front without removing it from queue.
*
* @return object at front of queue
* @throws IsEmptyException if queue is empty
* @throws IsEmptyException if queue is empty
*/
public QueueTask peek() throws IsEmptyException {
return this.data.peek();
}
@@ -65,9 +61,8 @@ public class QueueDatabase extends Database<QueueTask> {
* dequeue method removes the object at front and returns it.
*
* @return object at front of queue
* @throws IsEmptyException if queue is empty
* @throws IsEmptyException if queue is empty
*/
public QueueTask dequeue() throws IsEmptyException {
return this.data.dequeue();
}
@@ -76,5 +71,4 @@ public class QueueDatabase extends Database<QueueTask> {
public QueueTask get(String taskId) {
return null;
}
}
@@ -29,15 +29,11 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
/**
* QueueTask object is the object enqueued in queue.
*/
/** QueueTask object is the object enqueued in queue. */
@RequiredArgsConstructor
public class QueueTask {
/**
* TaskType is the type of task to be done.
*/
/** TaskType is the type of task to be done. */
public enum TaskType {
MESSAGING,
PAYMENT,
@@ -46,13 +42,11 @@ public class QueueTask {
public final Order order;
public final TaskType taskType;
public final int messageType; //0-fail, 1-error, 2-success
public final int messageType; // 0-fail, 1-error, 2-success
/*we could have varargs Object instead to pass in any parameter instead of just message type
but keeping it simple here*/
@Getter
@Setter
private long firstAttemptTime = -1L; //when first time attempt made to do task
@Getter @Setter private long firstAttemptTime = -1L; // when first time attempt made to do task
/**
* getType method.
@@ -76,4 +70,4 @@ public class QueueTask {
public boolean isFirstAttempt() {
return this.firstAttemptTime == -1L;
}
}
}
@@ -29,10 +29,7 @@ import com.iluwatar.commander.shippingservice.ShippingService.ShippingRequest;
import java.util.Hashtable;
import java.util.Map;
/**
* ShippingDatabase is where the ShippingRequest objects are added.
*/
/** ShippingDatabase is where the ShippingRequest objects are added. */
public class ShippingDatabase extends Database<ShippingRequest> {
private final Map<String, ShippingRequest> data = new Hashtable<>();
@@ -45,5 +42,4 @@ public class ShippingDatabase extends Database<ShippingRequest> {
public ShippingRequest get(String trasnactionId) {
return data.get(trasnactionId);
}
}
@@ -32,7 +32,6 @@ import lombok.AllArgsConstructor;
* ShippingService class receives request from {@link com.iluwatar.commander.Commander} class and
* adds it to the {@link ShippingDatabase}.
*/
public class ShippingService extends Service {
@AllArgsConstructor
@@ -46,10 +45,7 @@ public class ShippingService extends Service {
super(db, exc);
}
/**
* Public method which will receive request from {@link com.iluwatar.commander.Commander}.
*/
/** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */
public String receiveRequest(Object... parameters) throws DatabaseUnavailableException {
var id = generateId();
var item = (String) parameters[0];
File diff suppressed because it is too large Load Diff
@@ -40,33 +40,47 @@ class RetryTest {
@Test
void performTest() {
Retry.Operation op = (l) -> {
if (!l.isEmpty()) {
throw l.remove(0);
}
};
Retry.HandleErrorIssue<Order> handleError = (o, e) -> {
};
var r1 = new Retry<>(op, handleError, 3, 30000,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
var r2 = new Retry<>(op, handleError, 3, 30000,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
Retry.Operation op =
(l) -> {
if (!l.isEmpty()) {
throw l.remove(0);
}
};
Retry.HandleErrorIssue<Order> handleError = (o, e) -> {};
var r1 =
new Retry<>(
op,
handleError,
3,
30000,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
var r2 =
new Retry<>(
op,
handleError,
3,
30000,
e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass()));
var user = new User("Jim", "ABCD");
var order = new Order(user, "book", 10f);
var arr1 = new ArrayList<>(List.of(new ItemUnavailableException(), new DatabaseUnavailableException()));
var arr1 =
new ArrayList<>(
List.of(new ItemUnavailableException(), new DatabaseUnavailableException()));
try {
r1.perform(arr1, order);
} catch (Exception e1) {
LOG.error("An exception occurred", e1);
}
var arr2 = new ArrayList<>(List.of(new DatabaseUnavailableException(), new ItemUnavailableException()));
var arr2 =
new ArrayList<>(
List.of(new DatabaseUnavailableException(), new ItemUnavailableException()));
try {
r2.perform(arr2, order);
} catch (Exception e1) {
LOG.error("An exception occurred", e1);
}
//r1 stops at ItemUnavailableException, r2 retries because it encounters DatabaseUnavailableException
// r1 stops at ItemUnavailableException, r2 retries because it encounters
// DatabaseUnavailableException
assertTrue(arr1.size() == 1 && arr2.isEmpty());
}
}