Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iss 948 #977

Merged
merged 12 commits into from
Nov 24, 2015
54 changes: 42 additions & 12 deletions hystrix-contrib/hystrix-javanica/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,35 @@ The return type of command method should be Future that indicates that a command

## Reactive Execution

To performe "Reactive Execution" you should return an instance of `ObservableResult` in your command method as in the exapmple below:
To performe "Reactive Execution" you should return an instance of `Observable` in your command method as in the exapmple below:

```java
@HystrixCommand
public Observable<User> getUserById(final String id) {
return new ObservableResult<User>() {
@Override
public User invoke() {
return userResource.getUserById(id);
}
};
return Observable.create(new Observable.OnSubscribe<User>() {
@Override
public void call(Subscriber<? super User> observer) {
try {
if (!observer.isUnsubscribed()) {
observer.onNext(new User(id, name + id));
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
});
}
```

The return type of command method should be `Observable`.

HystrixObservable interface provides two methods: ```observe()``` - eagerly starts execution of the command the same as ``` HystrixCommand#queue()``` and ```HystrixCommand#execute()```; ```toObservable()``` - lazily starts execution of the command only once the Observable is subscribed to. To control this behaviour and swith between two modes ```@HystrixCommand``` provides specific parameter called ```observableExecutionMode```.
```@HystrixCommand(observableExecutionMode = EAGER)``` indicates that ```observe()``` method should be used to execute observable command
```@HystrixCommand(observableExecutionMode = LAZY)``` indicates that ```toObservable()``` should be used to execute observable command

**NOTE: EAGER mode is used by default**

## Fallback

Graceful degradation can be achieved by declaring name of fallback method in `@HystrixCommand` like below:
Expand Down Expand Up @@ -218,27 +231,27 @@ A fallback can be async or sync, at certain cases it depends on command executio
case 1: sync command, sync fallback

```java
@HystrixCommand(fallbackMethod = "fallbackAsync")
@HystrixCommand(fallbackMethod = "fallback")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}

@HystrixCommand
User fallbackAsync(String id) {
User fallback(String id) {
return new User("def", "def");
}
```

case 2: async command, sync fallback

```java
@HystrixCommand(fallbackMethod = "fallbackAsync")
@HystrixCommand(fallbackMethod = "fallback")
Future<User> getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}

@HystrixCommand
User fallbackAsync(String id) {
User fallback(String id) {
return new User("def", "def");
}
```
Expand All @@ -263,7 +276,7 @@ case 3: async command, async fallback

**Unsupported(prohibited)**

case 1: sync command, async fallback. This case isn't supported because in the essence a caller does not get a future buy calling ```getUserById``` command and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete ```fallbackAsync``` before a caller gets a result, having said it turns out there is no benefits of async fallback execution. Such case makes async fallback useless. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case.
case 1: sync command, async fallback command. This case isn't supported because in the essence a caller does not get a future buy calling ```getUserById``` and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete ```fallbackAsync``` before a caller gets a result, having said it turns out there is no benefits of async fallback execution. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case.

```java
@HystrixCommand(fallbackMethod = "fallbackAsync")
Expand All @@ -281,8 +294,25 @@ case 1: sync command, async fallback. This case isn't supported because in the e
};
}
```
case 2: sync command, async fallback. This case isn't supported for the same reason as for the case 1.

```java
@HystrixCommand(fallbackMethod = "fallbackAsync")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}

Future<User> fallbackAsync(String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return new User("def", "def");
}
};
}
```

Same restrictions are imposed on using observable feature in javanica.

