(NEW) Module "retry"
(NEW) Illustrative classes: - App: simulates a production application - BusinessOperation<T>: abstraction over any operation that can potentially fail - FindCustomer <: BusinessOperation<String>: illustrative operation that can throw an error - Retry <: BusinessOperation<T>: transparently implements the retry mechanism - Several "business" exceptions: - BusinessException: top-level - CustomerNotFoundException: can be ignored - DatabaseNotAvailableException: fatal error (NEW) .puml and .png for UML
This commit is contained in:
parent
4e290416df
commit
f7c396b0fd
153
retry/README.md
Normal file
153
retry/README.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
layout: pattern
|
||||||
|
title: Retry
|
||||||
|
folder: retry
|
||||||
|
permalink: /patterns/retry/
|
||||||
|
categories: other
|
||||||
|
tags:
|
||||||
|
- java
|
||||||
|
- difficulty-expert
|
||||||
|
- performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Retry / resiliency
|
||||||
|
Enables an application to handle transient failures from external resources.
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
Transparently retry certain operations that involve communication with external
|
||||||
|
resources, particularly over the network, isolating calling code from the
|
||||||
|
retry implementation details.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
The `Retry` pattern consists retrying operations on remote resources over the
|
||||||
|
network a set number of times. It closely depends on both business and technical
|
||||||
|
requirements: how much time will the business allow the end user to wait while
|
||||||
|
the operation finishes? What are the performance characteristics of the
|
||||||
|
remote resource during peak loads as well as our application as more threads
|
||||||
|
are waiting for the remote resource's availability? Among the errors returned
|
||||||
|
by the remote service, which can be safely ignored in order to retry? Is the
|
||||||
|
operation [idempotent](https://en.wikipedia.org/wiki/Idempotence)?
|
||||||
|
|
||||||
|
Another concern is the impact on the calling code by implementing the retry
|
||||||
|
mechanism. The retry mechanics should ideally be completely transparent to the
|
||||||
|
calling code (service interface remains unaltered). There are two general
|
||||||
|
approaches to this problem: from an enterprise architecture standpoint
|
||||||
|
(**strategic**), and a shared library standpoint (**tactical**).
|
||||||
|
|
||||||
|
*(As an aside, one interesting property is that, since implementations tend to
|
||||||
|
be configurable at runtime, daily monitoring and operation of this capability
|
||||||
|
is shifted over to operations support instead of the developers themselves.)*
|
||||||
|
|
||||||
|
From a strategic point of view, this would be solved by having requests
|
||||||
|
be redirected to a separate intermediary system, traditionally an
|
||||||
|
[ESB](https://en.wikipedia.org/wiki/Enterprise_service_bus), but more recently
|
||||||
|
a [Service Mesh](https://medium.com/microservices-in-practice/service-mesh-for-microservices-2953109a3c9a).
|
||||||
|
|
||||||
|
From a tactical point of view, this would be solved by reusing shared libraries
|
||||||
|
like [Hystrix](https://github.com/Netflix/Hystrix)[1]. This is the type of
|
||||||
|
solution showcased in the simple example that accompanies this *README*.
|
||||||
|
|
||||||
|
In our hypothetical application, we have a generic interface for all
|
||||||
|
operations on remote interfaces:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface BusinessOperation<T> {
|
||||||
|
T perform() throws BusinessException;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And we have an implementation of this interface that finds our customers
|
||||||
|
by looking up a database:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public final class FindCustomer implements BusinessOperation<String> {
|
||||||
|
@Override
|
||||||
|
public String perform() throws BusinessException {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our `FindCustomer` implementation can be configured to throw
|
||||||
|
`BusinessException`s before returning the customer's ID, thereby simulating a
|
||||||
|
'flaky' service that intermittently fails. Some exceptions, like the
|
||||||
|
`CustomerNotFoundException`, are deemed to be recoverable after some
|
||||||
|
hypothetical analysis because the root cause of the error stems from "some
|
||||||
|
database locking issue". However, the `DatabaseNotAvailableException` is
|
||||||
|
considered to be a definite showstopper - the application should not attempt
|
||||||
|
to recover from this error.
|
||||||
|
|
||||||
|
We can model a 'recoverable' scenario by instantiating `FindCustomer` like this:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final BusinessOperation<String> op = new FindCustomer(
|
||||||
|
"12345",
|
||||||
|
new CustomerNotFoundException("not found"),
|
||||||
|
new CustomerNotFoundException("still not found"),
|
||||||
|
new CustomerNotFoundException("don't give up yet!")
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
In this configuration, `FindCustomer` will throw `CustomerNotFoundException`
|
||||||
|
three times, after which it will consistently return the customer's ID
|
||||||
|
(`12345`).
|
||||||
|
|
||||||
|
In our hypothetical scenario, our analysts indicate that this operation
|
||||||
|
typically fails 2-4 times for a given input during peak hours, and that each
|
||||||
|
worker thread in the database subsystem typically needs 50ms to
|
||||||
|
"recover from an error". Applying these policies would yield something like
|
||||||
|
this:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final BusinessOperation<String> op = new Retry<>(
|
||||||
|
new FindCustomer(
|
||||||
|
"1235",
|
||||||
|
new CustomerNotFoundException("not found"),
|
||||||
|
new CustomerNotFoundException("still not found"),
|
||||||
|
new CustomerNotFoundException("don't give up yet!")
|
||||||
|
),
|
||||||
|
5,
|
||||||
|
100,
|
||||||
|
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Executing `op` *once* would automatically trigger at most 5 retry attempts,
|
||||||
|
with a 100 millisecond delay between attempts, ignoring any
|
||||||
|
`CustomerNotFoundException` thrown while trying. In this particular scenario,
|
||||||
|
due to the configuration for `FindCustomer`, there will be 1 initial attempt
|
||||||
|
and 3 additional retries before finally returning the desired result `12345`.
|
||||||
|
|
||||||
|
If our `FindCustomer` operation were instead to throw a fatal
|
||||||
|
`DatabaseNotFoundException`, which we were instructed not to ignore, but
|
||||||
|
more importantly we did *not* instruct our `Retry` to ignore, then the operation
|
||||||
|
would have failed immediately upon receiving the error, not matter how many
|
||||||
|
attempts were left.
|
||||||
|
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
[1] Please note that *Hystrix* is a complete implementation of the *Circuit
|
||||||
|
Breaker* pattern, of which the *Retry* pattern can be considered a subset of.
|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
Whenever an application needs to communicate with an external resource,
|
||||||
|
particularly in a cloud environment, and if the business requirements allow it.
|
||||||
|
|
||||||
|
## Presentations
|
||||||
|
You can view Microsoft's article [here](https://docs.microsoft.com/en-us/azure/architecture/patterns/retry).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
**Pros:**
|
||||||
|
|
||||||
|
* Resiliency
|
||||||
|
* Provides hard data on external failures
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
|
||||||
|
* Complexity
|
||||||
|
* Operations maintenance
|
||||||
|
|
||||||
|
## Related Patterns
|
||||||
|
* [Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html)
|
BIN
retry/etc/retry.png
Normal file
BIN
retry/etc/retry.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
38
retry/etc/retry.urm.puml
Normal file
38
retry/etc/retry.urm.puml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.retry {
|
||||||
|
class App {
|
||||||
|
- LOG : Logger {static}
|
||||||
|
- op : BusinessOperation<String> {static}
|
||||||
|
+ App()
|
||||||
|
- errorNoRetry() {static}
|
||||||
|
- errorWithRetry() {static}
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
- noErrors() {static}
|
||||||
|
}
|
||||||
|
interface BusinessOperation<T> {
|
||||||
|
+ perform() : T {abstract}
|
||||||
|
}
|
||||||
|
class FindCustomer {
|
||||||
|
- customerId : String
|
||||||
|
- errors : Deque<BusinessException>
|
||||||
|
+ FindCustomer(customerId : String, errors : BusinessException[])
|
||||||
|
+ perform() : String
|
||||||
|
}
|
||||||
|
class Retry<T> {
|
||||||
|
- attempts : AtomicInteger
|
||||||
|
- delay : long
|
||||||
|
- errors : List<Exception>
|
||||||
|
- maxAttempts : int
|
||||||
|
- op : BusinessOperation<T>
|
||||||
|
- test : Predicate<Exception>
|
||||||
|
+ Retry<T>(op : BusinessOperation<T>, maxAttempts : int, delay : long, ignoreTests : Predicate<Exception>[])
|
||||||
|
+ attempts() : int
|
||||||
|
+ errors() : List<Exception>
|
||||||
|
+ perform() : T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Retry --> "-op" BusinessOperation
|
||||||
|
App --> "-op" BusinessOperation
|
||||||
|
FindCustomer ..|> BusinessOperation
|
||||||
|
Retry ..|> BusinessOperation
|
||||||
|
@enduml
|
45
retry/pom.xml
Normal file
45
retry/pom.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
The MIT License
|
||||||
|
Copyright (c) 2014 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.17.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>retry</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
107
retry/src/main/java/com/iluwatar/retry/App.java
Normal file
107
retry/src/main/java/com/iluwatar/retry/App.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The <em>Retry</em> pattern enables applications to handle potentially recoverable failures from
|
||||||
|
* the environment if the business requirements and nature of the failures allow it. By retrying
|
||||||
|
* failed operations on external dependencies, the application may maintain stability and minimize
|
||||||
|
* negative impact on the user experience.
|
||||||
|
* <p>
|
||||||
|
* In our example, we have the {@link BusinessOperation} interface as an abstraction over
|
||||||
|
* all 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 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.
|
||||||
|
* <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}.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @see <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry">Retry pattern (Microsoft Azure Docs)</a>
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public final class App {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(App.class);
|
||||||
|
private static BusinessOperation<String> op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point.
|
||||||
|
*
|
||||||
|
* @param args not used
|
||||||
|
* @throws Exception not expected
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
noErrors();
|
||||||
|
errorNoRetry();
|
||||||
|
errorWithRetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void noErrors() throws Exception {
|
||||||
|
op = new FindCustomer("123");
|
||||||
|
op.perform();
|
||||||
|
LOG.info("Sometimes the operation executes with no errors.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void errorNoRetry() throws Exception {
|
||||||
|
op = new FindCustomer("123", new CustomerNotFoundException("not found"));
|
||||||
|
try {
|
||||||
|
op.perform();
|
||||||
|
} catch (CustomerNotFoundException e) {
|
||||||
|
LOG.info("Yet the operation will throw an error every once in a while.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void errorWithRetry() throws Exception {
|
||||||
|
final Retry<String> 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 String 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()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The top-most type in our exception hierarchy that signifies that an unexpected error
|
||||||
|
* condition 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.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public class BusinessException extends Exception {
|
||||||
|
private static final long serialVersionUID = 6235833142062144336L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor
|
||||||
|
*
|
||||||
|
* @param message the error message
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public BusinessException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs some business operation.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @param <T> the return type
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface BusinessOperation<T> {
|
||||||
|
/**
|
||||||
|
* Performs some business operation, returning a value {@code T} if successful, otherwise throwing
|
||||||
|
* an exception if an error occurs.
|
||||||
|
*
|
||||||
|
* @return the return value
|
||||||
|
* @throws BusinessException if the operation fails. Implementations are allowed to throw more
|
||||||
|
* specific subtypes depending on the error conditions
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
T perform() throws BusinessException;
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the customer was not found.
|
||||||
|
* <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?
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public final class CustomerNotFoundException extends BusinessException {
|
||||||
|
private static final long serialVersionUID = -6972888602621778664L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*
|
||||||
|
* @param message the error message
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public CustomerNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catastrophic error indicating that we have lost connection to our database.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public final class DatabaseNotAvailableException extends BusinessException {
|
||||||
|
private static final long serialVersionUID = -3750769625095997799L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*
|
||||||
|
* @param message the error message
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public DatabaseNotAvailableException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
65
retry/src/main/java/com/iluwatar/retry/FindCustomer.java
Normal file
65
retry/src/main/java/com/iluwatar/retry/FindCustomer.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a customer, returning its ID from our records.
|
||||||
|
* <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.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public final class FindCustomer implements BusinessOperation<String> {
|
||||||
|
private final String customerId;
|
||||||
|
private final Deque<BusinessException> errors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*
|
||||||
|
* @param customerId the final result of the remote operation
|
||||||
|
* @param errors the errors to throw before returning {@code customerId}
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public FindCustomer(String customerId, BusinessException... errors) {
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.errors = new ArrayDeque<>(Arrays.asList(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String perform() throws BusinessException {
|
||||||
|
if (!this.errors.isEmpty()) {
|
||||||
|
throw this.errors.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.customerId;
|
||||||
|
}
|
||||||
|
}
|
114
retry/src/main/java/com/iluwatar/retry/Retry.java
Normal file
114
retry/src/main/java/com/iluwatar/retry/Retry.java
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates {@link BusinessOperation business operation} with "retry" capabilities.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @param <T> the remote op's return type
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public final class Retry<T> implements BusinessOperation<T> {
|
||||||
|
private final BusinessOperation<T> op;
|
||||||
|
private final int maxAttempts;
|
||||||
|
private final long delay;
|
||||||
|
private final AtomicInteger attempts;
|
||||||
|
private final Predicate<Exception> test;
|
||||||
|
private final List<Exception> errors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctor.
|
||||||
|
*
|
||||||
|
* @param op the {@link BusinessOperation} to retry
|
||||||
|
* @param maxAttempts number of times to retry
|
||||||
|
* @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
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public Retry(
|
||||||
|
BusinessOperation<T> op,
|
||||||
|
int maxAttempts,
|
||||||
|
long delay,
|
||||||
|
Predicate<Exception>... ignoreTests
|
||||||
|
) {
|
||||||
|
this.op = op;
|
||||||
|
this.maxAttempts = maxAttempts;
|
||||||
|
this.delay = delay;
|
||||||
|
this.attempts = new AtomicInteger();
|
||||||
|
this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false);
|
||||||
|
this.errors = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The errors encountered while retrying, in the encounter order.
|
||||||
|
*
|
||||||
|
* @return the errors encountered while retrying
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public List<Exception> errors() {
|
||||||
|
return Collections.unmodifiableList(this.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of retries performed.
|
||||||
|
*
|
||||||
|
* @return the number of retries performed
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public int attempts() {
|
||||||
|
return this.attempts.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T perform() throws BusinessException {
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return this.op.perform();
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
this.errors.add(e);
|
||||||
|
|
||||||
|
if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(this.delay);
|
||||||
|
} catch (InterruptedException f) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
}
|
91
retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java
Normal file
91
retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link FindCustomer}.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public class FindCustomerTest {
|
||||||
|
/**
|
||||||
|
* Returns the given result with no exceptions.
|
||||||
|
*
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void noExceptions() throws Exception {
|
||||||
|
assertThat(
|
||||||
|
new FindCustomer("123").perform(),
|
||||||
|
is("123")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws the given exception.
|
||||||
|
*
|
||||||
|
* @throws Exception the expected exception
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test(expected = BusinessException.class)
|
||||||
|
public void oneException() throws Exception {
|
||||||
|
new FindCustomer("123", new BusinessException("test")).perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should first throw the given exceptions, then return the given result.
|
||||||
|
*
|
||||||
|
* @throws Exception not an expected exception
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void resultAfterExceptions() throws Exception {
|
||||||
|
final BusinessOperation<String> op = new FindCustomer(
|
||||||
|
"123",
|
||||||
|
new CustomerNotFoundException("not found"),
|
||||||
|
new DatabaseNotAvailableException("not available")
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
op.perform();
|
||||||
|
} catch (CustomerNotFoundException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
op.perform();
|
||||||
|
} catch (DatabaseNotAvailableException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
op.perform(),
|
||||||
|
is("123")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
117
retry/src/test/java/com/iluwatar/retry/RetryTest.java
Normal file
117
retry/src/test/java/com/iluwatar/retry/RetryTest.java
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2016 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.retry;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.hasItem;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link Retry}.
|
||||||
|
*
|
||||||
|
* @author George Aristy (george.aristy@gmail.com)
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
public class RetryTest {
|
||||||
|
/**
|
||||||
|
* Should contain all errors thrown.
|
||||||
|
*
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void errors() throws Exception {
|
||||||
|
final BusinessException e = new BusinessException("unhandled");
|
||||||
|
final Retry<String> retry = new Retry<>(
|
||||||
|
() -> { throw e; },
|
||||||
|
2,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
retry.perform();
|
||||||
|
} catch (BusinessException ex) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
retry.errors(),
|
||||||
|
hasItem(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No exceptions will be ignored, hence final number of attempts should be 1 even if we're asking
|
||||||
|
* it to attempt twice.
|
||||||
|
*
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void attempts() {
|
||||||
|
final BusinessException e = new BusinessException("unhandled");
|
||||||
|
final Retry<String> retry = new Retry<>(
|
||||||
|
() -> { throw e; },
|
||||||
|
2,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
retry.perform();
|
||||||
|
} catch (BusinessException ex) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
retry.attempts(),
|
||||||
|
is(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final number of attempts should be equal to the number of attempts asked because we are
|
||||||
|
* asking it to ignore the exception that will be thrown.
|
||||||
|
*
|
||||||
|
* @since 1.17.0
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void ignore() throws Exception {
|
||||||
|
final BusinessException e = new CustomerNotFoundException("customer not found");
|
||||||
|
final Retry<String> retry = new Retry<>(
|
||||||
|
() -> { throw e; },
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
retry.perform();
|
||||||
|
} catch (BusinessException ex) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
retry.attempts(),
|
||||||
|
is(2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user