
In the previous article, we introduced that Eureka is used for service registration and discovery, Feign supports service invocation and load balancing, Hystrix handles service fusing to prevent failure from spreading, Spring Cloud Config service cluster configuration center, it seems that a microservice framework has been completed .
We still missed a question, how can external applications access various internal microservices? In the microservice architecture, back-end services are often not directly exposed to the caller, but are routed to the corresponding service through an API gateway according to the requested URL. When the API gateway is added, a wall is created between the third-party caller and the service provider. This wall directly communicates with the caller for permission control, and then evenly distributes the requests to the background server.
Why do we need API Gateway
- Simplify client call complexity
In the microservice architecture mode, the number of back-end service instances is generally dynamic, and it is difficult for the client to find the access address information of the dynamically changed service instance. Therefore, in order to simplify the front-end call logic in microservice-based projects, API Gateway is usually introduced as a lightweight gateway, and related authentication logic is also implemented in API Gateway to simplify the complexity of mutual calls between internal services.

- Data tailoring and aggregation
Generally speaking, different clients have inconsistent data requirements for display, such as mobile phones or web terminals, or in low-latency or high-latency network environments.
Therefore, in order to optimize the client's use experience, API Gateway can tailor the general response data to meet the needs of different clients. At the same time, multiple API call logic can be aggregated, thereby reducing the number of client requests and optimizing the client user experience.
Of course, we can also provide different API Gateways for different channels and clients. The use of this mode is called Backend for front-end. In the Backend for front-end mode, we can target different Clients create their BFFs respectively. For further understanding of BFFs, please refer to this article: Pattern: Backends For Frontends
- Micro-service transformation of legacy systems
For the system, the transformation of microservices is usually due to more or less problems in the original system, such as technical debt, code quality, maintainability, scalability, and so on. The API Gateway model is also suitable for the transformation of this type of legacy system. Through the transformation of microservices, the problems in the original system are gradually repaired, thereby improving the responsiveness of the original business. By introducing an abstraction layer, gradually replace the old one with the new one.
Zuul introduction
Introduction
The service gateway is an indispensable part of the microservice architecture. In the process of uniformly providing REST APIs to external systems through the service gateway, in addition to the functions of service routing and load balancing, it also has functions such as permission control. Zuul in Spring Cloud Netflix plays such a role, providing front-door protection for the microservice architecture, and at the same time migrating these heavier non-business logic content of permission control to the service routing level, so that the service cluster body can have more High reusability and testability.
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, and security. Zuul is equivalent to the front door for all requests on the back end of the web site of the device and Netflix streaming application. Zuul can appropriately route requests to multiple Amazon Auto Scaling Groups.
Zuul execution process

The execution process can be clearly seen through the pictures. It is necessary to use zuul to make reasonable calls for various back-end references in microservices, such as load, current limiting, monitoring, security and other functions.
Use case
Ready to work
Before using Zuul, we first build a service registry and two simple services. For example, I built one service-A and one service-B. Then start eureka-server and these two services. By visiting eureka-server, we can see that service-A and service-B have been registered to the service center.