## Error Propagation
Based on [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation) description, `@HystrixCommand` has an ability to specify exceptions types which should be ignored and wrapped to throw in `HystrixBadRequestException`.
Expand Down
1 change: 1 addition & 0 deletions hystrix-contrib/hystrix-javanica/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,5 @@ dependencies {
testCompile 'org.mockito:mockito-all:1.9.5'
testCompile 'log4j:log4j:1.2.17'
testCompile 'org.slf4j:slf4j-log4j12:1.7.7'
testCompile 'com.tngtech.java:junit-dataprovider:1.10.2'
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,13 @@
* @return exceptions to ignore
*/
Class<? extends Throwable>[] ignoreExceptions() default {};

/**
* Specifies the mode that should be used to execute hystrix observable command.
* For more information see {@link ObservableExecutionMode}.
*
* @return observable execution mode
*/
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2015 Netflix, Inc.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 com.netflix.hystrix.contrib.javanica.annotation;

import com.netflix.hystrix.HystrixObservable;
import rx.Observable;

/**
* Hystrix observable command can be executed in two different ways:
* eager - {@link HystrixObservable#observe()},
* lazy - {@link HystrixObservable#toObservable()}.
* <p/>
* This enum is used to specify desire execution mode.
* <p/>
* Created by dmgcodevil.
*/
public enum ObservableExecutionMode {

/**
* This mode lazily starts execution of the command only once the {@link Observable} is subscribed to.
*/
LAZY,

/**
* This mode eagerly starts execution of the command the same as {@link com.netflix.hystrix.HystrixCommand#queue()}
* and {@link com.netflix.hystrix.HystrixCommand#execute()}.
*/
EAGER
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.netflix.hystrix.HystrixExecutable;
import com.netflix.hystrix.HystrixInvokable;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.collapser.CommandCollapser;
import com.netflix.hystrix.contrib.javanica.command.CommandExecutor;
import com.netflix.hystrix.contrib.javanica.command.ExecutionType;
import com.netflix.hystrix.contrib.javanica.command.GenericHystrixCommandFactory;
import com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory;
import com.netflix.hystrix.contrib.javanica.command.MetaHolder;
import com.netflix.hystrix.contrib.javanica.utils.FallbackMethod;
import com.netflix.hystrix.contrib.javanica.utils.MethodProvider;
Expand All @@ -43,7 +43,6 @@
import java.util.Map;
import java.util.concurrent.Future;


import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getDeclaredMethod;
import static com.netflix.hystrix.contrib.javanica.utils.AopUtils.getMethodFromTarget;
import static com.netflix.hystrix.contrib.javanica.utils.EnvUtils.isCompileWeaving;
Expand Down Expand Up @@ -83,17 +82,12 @@ public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinP
}
MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
HystrixExecutable executable;
ExecutionType executionType = metaHolder.isCollapser() ?
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
if (metaHolder.isCollapser()) {
executable = new CommandCollapser(metaHolder);
} else {
executable = GenericHystrixCommandFactory.getInstance().create(metaHolder, null);
}
Object result;
try {
result = CommandExecutor.execute(executable, executionType, metaHolder);
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} catch (HystrixBadRequestException e) {
throw e.getCause();
}
Expand Down Expand Up @@ -210,15 +204,18 @@ private static class CommandMetaHolderFactory extends MetaHolderFactory {
@Override
public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class);
ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType());
MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint);
builder.defaultCommandKey(method.getName());
builder.hystrixCommand(hystrixCommand);
builder.executionType(ExecutionType.getExecutionType(method.getReturnType()));
return builder.build();
return builder.defaultCommandKey(method.getName())
.hystrixCommand(hystrixCommand)
.observableExecutionMode(hystrixCommand.observableExecutionMode())
.executionType(executionType)
.observable(ExecutionType.OBSERVABLE == executionType)
.build();
}
}

private static enum HystrixPointcutType {
private enum HystrixPointcutType {
COMMAND,
COLLAPSER;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@
package com.netflix.hystrix.contrib.javanica.collapser;

import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapserKey;
import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommandFactory;
import com.netflix.hystrix.contrib.javanica.command.BatchHystrixCommand;
import com.netflix.hystrix.contrib.javanica.command.HystrixCommandBuilderFactory;
import com.netflix.hystrix.contrib.javanica.command.MetaHolder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.List;

import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.initializeCollapserProperties;
import static org.slf4j.helpers.MessageFormatter.arrayFormat;

/**
Expand All @@ -49,11 +45,7 @@ public class CommandCollapser extends HystrixCollapser<List<Object>, Object, Obj
* @param metaHolder the {@link MetaHolder}
*/
public CommandCollapser(MetaHolder metaHolder) {
super(new CollapserSetterBuilder()
.collapserKey(metaHolder.getHystrixCollapser().collapserKey(), metaHolder.getDefaultCollapserKey())
.scope(metaHolder.getHystrixCollapser().scope())
.properties(metaHolder.getHystrixCollapser().collapserProperties())
.build());
super(HystrixCommandBuilderFactory.getInstance().create(metaHolder).getSetterBuilder().buildCollapserCommandSetter());
this.metaHolder = metaHolder;
}

Expand All @@ -71,7 +63,7 @@ public Object getRequestArgument() {
@Override
protected HystrixCommand<List<Object>> createCommand(
Collection<CollapsedRequest<Object, Object>> collapsedRequests) {
return BatchHystrixCommandFactory.getInstance().create(metaHolder, collapsedRequests);
return new BatchHystrixCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder, collapsedRequests));
}

/**
Expand All @@ -89,39 +81,6 @@ protected void mapResponseToRequests(List<Object> batchResponse,
}
}

/**
* Builder for {@link Setter}.
*/
private static final class CollapserSetterBuilder {

private String collapserKey;

private Scope scope;

private HystrixProperty[] properties;

private CollapserSetterBuilder collapserKey(String pCollapserKey, String def) {
this.collapserKey = StringUtils.isNotEmpty(pCollapserKey) ? pCollapserKey : def;
return this;
}

private CollapserSetterBuilder scope(Scope pScope) {
this.scope = pScope;
return this;
}

private CollapserSetterBuilder properties(HystrixProperty[] properties) {
this.properties = properties;
return this;
}

public Setter build() {
HystrixCollapserProperties.Setter propSetter = initializeCollapserProperties(properties);
return Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey(collapserKey)).andScope(scope)
.andCollapserPropertiesDefaults(propSetter);
}
}

private String createMessage(Collection<CollapsedRequest<Object, Object>> requests,
List<Object> response) {
return ERROR_MSG + arrayFormat(ERROR_MSF_TEMPLATE, new Object[]{getCollapserKey().name(),
Expand Down
Loading