Zuul proxy as the API gateway

In most microservice implementations, internal microservice endpoints are not exposed outside. They are kept as private services. A set of public services will be exposed to the clients using an API gateway. There are many reasons to do this:

  • Only a selected set of microservices are required by the clients.
  • If there are client-specific policies to be applied, it is easy to apply them in a single place rather than in multiple places. An example of such a scenario is the cross-origin access policy.
  • It is hard to implement client-specific transformations at the service endpoint.
  • If there is data aggregation required, especially to avoid multiple client calls in a bandwidth-restricted environment, then a gateway is required in the middle.

Zuul is a simple gateway service or edge service that suits these situations well. Zuul also comes from the Netflix family of microservice products. Unlike many enterprise API gateway products, Zuul provides complete control for the developers to configure or program based on specific requirements:

Zuul proxy as the API gateway

The Zuul proxy internally uses the Eureka server for service discovery, and Ribbon for load balancing between service instances.

The Zuul proxy is also capable of routing, monitoring, managing resiliency, security, and so on. In simple terms, we can consider Zuul a reverse proxy service. With Zuul, we can even change the behaviors of the underlying services by overriding them at the API layer.

Setting up Zuul

Unlike the Eureka server and the Config server, in typical deployments, Zuul is specific to a microservice. However, there are deployments in which one API gateway covers many microservices. In this case, we are going to add Zuul for each of our microservices: Search, Booking, Fare, and Check-in:

Note

