Microservices Communication: Zuul API Gateway
Learn how to use a Zuul proxy in microservices development to create more fluid, robust design that is less prone to errors.
Join the DZone community and get the full member experience.
Join For FreeIn the previous microservice tutorial, we learned about microservices handling client side load balancing.
Here, we will discuss Zuul proxy.
What Is a Zuul Proxy?
The crux of the microservices pattern is to create an independent service which can be scaled and deployed independently. So in a complex business domain, more than 50-100 microservices is very common. Let's imagine a system where we have fifty microservices now we have to implement a UI which is kind of a dashboard, so it calls multiple services to fetch and show the important information in the UI.
From a UI developer perspective, to collect information from fifty underlying microservices, it has to call fifty REST APIs, as each microservice exposes a REST API for communication. So the client has to know the details of all REST API and URL patterns/ports to call them. Certainly, it does not sound like a good design. It is kind of a breach of encapsulation; the UI has to know all microservices server/port details to query the services.
Moreover, think about the common aspects of a web program, like CORS, authentication, security, and monitoring in terms of this design- each microservice team has to develop all these aspects into its own service, so the same code has been replicated over fifty microservices. Changes in the authentication requirements or CORS policy will ripple over all services. It is against the DRY principle, so this type of design is very error-prone and rigid. To make it robust, it has to be changed in such way so that we have only one entry point where all common aspects code is written and the client communicates with that common service. Here, the Zuul (The Gatekeeper/Demigod) concept pops up.
Zuul acts as an API gateway or Edge service. It receives all the requests coming from the UI and then delegates the requests to internal microservices. So, we have to create a brand new microservice which is Zuul-enabled, and this service sits on top of all other microservices. It acts as an Edge service or client-facing service. Its Service API should be exposed to the client/UI. The client calls this service as a proxy for an internal microservice, then this service delegates the request to the appropriate service.
The advantage of this type of design is that common aspects like CORS, authentication, and security can be put into a centralized service, so all common aspects will be applied on each request, and if any changes occur in the future, we just have to update the business logic of this Edge Service.
Also, we can implement any routing rules or any filter implementation. Say we want to append a special tag into the request header before it reaches the internal microservices, we can do it in the Edge service.
As the Edge service itself is a microservice, it can be independently scalable and deployable, so we can perform some load testing, also.
Coding Time
Create a project using http://start.spring.io/ named it as EmployeeZuluService select Zuul and Eureka Discovery module as Edge service itself a Eureka client.
The pom.xml will look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>EmployeeZuulService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EmployeeZuulService</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Next, rename the application.properties to bootstrap.properties and write down the following configuration there:
spring.application.name=EmployeeAPIGateway
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8084
security.basic.enable: false
management.security.enabled: false
zuul.routes.employeeUI.serviceId=EmployeeDashBoard
zuul.host.socket-timeout-millis=30000
As this is a microservice, it needs to be registered in the Eureka server so it can be aware of other services. Also, this service runs on port 8084.
Pay attention to the last two properties:
zuul.routes.employeeUI.serviceId=EmployeeDashBoard
By this, we are saying if any request comes to the API gateway in form of /employeeUI, it will redirect to the EmployeeDashBoard microservice. So, if you hit the following URL: http://localhost:8084/employeeUI/dashboard/1, it will redirect to http://localhost:8081/dashboard/1.
Note that the UI developer is only aware of the Gateway service port; it is the Gateway service's responsibility to route the service to the appropriate microservice.
NB: Zuul can be implemented without Eureka server. In that case, you have to provide the exact URL of the service where it will be redirected.
With zuul.host.socket-timeout-millis=30000 --
, we instruct Spring Boot to wait for the response for 30000 ms until Zuul's internal Hystrix timeout will kick off and show you the error.
Now add @EnableZuulproxy and @EnableDiscoveryClient on top of EmployeeZuulServiceApplication class:
package com.example.EmployeeZuulService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class EmployeeZuulServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeZuulServiceApplication.class, args);
}
}
Our API gateway service is ready.
Now start the config server, Eureka server, EmployeeSearchService, EmployeeDashboardService, and EmployeeZuulService respectively.
If you hit the following URL: http://localhost:8084/employeeUI/dashboard/1, you will see the following output:
{
"employeeId": 1,
"name": "Shamik Mitra",
"practiceArea": "Java",
"designation": "Architect",
"companyInfo": "Cognizant"
}
Published at DZone with permission of Shamik Mitra, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments