Friday, July 8, 2016

Microservices based Cloud Native Application - Part II

Preview:

This is the second post in the series of Microservices based application development.
The entire series could be found here:


Overview:


Continuing from my previos post, I'm going to explain in detail, three concepts which are essential ingredients of a Microservices Architecture.
  1. Service Discovery
  2. API Gateway
  3. Circuit Breaker

Service Discovery:


In a Microservices environment, we will have multiple services and when the same is deployed in a Cloud Environment, we will  have multiple instances of each service.

In such a scenario, we need services to be self discover-able. This will help in two ways. 

First, when one service invokes another service, it needs to know the actual location where it is hosted and which instance it should point to.
Second, in a cloud environment, when we add/remove instances, other services need to know about this transparently.

Using a centralized Service Discovery will help us solve this problem. Spring Cloud Netflix provides a library called 'Eureka' which will allow services to register to and discover other services.

Following are some code snippets for Eureka client and Eureka Server:

Eureka client: 

Code:
The following annotation needs to be placed in all the microservices which would register into Eureka. (Note that I'm using Spring boot app).


@SpringBootApplication
@EnableAutoConfiguration
@EnableDiscoveryClient
public class AppMain extends SpringBootServletInitializer{

}

Configuration:
The microservice registers into Eureka with a specific name (or serviceId). This could be configured in a file bootstrap.yml.


bootstrap.yml:
server:
  port: 8090

spring:
  application:
    name: profile-details

In order to locate the Eureka server, the client needs to know the server details. This could be configured in a file application.yml.


eureka:
  instance:
    hostname: localhost
    leaseRenewalIntervalInSeconds: 10
    metadataMap:
      instanceId: ${vcap.application.instance_id:${spring.application.name}:${server.port:8080}}
  client:
    serviceUrl:
      defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/

Eureka server:

This is a Spring boot app with an annotation @EnableEurekaServer. 

Code:

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication {

 public static void main(String[] args) {
  SpringApplication.run(DiscoveryServerApplication.class, args);
 }
}

Configuration:
We can configure this application to be a Eureka server in application.yml

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/



API Gateway:



A microservices based application can have multiple clients (for eg, Web, Mobile, Partner integrations etc). Note that each of the individual microservices can evolve on its own and can deploy with different versions for different clients. In such scenarios, it will be necessary to provide a centralized interface which will perform the routing and tranformation services required.

An API Gateway does exactly this. Spring cloud Netfix provides a library Zuul which acts as an API gateway.

To create an API gateway server, use the annotation @EnableZuulProxy on the spring boot app. Note that this application should also register itself with Eureka, since it has to locate other services which it would forward to.

Code:
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy

public class EdgeServerApplication {
}


Configuration:
The following configs in application.yml will allow it to register with Eureka and add routing logic for request forwarding.

eureka:
  instance:
    hostname: localhost
    leaseRenewalIntervalInSeconds: 10
    metadataMap:
      instanceId: ${vcap.application.instance_id:${spring.application.name}:${server.port:8080}}
  client:
    serviceUrl:
      defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/ 
   
zuul:
  routes:    
    profile-skills: 
      path: /**/skill/**
      stripprefix: false
      serviceId: profile-skills
      
    profile-summary: 
      path: /**/summary/**
      stripprefix: false
      serviceId: profile-details



Note:

  • The angular web application will point to the edge server. So all microservice invocations from the angular app goes via edge server.
  • For eg, if the angular web app has to invoke profile-skills service, then it would invoke /<edge-server-host-url>/<something>/skill/<something>/.
    • The edge server would then apply the rule as in the above configuration, and forward it to the "profile-skills" microservice (It would use the host name map obtained from Eureka to resolve the actual host url)).
  • The "stripprefix" attribute in the above configuration would retain the prefix part of the url before the routing path (like before /skill or before /summary)


Circuit Breaker:



When we have a huge number of microservices (which we will, in a typical complex application), it is necessary for the services to be fault tolerant. Since it is common for services to fail in a cloud environment using commodity hardware, we need to design our services in a fault tolerant way.

Circuit Breaker is a pattern used in Microservices which will work pretty much like a Circuit breaker in electric circuits. When a service fails, it creates an open circuit, breaking the flow and to fix that temporarily, we define an alternative service implementation, which will kick in and close the circuit.

In our use case, Profile-Details service invokes Profile-Recommendation service. When Profile-Recommendation service fails, an alternative implementation kicks in which will just return a dummy default recommendation, so that the entire flow is not broken. Once the Profile-Recommendation service is back online, normal services will resume.


We use FeignClient (which is a Client side load balancer) along with Hysterix.

Code:
In the profile-details service, add this annotation to the spring boot app:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients

public class ProfileDetailsApplication {
}


Now to invoke the Profile-Recommendation service, we need not know the host url of the Profile-Recommendation service service. We just need to know the service name (configured in bootstrap.yml of the Profile-Recommendation service). Since we use Eureka, the service will resolve the host url.


Create an interface as below to invoke an API in Profile-Recommendation service.

@FeignClient(name = "profile-recommendation" , fallback = RecommendationClientFallback.class)
public interface RecommendationFeignClient {
 @RequestMapping(method = RequestMethod.GET, value = "/api/profile/{userId}/recommendation")
 List getRecommendations(@PathVariable("userId") String userId);
}

The value of the name attribute is the service name which we are calling. The value of the  fallback attribute is an alternative implementation, in case the actual call fails.
Invoking another microservice from one is this simple!!!
Following is the fallback implemntation:



@Component
public class RecommendationClientFallback implements RecommendationFeignClient {
    @Override
    public List getRecommendations(String userId) {
     List recommdList = new ArrayList();
     ProfileRecommendation recommend = new ProfileRecommendation();
     recommend.setRecommendationText("This is a default recommendation!");
     recommdList.add(recommend);
     return recommdList;
    }
}

Thats it!! When profile-recommendation service fails, it would invoke the fallback and would return "This is a default recommendation!".

No comments:

Post a Comment