Reusable Fault Tolerance
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
The declarative, annotation-based API of MicroProfile Fault Tolerance doesn’t allow sharing configuration of fault tolerance strategies across multiple classes. In a single class, the configuration may be shared across all methods by putting the annotations on the class instead of individual methods, but even then, stateful fault tolerance strategies are not shared. Each method has its own bulkhead, circuit breaker and/or rate limit, which is often not what you want.
The programmatic API of SmallRye Fault Tolerance allows using a single Guard
or TypedGuard
object to guard multiple disparate actions, which allows reuse and state sharing.
It is possible to use a programmatically constructed Guard
or TypedGuard
object declaratively, using the @ApplyGuard
annotation.
To be able to do that, we need a bean of type Guard
(or TypedGuard
) with the @Identifier
qualifier:
@ApplicationScoped
public class PreconfiguredFaultTolerance {
@Produces
@Identifier("my-fault-tolerance")
public static final Guard GUARD = Guard.create()
.withRetry().maxRetries(2).done()
.withTimeout().done()
.build();
}
See the programmatic API documentation for more information about creating the Guard
or TypedGuard
instance.
It is customary to create the bean by declaring a static
producer field, just like in the previous example.
Once we have that, we can apply my-fault-tolerance
to any method:
@ApplicationScoped
public class MyService {
@ApplyGuard("my-fault-tolerance")
public String doSomething() {
...
}
@ApplyGuard("my-fault-tolerance")
public CompletionStage<Integer> doSomethingElse() {
...
}
}
Defining Fallback
Note that it is not possible to define a fallback on Guard
, because fallback is tied to the action type.
It is possible to define a fallback on TypedGuard
, because it can only be used to guard methods with a single return type, equal to the type the TypedGuard
was created with.
However, the @ApplyGuard
annotation pays attention to the @Fallback
annotation.
If @Fallback
is defined, it is used both by Guard
and TypedGuard
instances, and it overrides the possible fallback defined on the TypedGuard
.
Defining Thread Offload
Both Guard
and TypedGuard
allow enabling or disabling thread offload.
This is ignored by @ApplyGuard
if the annotated method is asynchronous as determined from the method signature and possible annotations (@Asynchronous
and @AsynchronousNonBlocking
).
See Asynchronous Execution for more information about how that determination works.
The thread offload configuration of Guard
or TypedGuard
is only honored when the method cannot be determined to be asynchronous, but it still declares an asynchronous return type.
Metrics
Methods annotated @ApplyGuard
gather metrics similarly to methods annotated with MicroProfile Fault Tolerance annotations.
That is, each method gets its own metrics, with the method
tag being <fully qualified class name>.<method name>
.
At the same time, state is still shared.
All methods annotated @ApplyGuard
share the same bulkhead, circuit breaker and/or rate limit.
If the Guard
or TypedGuard
object used for @ApplyGuard
is also used programmatically, that usage is coalesced in metrics under the description as the method
tag.
Differences to the Specification
@ApplyGuard
has the same differences to standard MicroProfile Fault Tolerance as Guard
/ TypedGuard
:
-
asynchronous actions of type
java.util.concurrent.Future
are not supported; -
the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
Kotlin suspend
Functions
Even though the programmatic API of Guard
and TypedGuard
does not support Kotlin suspend
functions, the declarative API of @ApplyGuard
does.
When the guard is a Guard
, no restrictions apply.
When the guard is a TypedGuard
, however, its type must be a synchronous return type of the suspend
function.
For example, when the suspend
function is declared to return a String
asynchronously:
@ApplyGuard("my-fault-tolerance")
@Fallback(fallbackMethod = "fallback")
suspend fun hello(): String {
delay(100)
throw IllegalArgumentException()
}
The TypedGuard
must be declared to guard actions of type String
:
@Produces
@Identifier("my-fault-tolerance")
val GUARD = TypedGuard.create(String::class.java)
.withRetry().maxRetries(2).done()
.withFallback().handler(Supplier { "fallback" }).done()
.build()
This means that a possible fallback declared on the TypedGuard
must be synchronous; it cannot be a suspend
lambda.
The @Fallback
method, if declared, must have a matching signature and so must be a suspend
function:
suspend fun fallback(): String {
delay(100)
return "fallback"
}
Migration from @ApplyFaultTolerance
The 1st version of the programmatic API had the @ApplyFaultTolerance
annotation.
That annotation is deprecated and scheduled for removal in SmallRye Fault Tolerance 7.0.
To migrate, replace @ApplyFaultTolerance
with @ApplyGuard
and change the FaultTolerance<>
producers to produce Guard
or TypedGuard<>
.
See the programmatic API migration guide for more details about that.
Note that it is not possible to define both Guard
and TypedGuard<>
with the same identifier; that leads to a deployment problem.
Therefore, for each producer of FaultTolerance<>
, you have to decide whether the replacement should be Guard
or TypedGuard<>
.