Start using Zuul
Introduce dependency on spring-cloud-starter-zuul, spring-cloud-starter-eureka, if not by specifying the serviceId, eureka dependency is not needed, but in order to be transparent to the details of the service cluster, serviceId is still used to avoid direct reference to the URL Way.
Use the @EnableZuulProxy annotation to enable Zuul in the main application class
@EnableZuulProxy
@SpringCloudApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
The @SpringCloudApplication annotation is used here, which has not been mentioned before. From the source code, we can see that it integrates @SpringBootApplication, @EnableDiscoveryClient, and @EnableCircuitBreaker. The main purpose is to simplify the configuration. The specific functions of these annotations will not be introduced in detail here, as the previous articles have already introduced them.
Configure basic information of Zuul application in application.properties, such as application name, service port, etc.
spring.application.name=api-gateway
server.port=5555
Zuul configuration
After completing the above work, Zuul is ready to run, but how to make it serve our microservice cluster requires us to configure separately. The following describes some common configuration contents in detail.
Service routing
Through the function of service routing, when we provide services to the outside world, we only need to expose the calling address configured in Zuul to allow callers to access our services in a unified manner, without knowing the specific host information that provides the service.
Two mapping methods are provided in Zuul:
# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/
The configuration defines that all the rules to Zuul are: /api-a-url/** accesses are mapped to http://localhost:2222/, which means that when we access http://localhost:5555 /api-a-url/add?a=1&b=2, Zuul will route the request to: http://localhost:2222/add?a=1&b=2.
Among them, the api-a-url part of the configuration property zuul.routes.api-a-url.path is the name of the route, which can be defined arbitrarily, but the path and url of a group of mapping relationships must be the same, and the same is true when we talk about serviceId below .
The method of url mapping is not particularly friendly to Zuul. Zuul needs to know all the addresses of our services in order to complete all mapping configurations. In fact, when we implement the microservice architecture, the relationship between the service name and the service instance address already exists in the eureka server, so we only need to register Zuul to the eureka server to discover other services, and then we can implement the serviceId mapping . For example, we can configure as follows
# routes to serviceId
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
For the two microservices service-A and service-B that we implemented in the preparation work, two routes api-a and api-b are defined to map respectively. In addition, in order for Zuul to discover service-A and service-B, the eureka configuration is also added.
Next, we start eureka-server, service-A, service-B and the service gateway implemented with Zuul here. In the control page of eureka-server, we can see that service-A and service-B are registered respectively And api-gateway
Try to access service-A and service-B through the service gateway, and access the following URLs according to the configured mapping relationship
- http://localhost:5555/api-a/add?a=1&b=2: Access the add service in service-A through serviceId mapping
- http://localhost:5555/api-b/add?a=1&b=2: Access the add service in service-B through serviceId mapping
- http://localhost:5555/api-a-url/add?a=1&b=2: Access the add service in service-A through url mapping
It is recommended to use the serviceId mapping method. In addition to being more friendly to Zuul maintenance, the serviceId mapping method also supports circuit breakers. In the case of service failures, it can effectively prevent the failure from spreading to the service gateway and affecting the external services of the entire system
After completing the service routing, we still need some security measures to protect the client from accessing the resources it should access. So we need to use Zuul's filters to achieve security control of our external services.
Defining a filter in the service gateway only needs to inherit the ZuulFilter abstract class to implement the four abstract functions defined by it to intercept and filter requests.
For example, in the following example, a Zuul filter is defined to check whether there is an accessToken parameter in the request before the request is routed. If there is an accessToken parameter, it will be routed. If not, access will be denied and a 401 Unauthorized error will be returned.
The implementation of custom filters needs to inherit ZuulFilter and need to rewrite the following four methods:
FilterType: Returns a string representing the type of filter. Four filter types with different life cycles are defined in zuul, as follows:
i. pre: can be called before the request is routed
ii. routing: is called when routing the request
iii. post: called after routing and error filters
iv. error: called when an error occurs while processing the request
FilterOrder: Define the execution order of the filter by int value
ShouldFilter: Return a boolean type to determine whether the filter is to be executed, so this function can be used to switch the filter. In the above example, we directly return true, so the filter always takes effect.
Run: The specific logic of the filter. It should be noted that here we use ctx.setSendZuulResponse(false) to make zuul filter the request without routing it, and then set the error code returned by ctx.setResponseStatusCode(401). Of course, we can also further optimize our return, such as , Edit the returned body content through ctx.setResponseBody(body), etc.
After implementing a custom filter, you need to instantiate the filter to take effect. We only need to add the following content to the application main class:
@EnableZuulProxy
@SpringCloudApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
After starting the service gateway, visit:
http://localhost:5555/api-a/add?a=1&b=2: 401 error is returned
http://localhost:5555/api-a/add?a=1&b=2&accessToken=token: correctly route to server-A and return the calculated content
to sum up
Not only realizes the routing function to shield many service details, but also realizes the routing of service level and load balance.
The decoupling of interface permission verification and microservice business logic is realized. Through the filter in the service gateway, the content of the request is verified in each life cycle, and the verification originally done at the external service layer is moved forward, ensuring the statelessness of the microservice and reducing the difficulty of microservice testing , Let the service itself focus more on the processing of business logic.
The circuit breaker is implemented, and the service gateway will not be blocked due to the failure of specific microservices, and external services can still be provided.