10 changed files with 327 additions and 6 deletions
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
Hystrix |
||||
=================== |
||||
|
||||
This module wraps Feign's http requests in [Hystrix](https://github.com/Netflix/Hystrix/), which enables the [Circuit Breaker Pattern](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern). |
||||
|
||||
To use Hystrix with Feign, add the Hystrix module to your classpath. Then, configure Feign to use the `HystrixInvocationHandler`: |
||||
|
||||
```java |
||||
GitHub github = HystrixFeign.builder() |
||||
.target(GitHub.class, "https://api.github.com"); |
||||
``` |
||||
|
||||
Methods that do *not* return [`HystrixCommand`](https://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/HystrixCommand.html) are still wrapped in a `HystrixCommand`, but `execute()` is automatically called for you. |
||||
|
||||
For asynchronous or reactive use, return `HystrixCommand<YourType>` rather than just `YourType`. |
||||
|
||||
```java |
||||
interface YourApi { |
||||
@RequestLine("GET /yourtype/{id}") |
||||
HystrixCommand<YourType> getYourType(@Param("id") String id); |
||||
|
||||
@RequestLine("GET /yourtype/{id}") |
||||
YourType getYourTypeSynchronous(@Param("id") String id); |
||||
} |
||||
|
||||
YourApi api = HystrixFeign.builder() |
||||
.target(YourApi.class, "https://example.com"); |
||||
|
||||
// for reactive |
||||
api.getYourType("a").toObservable(); |
||||
|
||||
// for asynchronous |
||||
api.getYourType("a").queue(); |
||||
|
||||
// for synchronous |
||||
api.getYourType("a").execute(); |
||||
|
||||
// or to apply hystrix to existing feign methods. |
||||
api.getYourTypeSynchronous("a"); |
||||
``` |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
apply plugin: 'java' |
||||
|
||||
sourceCompatibility = 1.6 |
||||
|
||||
dependencies { |
||||
compile project(':feign-core') |
||||
compile 'com.netflix.hystrix:hystrix-core:1.4.18' |
||||
testCompile 'junit:junit:4.12' |
||||
testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 |
||||
testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' |
||||
testCompile project(':feign-gson') |
||||
testCompile project(':feign-core').sourceSets.test.output // for assertions |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
package feign.hystrix; |
||||
|
||||
import static feign.Util.resolveLastTypeParameter; |
||||
|
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.util.List; |
||||
|
||||
import com.netflix.hystrix.HystrixCommand; |
||||
|
||||
import feign.Contract; |
||||
import feign.MethodMetadata; |
||||
|
||||
final class HystrixDelegatingContract implements Contract { |
||||
|
||||
private final Contract delegate; |
||||
|
||||
public HystrixDelegatingContract(Contract delegate) { |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
@Override |
||||
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) { |
||||
List<MethodMetadata> metadatas = this.delegate.parseAndValidatateMetadata(targetType); |
||||
|
||||
for (MethodMetadata metadata : metadatas) { |
||||
Type type = metadata.returnType(); |
||||
|
||||
if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType().equals(HystrixCommand.class)) { |
||||
Type actualType = resolveLastTypeParameter(type, HystrixCommand.class); |
||||
metadata.returnType(actualType); |
||||
} |
||||
} |
||||
|
||||
return metadatas; |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
package feign.hystrix; |
||||
|
||||
import com.netflix.hystrix.HystrixCommand; |
||||
|
||||
import feign.Contract; |
||||
import feign.Feign; |
||||
|
||||
/** |
||||
* Allows Feign interfaces to return HystrixCommand objects. |
||||
* Also decorates normal Feign methods with circuit breakers, but calls {@link HystrixCommand#execute()} directly. |
||||
*/ |
||||
public final class HystrixFeign { |
||||
|
||||
public static Builder builder() { |
||||
return new Builder(); |
||||
} |
||||
|
||||
public static final class Builder extends Feign.Builder { |
||||
|
||||
public Builder() { |
||||
invocationHandlerFactory(new HystrixInvocationHandler.Factory()); |
||||
contract(new HystrixDelegatingContract(new Contract.Default())); |
||||
} |
||||
|
||||
@Override |
||||
public Feign.Builder contract(Contract contract) { |
||||
return super.contract(new HystrixDelegatingContract(contract)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
/* |
||||
* Copyright 2015 Netflix, Inc. |
||||
* |
||||
* 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 feign.hystrix; |
||||
|
||||
import static feign.Util.checkNotNull; |
||||
|
||||
import java.lang.reflect.InvocationHandler; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Map; |
||||
|
||||
import com.netflix.hystrix.HystrixCommand; |
||||
import com.netflix.hystrix.HystrixCommandGroupKey; |
||||
import com.netflix.hystrix.HystrixCommandKey; |
||||
|
||||
import feign.InvocationHandlerFactory; |
||||
import feign.InvocationHandlerFactory.MethodHandler; |
||||
import feign.Target; |
||||
|
||||
final class HystrixInvocationHandler implements InvocationHandler { |
||||
|
||||
private final Target target; |
||||
private final Map<Method, MethodHandler> dispatch; |
||||
|
||||
HystrixInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) { |
||||
this.target = checkNotNull(target, "target"); |
||||
this.dispatch = checkNotNull(dispatch, "dispatch"); |
||||
} |
||||
|
||||
@Override |
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { |
||||
String groupKey = this.target.name(); |
||||
String commandKey = method.getName(); |
||||
HystrixCommand.Setter setter = HystrixCommand.Setter |
||||
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) |
||||
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); |
||||
|
||||
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setter) { |
||||
@Override |
||||
protected Object run() throws Exception { |
||||
try { |
||||
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args); |
||||
} catch (Exception e) { |
||||
throw e; |
||||
} catch (Throwable t) { |
||||
throw (Error)t; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
if (HystrixCommand.class.isAssignableFrom(method.getReturnType())) { |
||||
return hystrixCommand; |
||||
} |
||||
return hystrixCommand.execute(); |
||||
} |
||||
|
||||
static final class Factory implements InvocationHandlerFactory { |
||||
|
||||
@Override |
||||
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { |
||||
return new HystrixInvocationHandler(target, dispatch); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
package feign.hystrix; |
||||
|
||||
import static feign.assertj.MockWebServerAssertions.assertThat; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
|
||||
import com.netflix.hystrix.HystrixCommand; |
||||
import com.squareup.okhttp.mockwebserver.MockResponse; |
||||
import com.squareup.okhttp.mockwebserver.MockWebServer; |
||||
|
||||
import feign.Headers; |
||||
import feign.RequestLine; |
||||
import feign.gson.GsonDecoder; |
||||
|
||||
public class HystrixBuilderTest { |
||||
|
||||
@Rule |
||||
public final ExpectedException thrown = ExpectedException.none(); |
||||
@Rule |
||||
public final MockWebServer server = new MockWebServer(); |
||||
|
||||
@Test |
||||
public void hystrixCommand() { |
||||
server.enqueue(new MockResponse().setBody("\"foo\"")); |
||||
|
||||
TestInterface api = target(); |
||||
|
||||
HystrixCommand<String> command = api.command(); |
||||
|
||||
assertThat(command).isNotNull(); |
||||
assertThat(command.execute()).isEqualTo("foo"); |
||||
} |
||||
|
||||
@Test |
||||
public void hystrixCommandInt() { |
||||
server.enqueue(new MockResponse().setBody("1")); |
||||
|
||||
TestInterface api = target(); |
||||
|
||||
HystrixCommand<Integer> command = api.intCommand(); |
||||
|
||||
assertThat(command).isNotNull(); |
||||
assertThat(command.execute()).isEqualTo(new Integer(1)); |
||||
} |
||||
|
||||
@Test |
||||
public void hystrixCommandList() { |
||||
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]")); |
||||
|
||||
TestInterface api = target(); |
||||
|
||||
HystrixCommand<List<String>> command = api.listCommand(); |
||||
|
||||
assertThat(command).isNotNull(); |
||||
assertThat(command.execute()).hasSize(2).contains("foo", "bar"); |
||||
} |
||||
|
||||
@Test |
||||
public void plainString() { |
||||
server.enqueue(new MockResponse().setBody("\"foo\"")); |
||||
|
||||
TestInterface api = target(); |
||||
|
||||
String string = api.get(); |
||||
|
||||
assertThat(string).isEqualTo("foo"); |
||||
} |
||||
|
||||
@Test |
||||
public void plainList() { |
||||
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]")); |
||||
|
||||
TestInterface api = target(); |
||||
|
||||
List<String> list = api.getList(); |
||||
|
||||
assertThat(list).isNotNull().hasSize(2).contains("foo", "bar"); |
||||
} |
||||
|
||||
private TestInterface target() { |
||||
return HystrixFeign.builder() |
||||
.decoder(new GsonDecoder()) |
||||
.target(TestInterface.class, "http://localhost:" + server.getPort()); |
||||
} |
||||
|
||||
interface TestInterface { |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers("Accept: application/json") |
||||
HystrixCommand<List<String>> listCommand(); |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers("Accept: application/json") |
||||
HystrixCommand<String> command(); |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers("Accept: application/json") |
||||
HystrixCommand<Integer> intCommand(); |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers("Accept: application/json") |
||||
String get(); |
||||
|
||||
@RequestLine("GET /") |
||||
@Headers("Accept: application/json") |
||||
List<String> getList(); |
||||
} |
||||
} |
Loading…
Reference in new issue