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 14403 additions and 17632 deletions
+30 -28
View File
@@ -37,19 +37,19 @@ import org.slf4j.LoggerFactory;
* operations that our application performs involving remote systems. The calling code should remain
* decoupled from implementations.
*
* <p>{@link FindCustomer} is a business operation that looks up a customer's record and returns
* its ID. Imagine its job is performed by looking up the customer in our local database and
* returning its ID. We can pass {@link CustomerNotFoundException} as one of its {@link
* <p>{@link FindCustomer} is a business operation that looks up a customer's record and returns its
* ID. Imagine its job is performed by looking up the customer in our local database and returning
* its ID. We can pass {@link CustomerNotFoundException} as one of its {@link
* FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) constructor
* parameters} in order to simulate not finding the customer.
*
* <p>Imagine that, lately, this operation has experienced intermittent failures due to some weird
* corruption and/or locking in the data. After retrying a few times the customer is found. The
* database is still, however, expected to always be available. While a definitive solution is
* found to the problem, our engineers advise us to retry the operation a set number of times with a
* set delay between retries, although not too many retries otherwise the end user will be left
* waiting for a long time, while delays that are too short will not allow the database to recover
* from the load.
* database is still, however, expected to always be available. While a definitive solution is found
* to the problem, our engineers advise us to retry the operation a set number of times with a set
* delay between retries, although not too many retries otherwise the end user will be left waiting
* for a long time, while delays that are too short will not allow the database to recover from the
* load.
*
* <p>To keep the calling code as decoupled as possible from this workaround, we have implemented
* the retry mechanism as a {@link BusinessOperation} named {@link Retry}.
@@ -91,32 +91,34 @@ public final class App {
}
private static void errorWithRetry() throws Exception {
final var retry = new Retry<>(
new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)),
3, //3 attempts
100, //100 ms delay between attempts
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
);
final var retry =
new Retry<>(
new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)),
3, // 3 attempts
100, // 100 ms delay between attempts
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()));
op = retry;
final var customerId = op.perform();
LOG.info(String.format(
"However, retrying the operation while ignoring a recoverable error will eventually yield "
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
));
LOG.info(
String.format(
"However, retrying the operation while ignoring a recoverable error will eventually yield "
+ "the result %s after a number of attempts %s",
customerId, retry.attempts()));
}
private static void errorWithRetryExponentialBackoff() throws Exception {
final var retry = new RetryExponentialBackoff<>(
new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)),
6, //6 attempts
30000, //30 s max delay between attempts
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
);
final var retry =
new RetryExponentialBackoff<>(
new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)),
6, // 6 attempts
30000, // 30 s max delay between attempts
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()));
op = retry;
final var customerId = op.perform();
LOG.info(String.format(
"However, retrying the operation while ignoring a recoverable error will eventually yield "
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
));
LOG.info(
String.format(
"However, retrying the operation while ignoring a recoverable error will eventually yield "
+ "the result %s after a number of attempts %s",
customerId, retry.attempts()));
}
}
@@ -31,11 +31,9 @@ import java.io.Serial;
* occurred. Its use is reserved as a "catch-all" for cases where no other subtype captures the
* specificity of the error condition in question. Calling code is not expected to be able to handle
* this error and should be reported to the maintainers immediately.
*
*/
public class BusinessException extends Exception {
@Serial
private static final long serialVersionUID = 6235833142062144336L;
@Serial private static final long serialVersionUID = 6235833142062144336L;
/**
* Ctor.
@@ -37,7 +37,7 @@ public interface BusinessOperation<T> {
*
* @return the return value
* @throws BusinessException if the operation fails. Implementations are allowed to throw more
* specific subtypes depending on the error conditions
* specific subtypes depending on the error conditions
*/
T perform() throws BusinessException;
}
@@ -32,12 +32,10 @@ import java.io.Serial;
* <p>The severity of this error is bounded by its context: was the search for the customer
* triggered by an input from some end user, or were the search parameters pulled from your
* database?
*
*/
public final class CustomerNotFoundException extends BusinessException {
@Serial
private static final long serialVersionUID = -6972888602621778664L;
@Serial private static final long serialVersionUID = -6972888602621778664L;
/**
* Ctor.
@@ -26,13 +26,9 @@ package com.iluwatar.retry;
import java.io.Serial;
/**
* Catastrophic error indicating that we have lost connection to our database.
*
*/
/** Catastrophic error indicating that we have lost connection to our database. */
public final class DatabaseNotAvailableException extends BusinessException {
@Serial
private static final long serialVersionUID = -3750769625095997799L;
@Serial private static final long serialVersionUID = -3750769625095997799L;
/**
* Ctor.
@@ -34,13 +34,13 @@ import java.util.List;
* <p>This is an imaginary operation that, for some imagined input, returns the ID for a customer.
* However, this is a "flaky" operation that is supposed to fail intermittently, but for the
* purposes of this example it fails in a programmed way depending on the constructor parameters.
*
*/
public record FindCustomer(String customerId, Deque<BusinessException> errors) implements BusinessOperation<String> {
public record FindCustomer(String customerId, Deque<BusinessException> errors)
implements BusinessOperation<String> {
public FindCustomer(String customerId, BusinessException... errors) {
this(customerId, new ArrayDeque<>(List.of(errors)));
}
@Override
public String perform() throws BusinessException {
if (!this.errors.isEmpty()) {
@@ -47,19 +47,15 @@ public final class Retry<T> implements BusinessOperation<T> {
/**
* Ctor.
*
* @param op the {@link BusinessOperation} to retry
* @param op the {@link BusinessOperation} to retry
* @param maxAttempts number of times to retry
* @param delay delay (in milliseconds) between attempts
* @param delay delay (in milliseconds) between attempts
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
* will be ignored if no tests are given
* will be ignored if no tests are given
*/
@SafeVarargs
public Retry(
BusinessOperation<T> op,
int maxAttempts,
long delay,
Predicate<Exception>... ignoreTests
) {
BusinessOperation<T> op, int maxAttempts, long delay, Predicate<Exception>... ignoreTests) {
this.op = op;
this.maxAttempts = maxAttempts;
this.delay = delay;
@@ -101,7 +97,7 @@ public final class Retry<T> implements BusinessOperation<T> {
try {
Thread.sleep(this.delay);
} catch (InterruptedException f) {
//ignore
// ignore
}
}
} while (true);
@@ -49,18 +49,17 @@ public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
/**
* Ctor.
*
* @param op the {@link BusinessOperation} to retry
* @param op the {@link BusinessOperation} to retry
* @param maxAttempts number of times to retry
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
* will be ignored if no tests are given
* will be ignored if no tests are given
*/
@SafeVarargs
public RetryExponentialBackoff(
BusinessOperation<T> op,
int maxAttempts,
long maxDelay,
Predicate<Exception>... ignoreTests
) {
Predicate<Exception>... ignoreTests) {
this.op = op;
this.maxAttempts = maxAttempts;
this.maxDelay = maxDelay;
@@ -104,7 +103,7 @@ public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
var delay = Math.min(testDelay, this.maxDelay);
Thread.sleep(delay);
} catch (InterruptedException f) {
//ignore
// ignore
}
}
} while (true);
@@ -30,22 +30,15 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link FindCustomer}.
*
*/
/** Unit tests for {@link FindCustomer}. */
class FindCustomerTest {
/**
* Returns the given result with no exceptions.
*/
/** Returns the given result with no exceptions. */
@Test
void noExceptions() throws Exception {
assertThat(new FindCustomer("123").perform(), is("123"));
}
/**
* Throws the given exception.
*/
/** Throws the given exception. */
@Test
void oneException() {
var findCustomer = new FindCustomer("123", new BusinessException("test"));
@@ -59,20 +52,20 @@ class FindCustomerTest {
*/
@Test
void resultAfterExceptions() throws Exception {
final var op = new FindCustomer(
"123",
new CustomerNotFoundException("not found"),
new DatabaseNotAvailableException("not available")
);
final var op =
new FindCustomer(
"123",
new CustomerNotFoundException("not found"),
new DatabaseNotAvailableException("not available"));
try {
op.perform();
} catch (CustomerNotFoundException e) {
//ignore
// ignore
}
try {
op.perform();
} catch (DatabaseNotAvailableException e) {
//ignore
// ignore
}
assertThat(op.perform(), is("123"));
@@ -30,28 +30,23 @@ import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link Retry}.
*
*/
/** Unit tests for {@link Retry}. */
class RetryExponentialBackoffTest {
/**
* Should contain all errors thrown.
*/
/** Should contain all errors thrown. */
@Test
void errors() {
final var e = new BusinessException("unhandled");
final var retry = new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0
);
final var retry =
new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.errors(), hasItem(e));
@@ -64,17 +59,17 @@ class RetryExponentialBackoffTest {
@Test
void attempts() {
final var e = new BusinessException("unhandled");
final var retry = new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0
);
final var retry =
new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.attempts(), is(1));
@@ -87,18 +82,18 @@ class RetryExponentialBackoffTest {
@Test
void ignore() {
final var e = new CustomerNotFoundException("customer not found");
final var retry = new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0,
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())
);
final var retry =
new RetryExponentialBackoff<String>(
() -> {
throw e;
},
2,
0,
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()));
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.attempts(), is(2));
@@ -30,29 +30,24 @@ import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link Retry}.
*
*/
/** Unit tests for {@link Retry}. */
class RetryTest {
/**
* Should contain all errors thrown.
*/
/** Should contain all errors thrown. */
@Test
void errors() {
final var e = new BusinessException("unhandled");
final var retry = new Retry<String>(
() -> {
throw e;
},
2,
0
);
final var retry =
new Retry<String>(
() -> {
throw e;
},
2,
0);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.errors(), hasItem(e));
@@ -65,17 +60,17 @@ class RetryTest {
@Test
void attempts() {
final var e = new BusinessException("unhandled");
final var retry = new Retry<String>(
() -> {
throw e;
},
2,
0
);
final var retry =
new Retry<String>(
() -> {
throw e;
},
2,
0);
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.attempts(), is(1));
@@ -88,21 +83,20 @@ class RetryTest {
@Test
void ignore() {
final var e = new CustomerNotFoundException("customer not found");
final var retry = new Retry<String>(
() -> {
throw e;
},
2,
0,
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())
);
final var retry =
new Retry<String>(
() -> {
throw e;
},
2,
0,
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()));
try {
retry.perform();
} catch (BusinessException ex) {
//ignore
// ignore
}
assertThat(retry.attempts(), is(2));
}
}
}