Microservices with NetflixOSS: Ribbon part 4
This is the next post on the services of bulding a microservice with the netflix stack . If you did not read the previous posts i recomed you do:
* NetflixOSS The DevOps Stack for MicroServices - http://diego-pacheco.blogspot.com/2015/09/netflixoss-devops-stack-for.html
* Microservices with NetflixOSS: Karyon, Ribbon and Eureka part 1 - http://diego-pacheco.blogspot.com/2015/09/microservices-with-netflixoss-karyon.html
* Microservices with NetflixOSS: Building and Running Eureka part 2 - http://diego-pacheco.blogspot.com/2015/09/microservices-with-netflixoss-building.html
* Microservices with NetflixOSS: Karyon and Services part 3 - http://diego-pacheco.blogspot.com/2015/09/this-is-next-post-on-services-of.html
Now is the time to introduce Ribbon. So we gonna create a very simple client that will call our REST Service we did on the previous post. We will need eureka client in order to tell ribbon how to get the IP of the service - first of all we need configure the client with this config here: eureka-client.properties
This code could be used for several reasons, for instance this could be another microservice calling our microservice, this also could be a website. We just need 3 classe todo the job :-)
The Classes
ApiFallbackHandler -> This is fall back implementation is the code will be run when service call fails.
ApiResponseValidator -> This validates the response to check if everything is ok.
RibbonClientUsingEureka -> This is the ribbon client using eureka.
ApiResponseValidator
You can use this class to throw errors in case the anwser is not proper - for this case im just logging but keep in my this is not just for logging.
ApiFallbackHandler
This is important for the sense of Anti-fragility because here we can do other thing if the service is down or even return something is wrong or incosistent. The code is kinda of confusing because we need use RxJava to have non-blocking IO.
RibbonClientUsingEureka
Here is were the magic happens. I`m using the Ribbon Code Template to call our service. You can see we are using Eureka Client API to retrieve the service HOST and PORT. When you use Ribbon you are using Hystrix for free so this code is protected - you will have retrys, load balancers, time outs, caching and much more :-)
Lalter i`m just using the Google Gson library to process the JSON response. That`s it you have a microservice calling another microservice using Ribbon, Hystrix, Eureka and Karyon. You also can get the full code on my github https://github.com/diegopacheco/netflixoss-pocs/tree/master/ribbon-eureka-client
Cheers,
Diego Pacheco
* NetflixOSS The DevOps Stack for MicroServices - http://diego-pacheco.blogspot.com/2015/09/netflixoss-devops-stack-for.html
* Microservices with NetflixOSS: Karyon, Ribbon and Eureka part 1 - http://diego-pacheco.blogspot.com/2015/09/microservices-with-netflixoss-karyon.html
* Microservices with NetflixOSS: Building and Running Eureka part 2 - http://diego-pacheco.blogspot.com/2015/09/microservices-with-netflixoss-building.html
* Microservices with NetflixOSS: Karyon and Services part 3 - http://diego-pacheco.blogspot.com/2015/09/this-is-next-post-on-services-of.html
Now is the time to introduce Ribbon. So we gonna create a very simple client that will call our REST Service we did on the previous post. We will need eureka client in order to tell ribbon how to get the IP of the service - first of all we need configure the client with this config here: eureka-client.properties
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
eureka.region=default | |
eureka.name=weather-service | |
eureka.vipAddress=localhost | |
eureka.port=6002 | |
eureka.preferSameZone=true | |
eureka.shouldUseDns=false | |
eureka.us-east-1.availabilityZones=default | |
eureka.serviceUrl.default=http://localhost:8080/eureka/v2/ | |
# | |
# Because i`m not running on Amazon | |
# | |
eureka.validateInstanceId=false |
This code could be used for several reasons, for instance this could be another microservice calling our microservice, this also could be a website. We just need 3 classe todo the job :-)
The Classes
ApiFallbackHandler -> This is fall back implementation is the code will be run when service call fails.
ApiResponseValidator -> This validates the response to check if everything is ok.
RibbonClientUsingEureka -> This is the ribbon client using eureka.
ApiResponseValidator
You can use this class to throw errors in case the anwser is not proper - for this case im just logging but keep in my this is not just for logging.
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
@SuppressWarnings("rawtypes") | |
public class ApiResponseValidator implements ResponseValidator<HttpClientResponse> { | |
@Override | |
public void validate(HttpClientResponse response) throws UnsuccessfulResponseException,ServerError { | |
System.out.println("Validator: " + response); | |
} | |
} |
ApiFallbackHandler
This is important for the sense of Anti-fragility because here we can do other thing if the service is down or even return something is wrong or incosistent. The code is kinda of confusing because we need use RxJava to have non-blocking IO.
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
public class ApiFallbackHandler implements FallbackHandler<ByteBuf> { | |
@Override | |
public Observable<ByteBuf> getFallback(HystrixInvokableInfo<?> hystrixInfo,Map<String, Object> requestProperties) { | |
return Observable.from( | |
new java.util.concurrent.Future<ByteBuf>(){ | |
ByteBuf buf = Unpooled.wrappedBuffer(("Error on call api " + hystrixInfo + " : " + requestProperties).getBytes()); | |
public boolean cancel(boolean mayInterruptIfRunning) { | |
return false; | |
} | |
public boolean isCancelled() { | |
return false; | |
} | |
public boolean isDone() { | |
return true; | |
} | |
public ByteBuf get() throws InterruptedException,ExecutionException { | |
return buf; | |
} | |
public ByteBuf get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { | |
return buf; | |
} | |
}); | |
} | |
} |
RibbonClientUsingEureka
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
public class RibbonClientUsingEureka { | |
@SuppressWarnings("unchecked") | |
public static void main(String[] args) { | |
// http://localhost:6002/weather/now/gravatai | |
System.out.println("apiCall.ribbon.MaxAutoRetriesNextServer: " + ConfigurationManager.getConfigInstance().getProperty("apiCall.ribbon.MaxAutoRetriesNextServer")); | |
System.out.println("apiCall.ribbon.ConnectTimeout: " + ConfigurationManager.getConfigInstance().getProperty("apiCall.ribbon.ConnectTimeout")); | |
System.out.println("apiCall.ribbon.ReadTimeout: " + ConfigurationManager.getConfigInstance().getProperty("apiCall.ribbon.ReadTimeout")); | |
System.out.println("Using eureka: " + new DefaultEurekaClientConfigProvider().get().getEurekaServerServiceUrls("default")); | |
LifecycleInjectorBuilder builder = LifecycleInjector.builder(); | |
Injector injector = builder.build().createInjector(); | |
EurekaClient client = injector.getInstance(EurekaClient.class); | |
InstanceInfo info = client.getApplication("WEATHER-SERVICE").getInstances().get(0); | |
String server = "http://" + info.getVIPAddress() + ":" + info.getPort(); | |
System.out.println("Eureka Discovery client using: " + server); | |
HttpResourceGroup httpRG = Ribbon.createHttpResourceGroup("apiCall", | |
ClientOptions.create() | |
.withMaxAutoRetriesNextServer(1) | |
.withConfigurationBasedServerList(server) | |
); | |
HttpRequestTemplate<ByteBuf> apiTemplate = httpRG.newTemplateBuilder("apiCall",ByteBuf.class) | |
.withMethod("GET") | |
.withUriTemplate("/weather/now/gravatai") | |
.withFallbackProvider(new ApiFallbackHandler()) | |
.withResponseValidator(new ApiResponseValidator()) | |
.build(); | |
RibbonResponse<ByteBuf> result = apiTemplate.requestBuilder() | |
.build() | |
.withMetadata().execute(); | |
ByteBuf buf = result.content(); | |
String json = buf.toString(Charset.forName("UTF-8" )); | |
System.out.println("Result in Json: " + json); | |
Gson gson = new Gson(); | |
Map<String, String> mapResult = gson.fromJson(json, new TypeToken<Map<String, String>>() {}.getType()); | |
System.out.println("Result weather: " + mapResult.get("weather")); | |
System.exit(0); | |
} | |
} |
Lalter i`m just using the Google Gson library to process the JSON response. That`s it you have a microservice calling another microservice using Ribbon, Hystrix, Eureka and Karyon. You also can get the full code on my github https://github.com/diegopacheco/netflixoss-pocs/tree/master/ribbon-eureka-client
Cheers,
Diego Pacheco