The full source code of this section is available under the chapter5.*-apigateway project in the code files.

  1. Convert the microservices one by one. Start with Search API Gateway. Create a new Spring Starter project, and select Zuul, Config Client, Actuator, and Eureka Discovery:
    Setting up Zuul

    The project structure for search-apigateway is shown in the following diagram:

    Setting up Zuul
  2. The next step is to integrate the API gateway with Eureka and the Config server. Create a search-apigateway.property file with the contents given next, and commit to the Git repository.

    This configuration also sets a rule on how to forward traffic. In this case, any request coming on the /api endpoint of the API gateway should be sent to search-service:

    spring.application.name=search-apigateway
    zuul.routes.search-apigateway.serviceId=search-service
    zuul.routes.search-apigateway.path=/api/**
    eureka.client.serviceUrl.defaultZone:http://localhost:8761/eureka/

    search-service is the service ID of the Search service, and it will be resolved using the Eureka server.

  3. Update the bootstrap.properties file of search-apigateway as follows. There is nothing new in this configuration—a name to the service, the port, and the Config server URL:
    spring.application.name=search-apigateway
    server.port=8095
    spring.cloud.config.uri=http://localhost:8888
  4. Edit Application.java. In this case, the package name and the class name are also changed to com.brownfield.pss.search.apigateway and SearchApiGateway respectively. Also add @EnableZuulProxy to tell Spring Boot that this is a Zuul proxy:
    @EnableZuulProxy
    @EnableDiscoveryClient
    @SpringBootApplication
    public class SearchApiGateway {
  5. Run this as a Spring Boot app. Before that, ensure that the Config server, the Eureka server, and the Search microservice are running.
  6. Change the website project's CommandLineRunner as well as BrownFieldSiteController to make use of the API gateway:
    Flight[] flights = searchClient.postForObject("http://search-apigateway/api/search/get", searchQuery, Flight[].class); 

In this case, the Zuul proxy acts as a reverse proxy which proxies all microservice endpoints to consumers. In the preceding example, the Zuul proxy does not add much value, as we just pass through the incoming requests to the corresponding backend service.

Zuul is particularly useful when we have one or more requirements like the following:

  • Enforcing authentication and other security policies at the gateway instead of doing that on every microservice endpoint. The gateway can handle security policies, token handling, and so on before passing the request to the relevant services behind. It can also do basic rejections based on some business policies such as blocking requests coming from certain black-listed users.
  • Business insights and monitoring can be implemented at the gateway level. Collect real-time statistical data, and push it to an external system for analysis. This will be handy as we can do this at one place rather than applying it across many microservices.
  • API gateways are useful in scenarios where dynamic routing is required based on fine-grained controls. For example, send requests to different service instances based on business specific values such as "origin country". Another example is all requests coming from a region to be sent to one group of service instances. Yet another example is all requests requesting for a particular product have to be routed to a group of service instances.
  • Handling the load shredding and throttling requirements is another scenario where API gateways are useful. This is when we have to control load based on set thresholds such as number of requests in a day. For example, control requests coming from a low-value third party online channel.
  • The Zuul gateway is useful for fine-grained load balancing scenarios. The Zuul, Eureka client, and Ribbon together provide fine-grained controls over the load balancing requirements. Since the Zuul implementation is nothing but another Spring Boot application, the developer has full control over the load balancing.
  • The Zuul gateway is also useful in scenarios where data aggregation requirements are in place. If the consumer wants higher level coarse-grained services, then the gateway can internally aggregate data by calling more than one service on behalf of the client. This is particularly applicable when the clients are working in low bandwidth environments.

Zuul also provides a number of filters. These filters are classified as pre filters, routing filters, post filters, and error filters. As the names indicate, these are applied at different stages of the life cycle of a service call. Zuul also provides an option for developers to write custom filters. In order to write a custom filter, extend from the abstract ZuulFilter, and implement the following methods:

public class CustomZuulFilter extends ZuulFilter{
public Object run(){}
public boolean shouldFilter(){}
public int filterOrder(){}
public String filterType(){}

Once a custom filter is implemented, add that class to the main context. In our example case, add this to the SearchApiGateway class as follows:

@Bean
public CustomZuulFilter customFilter() {
    return new CustomZuulFilter();
}

As mentioned earlier, the Zuul proxy is a Spring Boot service. We can customize the gateway programmatically in the way we want. As shown in the following code, we can add custom endpoints to the gateway, which, in turn, can call the backend services:

@RestController 
class SearchAPIGatewayController {

  @RequestMapping("/")
  String greet(HttpServletRequest req){
    return "<H1>Search Gateway Powered By Zuul</H1>";
  }
}

In the preceding case, it just adds a new endpoint, and returns a value from the gateway. We can further use @Loadbalanced RestTemplate to call a backend service. Since we have full control, we can do transformations, data aggregation, and so on. We can also use the Eureka APIs to get the server list, and implement completely independent load-balancing or traffic-shaping mechanisms instead of the out-of-the-box load balancing features provided by Ribbon.

High availability of Zuul

Zuul is just a stateless service with an HTTP endpoint, hence, we can have as many Zuul instances as we need. There is no affinity or stickiness required. However, the availability of Zuul is extremely critical as all traffic from the consumer to the provider flows through the Zuul proxy. However, the elastic scaling requirements are not as critical as the backend microservices where all the heavy lifting happens.

The high availability architecture of Zuul is determined by the scenario in which we are using Zuul. The typical usage scenarios are:

  • When a client-side JavaScript MVC such as AngularJS accesses Zuul services from a remote browser.
  • Another microservice or non-microservice accesses services via Zuul

In some cases, the client may not have the capabilities to use the Eureka client libraries, for example, a legacy application written on PL/SQL. In some cases, organization policies do not allow Internet clients to handle client-side load balancing. In the case of browser-based clients, there are third-party Eureka JavaScript libraries available.

It all boils down to whether the client is using Eureka client libraries or not. Based on this, there are two ways we can set up Zuul for high availability.

High availability of Zuul when the client is also a Eureka client

In this case, since the client is also another Eureka client, Zuul can be configured just like other microservices. Zuul registers itself to Eureka with a service ID. The clients then use Eureka and the service ID to resolve Zuul instances:

High availability of Zuul when the client is also a Eureka client

As shown in the preceding diagram, Zuul services register themselves with Eureka with a service ID, search-apigateway in our case. The Eureka client asks for the server list with the ID search-apigateway. The Eureka server returns the list of servers based on the current Zuul topology. The Eureka client, based on this list picks up one of the servers, and initiates the call.

As we saw earlier, the client uses the service ID to resolve the Zuul instance. In the following case, search-apigateway is the Zuul instance ID registered with Eureka:

Flight[] flights = searchClient.postForObject("http://search-apigateway/api/search/get", searchQuery, Flight[].class); 

High availability when the client is not a Eureka client

In this case, the client is not capable of handling load balancing by using the Eureka server. As shown in the following diagram, the client sends the request to a load balancer, which in turn identifies the right Zuul service instance. The Zuul instance, in this case, will be running behind a load balancer such as HAProxy or a hardware load balancer like NetScaler:

High availability when the client is not a Eureka client

The microservices will still be load balanced by Zuul using the Eureka server.

Completing Zuul for all other services

In order to complete this exercise, add API gateway projects (name them as *-apigateway) for all our microservices. The following steps are required to achieve this task:

  1. Create new property files per service, and check in to the Git repositories.
  2. Change application.properties to bootstrap.properties, and add the required configurations.
  3. Add @EnableZuulProxy to Application.java in each of the *-apigateway projects.
  4. Add @EnableDiscoveryClient in all the Application.java files under each of the *-apigateway projects.
  5. Optionally, change the package names and file names generated by default.

In the end, we will have the following API gateway projects:

  • chapter5.fares-apigateway
  • chapter5.search-apigateway
  • chapter5.checkin-apigateway
  • chapter5.book-apigateway
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.37.20