Spring Boot 2 Microservices with Netflix Zuul API Gateway

In this lesson, we will demonstrate how we can make a Spring Boot based microservice which will reside behind an authentication service and the Netflix Zuul API Gateway. We will be using the Spring Initializr tool for setting up the project quickly.

During the course of the lesson, we will work on three different projects to construct microservice based architecture in our application. These three projects will be:

  • API Gateway based on Netflix Zuul which will perform the task of filtering, routing etc.
  • Service discovery server which will maintain the records for each microservice present in the system. This server will be based on Spring Eureka
  • Finally, a microservice which can be accessed via the API Gateway

This sounds a lot. Let’s get started with the Eureka Server first which will act as a Service Registration and Discovery server.

Please make sure that you are running on Java 8. Else code below will not work. Java 9 and above require extra dependencies.

Service Discovery

For maintaining the record of each microservice which is present in the system, we will Eureka server. Let’s setup the project to start.

Setting up the project

We will use Spring Initializr tool to quickly get started with the project setup. Here are the dependencies we need to add:

For this project, we need just one Maven dependency, the Eureka Server.

If you want to explicitly use Maven for dependency definitions, here is the part of pom.xml file:

<groupId>com.javabeginnerstutorial</groupId>
<artifactId>discovery-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging><name>discovery-server</name>
<description>Demo project for Spring Boot</description><parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.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>Finchley.M8</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>

Find the latest version of the dependencies here.

Making Eureka Server

With Spring Boot, it is extremely easy to turn a service into a running Eureka server for service registration and discovery. Let’s start with the main class:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class DiscoveryServerApplication {

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

The only key part here is the @EnableEurekaServer annotation. This turns the application into a Eureka server.

Finally, we need to insert some configuration for Eureka server as well:

#Port for Registry service
server.port=8761

#Service should not register with itself
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

#Managing the logging
logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

We just defined the port for the service and the logging level for some classes along with Eureka related properties. Now, we can run our application using the command:

mvn spring-boot:run

When the application is up, we can visit this URL:

http://localhost:8761/

Eureka will be up and running:

For now, that list of application which has registered with Eureka is empty. Once we initialize our microservice, we will see its instance in this service as well.

Spring boot Eureka Server Codebase

Preparing the microservice

For the next part of the system, we will start making the microservice application as well. We are making this before an API Gateway and the authentication service to make clear about the things which will be different later once those components come into the picture.

Setting up the project

Again, let’s quickly set up the project:

We need two dependencies in this project. Let’s have a look at the Maven pom.xml file:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.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>
</properties>

<dependencies>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>

  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
     <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
  </plugins>
</build>

Find the latest version of the dependencies here.

Making a Controller

For now, we just need a single URL which is part of this microservice and can be reached via an API Gateway which we will be making next. Let’s add this controller now:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@EnableEurekaClient
public class MicroserviceBeginnersApplication {

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

  @RequestMapping
  public String helloWorld() {
     return "Hello World";
  }
}

In above class definition, we added @EnableEurekaClient annotation as well. With this, we need to add some more configuration as:

# Application Config
server.port=8081
spring.application.name=Beginner-Microservice

# Eureka Config
eureka.client.eureka-server-port=8761

Finally, we can run our application using the command:

mvn spring-boot:run

When the application is up, we can visit this URL:

http://localhost:8081/

We will see the following output:

Also, notice the change in Eureka server as well. When this application started, it registered itself as a Eureka client and so, it will now appear in the Eureka dashboard as well:

Ignore the warning message and notice the instances listed in the Eureka as registered services. The name which we supplied to the service appears on the Dashboard, so it is important we define specific names for each application only.

Preparing the API Gateway

API Gateway is one of the biggest components in a microservice based application. With Netflix Zuul and Spring Boot, its configuration is super easy to do! Let’s use the Initializr first.

Setting up the project

Again, let’s quickly set up the project:

Here’s the Maven pom.xml file:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.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>Finchley.M8</spring-cloud.version>
</properties>

<dependencies>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-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>

<repositories>
  <repository>
     <id>spring-milestones</id>
     <name>Spring Milestones</name>
     <url>https://repo.spring.io/milestone</url>
     <snapshots>
        <enabled>false</enabled>
     </snapshots>
  </repository>
</repositories>

Zuul Filter

Though not required as of now, it is still a good practice to always make a Zuul Filter in the application. This is due to the reason that in the filer, we can do anything with the incoming requests like analyzing response time, logging, metric etc.

Here is how we can make a simple ZuulFilter:

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

public class SimpleFilter extends ZuulFilter {

   private static Logger log = LoggerFactory.getLogger(SimpleFilter.class);

   @Override
   public String filterType() {
       return "pre";
   }

   @Override
   public int filterOrder() {
       return 1;
   }

   @Override
   public boolean shouldFilter() {
       return true;
   }

   @Override
   public Object run() {
       RequestContext ctx = RequestContext.getCurrentContext();
       HttpServletRequest request = ctx.getRequest();

       log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

       return null;
   }
}

Here, we just log the incoming request along with its timestamp and request URL.

Next, we make this application a reverse proxy so that it can forward the requests to other microservices:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ApiGatewayApplication {

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

  @Bean
  public SimpleFilter simpleFilter() {
     return new SimpleFilter();
  }
}

This is a Eureka client as well, as mentioned by the @EnableEurekaClient annotation. Finally, we will provide configuration for the hello world service in application.properties file:

# Application Config
server.port=8082
spring.application.name=API-Gateway

# Eureka Config
eureka.client.eureka-server-port=8761

# Test service mapping
zuul.routes.hello.path=/hello/**
zuul.routes.hello.serviceId=Beginner-Microservice

Finally, we can run our application using the command:

mvn spring-boot:run

When the application is up, we can visit this URL:

http://localhost:8082/hello/

We will see the following output:

See how this URL returns the same output. This is because API Gateway forwarded the request to the hello world application which responded back to the API Gateway and we finally saw the result.

Notice how all the request and response forwarding has happened with just a few lines of code and configuration.

Zuul api gateway codebase

Conclusion

That is excellent. We saw we can set up microservice-based architecture with Spring Boot and one of the most popular API Gateway of all times, Netflix Zuul. Zuul can take care of routing and filtering on the fly and is an excellent choice for any scale of application.

To protect our microservices, we can restrict access to the microservice port itself on the host machine and allow only the API Gateway to access the microservices directly. This can vary based on your use-case of the architecture.

Codebase in Github

12 Comments

    • Could you please provide some more details. Like if service has been registered and all the application are up and running. And also paste full exception.

  1. Thanks sir very knowledgable article. Would request you kindly write an article for authorization and authentication on zuul end.

  2. Very simple and very good tutorial.
    I was having trouble with Zuul and after several tutorials, yours was the first one to run perfectly.
    Thanks a lot

  3. It would be great if you provided a download of your complete source code. I have followed all of the steps, but I can’t get the microservice routing to work. Having the original source code might help determine what step was missed.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.