forked from ReactiveX/RxJava
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request ReactiveX#199 from pwlan/master
Initial suggestion for a feign <-> resilience4j module
- Loading branch information
Showing
18 changed files
with
1,357 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
= resilience4j-feign | ||
|
||
Resilience4j decorators for https://github.com/OpenFeign/feign[feign]. | ||
Similar to https://github.com/OpenFeign/feign/tree/master/hystrix[HystrixFeign], | ||
resilience4j-feign makes it easy to incorporate "fault tolerance" patterns into the feign framework, such as | ||
the CircuitBreaker and RateLimiter. | ||
|
||
|
||
== Current Features | ||
* CircuitBreaker | ||
* RateLimiter | ||
* Fallback | ||
|
||
|
||
== Decorating Feign Interfaces | ||
|
||
The `Resilience4jFeign.builder` is the main class for creating fault tolerance instances of feign. | ||
It extends the `Feign.builder` and can be configured in the same way with the exception of adding a custom | ||
`InvocationHandlerFactory`. Resilience4jFeign uses its own `InvocationHandlerFactory` to apply the decorators. | ||
Decorators can be built using the `FeignDecorators` class. Multiple decorators can be combined | ||
|
||
The following example shows how to decorate a feign interface with a RateLimiter and CircuitBreaker: | ||
``` java | ||
public interface MyService { | ||
@RequestLine("GET /greeting") | ||
String getGreeting(); | ||
|
||
@RequestLine("POST /greeting") | ||
String createGreeting(); | ||
} | ||
|
||
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName"); | ||
RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName"); | ||
FeignDecorators decorators = FeignDecorators.builder() | ||
.withRateLimiter(rateLimiter) | ||
.withCircuitBreaker(circuitBreaker) | ||
.build(); | ||
MyService myService = Resilience4jFeign.builder(decorators).target(MyService.class, "http://localhost:8080/"); | ||
``` | ||
|
||
Calling any method of the `MyService` instance will invoke a CircuitBreaker and then a RateLimiter. | ||
If one of these mechanisms take affect, then the corresponding RuntimeException will be thrown, for example, `CircuitBreakerOpenException` or `RequestNotPermitted` (Hint: These do not extend the `FeignException` class). | ||
|
||
The following diagram illustrates how the decorators are stacked: | ||
image::feign-decorators.png[] | ||
|
||
|
||
== Ordering of Decorators | ||
The order in which decorators are applied correspond to the order in which they are declared. | ||
When building `FeignDecorators`, it is important to be wary of this, since the order affects the resulting behavior. | ||
|
||
For example, | ||
``` java | ||
FeignDecorators decoratorsA = FeignDecorators.builder() | ||
.withCircuitBreaker(circuitBreaker) | ||
.withRateLimiter(rateLimiter) | ||
.build(); | ||
|
||
FeignDecorators decoratorsB = FeignDecorators.builder() | ||
.withRateLimiter(rateLimiter) | ||
.withCircuitBreaker(circuitBreaker) | ||
.build(); | ||
``` | ||
|
||
With `decoratorsA` the RateLimiter will be called before the CircuitBreaker. That means that even if the CircuitBreaker is open, the RateLimiter will still limit the rate of calls. | ||
`decoratorsB` applies the reserve order. Meaning that once the CircuitBreaker is open, the RateLimiter will no longer be in affect. | ||
|
||
|
||
== Fallback | ||
Fallbacks can be defined that are called when Exceptions are thrown. Exceptions can occur when the HTTP request fails, but also when one of the `FeignDecorators` activates, for example, the CircuitBreaker. | ||
|
||
``` java | ||
public interface MyService { | ||
@RequestLine("GET /greeting") | ||
String greeting(); | ||
} | ||
|
||
MyService requestFailedFallback = () -> "fallback greeting"; | ||
MyService circuitBreakerFallback = () -> "CircuitBreaker is open!"; | ||
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName"); | ||
FeignDecorators decorators = FeignDecorators.builder() | ||
.withFallback(requestFailedFallback, FeignException.class) | ||
.withFallback(circuitBreakerFallback, CircuitBreakerOpenException.class) | ||
.build(); | ||
MyService myService = Resilience4jFeign.builder(decorators).target(MyService.class, "http://localhost:8080/", fallback); | ||
``` | ||
In this example, the `requestFailedFallback` is called when a `FeignException` is thrown (usually when the HTTP request fails), whereas | ||
the `circuitBreakerFallback` is only called in the case of a `CircuitBreakerOpenException`. | ||
Check the `FeignDecorators` class for more ways to filter fallbacks. | ||
|
||
All fallbacks must implement the same interface that is declared in the "target" (Resilience4jFeign.Builder#target) method, otherwise an IllegalArgumentException will be thrown. | ||
Multiple fallbacks can be assigned to handle the same Exception with the next fallback being called when the previous one fails. | ||
|
||
|
||
|
||
== License | ||
|
||
Copyright 2018 | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
dependencies { | ||
compile ( libraries.feign ) | ||
compile project(':resilience4j-circuitbreaker') | ||
compile project(':resilience4j-ratelimiter') | ||
testCompile ( libraries.feign_wiremock ) | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions
117
...lience4j-feign/src/main/java/io/github/resilience4j/feign/DecoratorInvocationHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* | ||
* Copyright 2018 | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
* | ||
* | ||
*/ | ||
package io.github.resilience4j.feign; | ||
|
||
import static feign.Util.checkNotNull; | ||
|
||
import java.lang.reflect.InvocationHandler; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import feign.InvocationHandlerFactory.MethodHandler; | ||
import feign.Target; | ||
import io.vavr.CheckedFunction1; | ||
|
||
/** | ||
* An instance of {@link InvocationHandler} that uses {@link FeignDecorator}s to enhance the | ||
* invocations of methods. | ||
*/ | ||
class DecoratorInvocationHandler implements InvocationHandler { | ||
|
||
private final Target<?> target; | ||
private final Map<Method, CheckedFunction1<Object[], Object>> decoratedDispatch; | ||
|
||
public DecoratorInvocationHandler(Target<?> target, | ||
Map<Method, MethodHandler> dispatch, | ||
FeignDecorator invocationDecorator) { | ||
this.target = checkNotNull(target, "target"); | ||
checkNotNull(dispatch, "dispatch"); | ||
this.decoratedDispatch = decorateMethodHandlers(dispatch, invocationDecorator, target); | ||
} | ||
|
||
/** | ||
* Applies the specified {@link FeignDecorator} to all specified {@link MethodHandler}s and | ||
* returns the result as a map of {@link CheckedFunction1}s. Invoking a {@link CheckedFunction1} | ||
* will therefore invoke the decorator which, in turn, may invoke the corresponding | ||
* {@link MethodHandler}. | ||
* | ||
* @param dispatch a map of the methods from the feign interface to the {@link MethodHandler}s. | ||
* @param invocationDecorator the {@link FeignDecorator} with which to decorate the | ||
* {@link MethodHandler}s. | ||
* @param target the target feign interface. | ||
* @return a new map where the {@link MethodHandler}s are decorated with the | ||
* {@link FeignDecorator}. | ||
*/ | ||
private Map<Method, CheckedFunction1<Object[], Object>> decorateMethodHandlers(Map<Method, MethodHandler> dispatch, | ||
FeignDecorator invocationDecorator, Target<?> target) { | ||
final Map<Method, CheckedFunction1<Object[], Object>> map = new HashMap<>(); | ||
for (final Map.Entry<Method, MethodHandler> entry : dispatch.entrySet()) { | ||
final Method method = entry.getKey(); | ||
final MethodHandler methodHandler = entry.getValue(); | ||
map.put(method, invocationDecorator.decorate(methodHandler::invoke, method, methodHandler, target)); | ||
} | ||
return map; | ||
} | ||
|
||
@Override | ||
public Object invoke(final Object proxy, final Method method, final Object[] args) | ||
throws Throwable { | ||
switch (method.getName()) { | ||
case "equals": | ||
return equals(args.length > 0 ? args[0] : null); | ||
|
||
case "hashCode": | ||
return hashCode(); | ||
|
||
case "toString": | ||
return toString(); | ||
|
||
default: | ||
break; | ||
} | ||
|
||
return decoratedDispatch.get(method).apply(args); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
Object compareTo = obj; | ||
if (compareTo == null) { | ||
return false; | ||
} | ||
if (Proxy.isProxyClass(compareTo.getClass())) { | ||
compareTo = Proxy.getInvocationHandler(compareTo); | ||
} | ||
if (compareTo instanceof DecoratorInvocationHandler) { | ||
final DecoratorInvocationHandler other = (DecoratorInvocationHandler) compareTo; | ||
return target.equals(other.target); | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return target.hashCode(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return target.toString(); | ||
} | ||
} |
106 changes: 106 additions & 0 deletions
106
resilience4j-feign/src/main/java/io/github/resilience4j/feign/FallbackDecorator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* | ||
* Copyright 2018 | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
* | ||
* | ||
*/ | ||
package io.github.resilience4j.feign; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.function.Predicate; | ||
|
||
import feign.InvocationHandlerFactory.MethodHandler; | ||
import feign.Target; | ||
import io.vavr.CheckedFunction1; | ||
|
||
/** | ||
* Decorator that calls a fallback in the case that an exception is thrown. | ||
*/ | ||
class FallbackDecorator<T> implements FeignDecorator { | ||
|
||
private final T fallback; | ||
private Predicate<Exception> filter; | ||
|
||
/** | ||
* Creates a fallback that will be called for every {@link Exception}. | ||
*/ | ||
public FallbackDecorator(T fallback) { | ||
this(fallback, ex -> true); | ||
} | ||
|
||
/** | ||
* Creates a fallback that will only be called for the specified {@link Exception}. | ||
*/ | ||
public FallbackDecorator(T fallback, Class<? extends Exception> filter) { | ||
this(fallback, filter::isInstance); | ||
requireNonNull(filter, "Filter cannot be null!"); | ||
} | ||
|
||
/** | ||
* Creates a fallback that will only be called if the specified {@link Predicate} returns | ||
* <code>true</code>. | ||
*/ | ||
public FallbackDecorator(T fallback, Predicate<Exception> filter) { | ||
this.fallback = requireNonNull(fallback, "Fallback cannot be null!"); | ||
this.filter = requireNonNull(filter, "Filter cannot be null!"); | ||
} | ||
|
||
/** | ||
* Calls the fallback if the invocationCall throws an {@link Exception}. | ||
* | ||
* @throws IllegalArgumentException if the fallback object does not have a corresponding | ||
* fallback method. | ||
*/ | ||
@Override | ||
public CheckedFunction1<Object[], Object> decorate(CheckedFunction1<Object[], Object> invocationCall, | ||
Method method, | ||
MethodHandler methodHandler, | ||
Target<?> target) { | ||
final Method fallbackMethod; | ||
validateFallback(method); | ||
fallbackMethod = getFallbackMethod(method); | ||
return args -> { | ||
try { | ||
return invocationCall.apply(args); | ||
} catch (final Exception exception) { | ||
if (filter.test(exception)) { | ||
return fallbackMethod.invoke(fallback, args); | ||
} | ||
throw exception; | ||
} | ||
}; | ||
} | ||
|
||
private void validateFallback(Method method) { | ||
if (fallback.getClass().isAssignableFrom(method.getDeclaringClass())) { | ||
throw new IllegalArgumentException("Cannot use the fallback [" | ||
+ fallback.getClass() + "] for [" | ||
+ method.getDeclaringClass() + "]!"); | ||
} | ||
} | ||
|
||
private Method getFallbackMethod(Method method) { | ||
Method fallbackMethod; | ||
try { | ||
fallbackMethod = fallback.getClass().getMethod(method.getName(), method.getParameterTypes()); | ||
} catch (NoSuchMethodException | SecurityException e) { | ||
throw new IllegalArgumentException("Cannot use the fallback [" | ||
+ fallback.getClass() + "] for [" | ||
+ method.getDeclaringClass() + "]", e); | ||
} | ||
return fallbackMethod; | ||
} | ||
|
||
} |
Oops, something went wrong.