Browse Source

Adds fallback support for HystrixCommand, Observable, and Single results

pull/341/merge
Jimmy Lu 9 years ago committed by Adrian Cole
parent
commit
07dcd87fc0
  1. 1
      CHANGELOG.md
  2. 32
      hystrix/src/main/java/feign/hystrix/HystrixInvocationHandler.java
  3. 237
      hystrix/src/test/java/feign/hystrix/HystrixBuilderTest.java

1
CHANGELOG.md

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
### Version 8.16
* Adds `@QueryMap` annotation to support dynamic query parameters
* Adds fallback support for HystrixCommand, Observable, and Single results
### Version 8.15
* Supports PUT without a body parameter

32
hystrix/src/main/java/feign/hystrix/HystrixInvocationHandler.java

@ -29,6 +29,7 @@ import feign.InvocationHandlerFactory.MethodHandler; @@ -29,6 +29,7 @@ import feign.InvocationHandlerFactory.MethodHandler;
import feign.Target;
import rx.Observable;
import rx.Single;
import rx.functions.Action1;
import static feign.Util.checkNotNull;
@ -68,7 +69,18 @@ final class HystrixInvocationHandler implements InvocationHandler { @@ -68,7 +69,18 @@ final class HystrixInvocationHandler implements InvocationHandler {
protected Object getFallback() {
if (fallback == null) return super.getFallback();
try {
return method.invoke(fallback, args);
Object result = method.invoke(fallback, args);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
@ -79,18 +91,30 @@ final class HystrixInvocationHandler implements InvocationHandler { @@ -79,18 +91,30 @@ final class HystrixInvocationHandler implements InvocationHandler {
}
};
if (HystrixCommand.class.isAssignableFrom(method.getReturnType())) {
if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (Observable.class.isAssignableFrom(method.getReturnType())) {
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (Single.class.isAssignableFrom(method.getReturnType())) {
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
}
return hystrixCommand.execute();
}
private boolean isReturnsHystrixCommand(Method method) {
return HystrixCommand.class.isAssignableFrom(method.getReturnType());
}
private boolean isReturnsObservable(Method method) {
return Observable.class.isAssignableFrom(method.getReturnType());
}
private boolean isReturnsSingle(Method method) {
return Single.class.isAssignableFrom(method.getReturnType());
}
static final class Factory implements InvocationHandlerFactory {
@Override

237
hystrix/src/test/java/feign/hystrix/HystrixBuilderTest.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package feign.hystrix;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
@ -10,6 +11,7 @@ import org.junit.Rule; @@ -10,6 +11,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -45,6 +47,18 @@ public class HystrixBuilderTest { @@ -45,6 +47,18 @@ public class HystrixBuilderTest {
assertThat(command.execute()).isEqualTo("foo");
}
@Test
public void hystrixCommandFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
HystrixCommand<String> command = api.command();
assertThat(command).isNotNull();
assertThat(command.execute()).isEqualTo("fallback");
}
@Test
public void hystrixCommandInt() {
server.enqueue(new MockResponse().setBody("1"));
@ -57,6 +71,18 @@ public class HystrixBuilderTest { @@ -57,6 +71,18 @@ public class HystrixBuilderTest {
assertThat(command.execute()).isEqualTo(new Integer(1));
}
@Test
public void hystrixCommandIntFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
HystrixCommand<Integer> command = api.intCommand();
assertThat(command).isNotNull();
assertThat(command.execute()).isEqualTo(new Integer(0));
}
@Test
public void hystrixCommandList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
@ -69,12 +95,29 @@ public class HystrixBuilderTest { @@ -69,12 +95,29 @@ public class HystrixBuilderTest {
assertThat(command.execute()).containsExactly("foo", "bar");
}
@Test
public void hystrixCommandListFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
HystrixCommand<List<String>> command = api.listCommand();
assertThat(command).isNotNull();
assertThat(command.execute()).containsExactly("fallback");
}
// When dealing with fallbacks, it is less tedious to keep interfaces small.
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
interface GitHubHystrix {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
HystrixCommand<List<String>> contributorsHystrixCommand(@Param("owner") String owner, @Param("repo") String repo);
}
@Test
public void fallbacksApplyOnError() {
server.enqueue(new MockResponse().setResponseCode(500));
@ -150,6 +193,23 @@ public class HystrixBuilderTest { @@ -150,6 +193,23 @@ public class HystrixBuilderTest {
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo("foo");
}
@Test
public void rxObservableFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Observable<String> observable = api.observable();
assertThat(observable).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<String> testSubscriber = new TestSubscriber<String>();
observable.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo("fallback");
}
@Test
public void rxObservableInt() {
server.enqueue(new MockResponse().setBody("1"));
@ -167,6 +227,23 @@ public class HystrixBuilderTest { @@ -167,6 +227,23 @@ public class HystrixBuilderTest {
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(new Integer(1));
}
@Test
public void rxObservableIntFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Observable<Integer> observable = api.intObservable();
assertThat(observable).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>();
observable.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(new Integer(0));
}
@Test
public void rxObservableList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
@ -185,6 +262,24 @@ public class HystrixBuilderTest { @@ -185,6 +262,24 @@ public class HystrixBuilderTest {
assertThat(testSubscriber.getOnNextEvents().get(0)).containsExactly("foo", "bar");
}
@Test
public void rxObservableListFall() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Observable<List<String>> observable = api.listObservable();
assertThat(observable).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<List<String>> testSubscriber = new TestSubscriber<List<String>>();
observable.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
assertThat(testSubscriber.getOnNextEvents().get(0)).containsExactly("fallback");
}
@Test
public void rxSingle() {
server.enqueue(new MockResponse().setBody("\"foo\""));
@ -202,6 +297,23 @@ public class HystrixBuilderTest { @@ -202,6 +297,23 @@ public class HystrixBuilderTest {
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo("foo");
}
@Test
public void rxSingleFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Single<String> single = api.single();
assertThat(single).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<String> testSubscriber = new TestSubscriber<String>();
single.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo("fallback");
}
@Test
public void rxSingleInt() {
server.enqueue(new MockResponse().setBody("1"));
@ -219,6 +331,23 @@ public class HystrixBuilderTest { @@ -219,6 +331,23 @@ public class HystrixBuilderTest {
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(new Integer(1));
}
@Test
public void rxSingleIntFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Single<Integer> single = api.intSingle();
assertThat(single).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>();
single.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
Assertions.assertThat(testSubscriber.getOnNextEvents().get(0)).isEqualTo(new Integer(0));
}
@Test
public void rxSingleList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
@ -236,6 +365,23 @@ public class HystrixBuilderTest { @@ -236,6 +365,23 @@ public class HystrixBuilderTest {
assertThat(testSubscriber.getOnNextEvents().get(0)).containsExactly("foo", "bar");
}
@Test
public void rxSingleListFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
Single<List<String>> single = api.listSingle();
assertThat(single).isNotNull();
assertThat(server.getRequestCount()).isEqualTo(0);
TestSubscriber<List<String>> testSubscriber = new TestSubscriber<List<String>>();
single.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
assertThat(testSubscriber.getOnNextEvents().get(0)).containsExactly("fallback");
}
@Test
public void plainString() {
server.enqueue(new MockResponse().setBody("\"foo\""));
@ -247,6 +393,17 @@ public class HystrixBuilderTest { @@ -247,6 +393,17 @@ public class HystrixBuilderTest {
assertThat(string).isEqualTo("foo");
}
@Test
public void plainStringFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
String string = api.get();
assertThat(string).isEqualTo("fallback");
}
@Test
public void plainList() {
server.enqueue(new MockResponse().setBody("[\"foo\",\"bar\"]"));
@ -258,10 +415,21 @@ public class HystrixBuilderTest { @@ -258,10 +415,21 @@ public class HystrixBuilderTest {
assertThat(list).isNotNull().containsExactly("foo", "bar");
}
@Test
public void plainListFallback() {
server.enqueue(new MockResponse().setResponseCode(500));
TestInterface api = target();
List<String> list = api.getList();
assertThat(list).isNotNull().containsExactly("fallback");
}
private TestInterface target() {
return HystrixFeign.builder()
.decoder(new GsonDecoder())
.target(TestInterface.class, "http://localhost:" + server.getPort());
.target(TestInterface.class, "http://localhost:" + server.getPort(), new FallbackTestInterface());
}
interface TestInterface {
@ -311,4 +479,71 @@ public class HystrixBuilderTest { @@ -311,4 +479,71 @@ public class HystrixBuilderTest {
@Headers("Accept: application/json")
List<String> getList();
}
class FallbackTestInterface implements TestInterface {
@Override public HystrixCommand<String> command() {
return new HystrixCommand<String>(HystrixCommandGroupKey.Factory.asKey("Test")) {
@Override
protected String run() throws Exception {
return "fallback";
}
};
}
@Override public HystrixCommand<List<String>> listCommand() {
return new HystrixCommand<List<String>>(HystrixCommandGroupKey.Factory.asKey("Test")) {
@Override protected List<String> run() throws Exception {
List<String> fallbackResult = new ArrayList<String>();
fallbackResult.add("fallback");
return fallbackResult;
}
};
}
@Override public HystrixCommand<Integer> intCommand() {
return new HystrixCommand<Integer>(HystrixCommandGroupKey.Factory.asKey("Test")) {
@Override protected Integer run() throws Exception {
return 0;
}
};
}
@Override public Observable<List<String>> listObservable() {
List<String> fallbackResult = new ArrayList<String>();
fallbackResult.add("fallback");
return Observable.just(fallbackResult);
}
@Override public Observable<String> observable() {
return Observable.just("fallback");
}
@Override public Single<Integer> intSingle() {
return Single.just(0);
}
@Override public Single<List<String>> listSingle() {
List<String> fallbackResult = new ArrayList<String>();
fallbackResult.add("fallback");
return Single.just(fallbackResult);
}
@Override public Single<String> single() {
return Single.just("fallback");
}
@Override public Observable<Integer> intObservable() {
return Observable.just(0);
}
@Override public String get() {
return "fallback";
}
@Override public List<String> getList() {
List<String> fallbackResult = new ArrayList<String>();
fallbackResult.add("fallback");
return fallbackResult;
}
}
}

Loading…
Cancel
Save