This a simple microservice(ish) application consisted of three microservices
-
gnomes-data: REST data endpoint that generates random data points (Eureka)
-
aggregation: Aggregate data from data endpoint, does in a reactive manner using rx-java (Eureka, Hystrix)
-
api: An [API-Gateway](http://microservices.io/patterns/apigateway.html) that serves the datapoint using charts (Eureka,Zuul)
You will need a circuit breaker and a service registry to run this apps:
cf cs p-circuit-breaker-dashboard standard hystrix
cf cs p-service-registry standard registry
You can push all apps at once using the parent manifest file. There’s a manifest on each microservice as well to push individually
cf push -f manifest.yml
You can demo how simple it is to bind to a service registry:
@SpringBootApplication
@EnableDiscoveryClient
@ServiceScan
public class DataApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DataApplication.class);
app.run(args);
}
}
If you curl the enpoint of this application:
{
data: 19.878341758728503
}
Outputs random data to simulate a data service
This project aggregates the results from the data project, it simulates two calls protected by hystrix.
If you curl the endpoint:
{
profit: 116.68136286795357,
gnomes: 15.65027543673633
}
The clients:
@Component
public class GnomesClient {
@Autowired
private RestTemplate template;
private Double lastValue = 0.0; (1)
@HystrixCommand(fallbackMethod = "defaultValue",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")
})
public Observable<Map<String,Double>> gnomesData(){
return new ObservableResult<Map<String, Double>>() {
@Override
public Map<String, Double> invoke() {
Map<String,Double> response = template.getForObject("http://gnomes-data/data",Map.class, Collections.singletonMap("base",10)); (2)
Double data = response.get("data");
lastValue = data;
return Collections.singletonMap("gnomes",data);
}
};
}
public Map<String,Double> defaultValue(){
return Collections.singletonMap("gnomes",lastValue);
}
}
-
This acts as a "cache" that fallback method will always return the last read value when the circuit opens
-
Note the usage of ribbon here, no call to an URI but instead a service id on eureka
The aggregation service:
@Component
public class AggregationService {
@Autowired
private GnomesClient gnomesClient;
@Autowired
private ProfitClient profitClient;
public Observable<Map<String,Double>> aggregate(){ (1)
return Observable.zip(gnomesClient.gnomesData(), profitClient.profitData(), (d1,d2) -> {
Map<String,Double> zip = new HashMap<String, Double>();
zip.putAll(d1);
zip.putAll(d2);
return zip;
});
}
}
-
Combines two observables from the results of the clients into one
-
Point your browser to the api application:
The application charts the data from aggregation at a 5 requests/second rate
-
Point your browser to hystrix again to show the metrics
cf stop gnomes-data
You can demonstrate the resilience of your app, instead of the client throwing an error due a service being down, it actually plots the last read value from the service (fallback method)