Spring Cloud, Eureka and Zuul

Spring Cloud, Eureka and Zuul

Introduction

        I was looking online for a simple end-to-end working example of Spring Cloud, Eureka and Zuul. I did not find a single post that gave a simple end-to-end implementation. I found so many posts that were really confusing and did not give a simple explanation of how these work together or how to set them up. Hence I came up with this post to explain how you can setup microservices using Spring Cloud, Eureka and Zuul.

Eureka, Zuul and Ribbon

Eureka, Zuul and Ribbon are part of Netflix Open Source Software (OSS).
Eureka in simple terms is a Discovery and Service registration server. Multiple microservices can register themselves to Eureka at runtime.
Zuul in simple terms is an Edge Server or a Gateway Service. It provides dynamic routing, monitoring, resiliency, security and a single entry point to multiple microservices. Zuul uses Ribbon to lookup available services in Eureka and routes the external request to an appropriate service instance.
Ribbon in simple terms provides Dynamic Routing and Load Balancing to multiple service instances. Ribbon uses the information available in Eureka to locate appropriate service instances. If Ribbon finds more than one service instance then it will apply load balancing to spread the requests over the available service instances.
Read more about Eureka, Zuul and Ribbon under the heading: Common Runtime Services & Libraries in the website: Netflix OSS.

In this tutorial, we will be addressing the following use cases using Eureka and Zuul:
  1. Setting up a single gateway for multiple microservices so that they can be accessed from one entry point.
  2. Load balancing of multiple service instances.

Requirements to run the application

  1. Java
  2. Maven
  3. IDE of your choice
We will setup the multiple microservices with Eureka and Zuul in few simple steps.

Step 1: Setup Eureka Server

pom.xml

Spring cloud related dependencies need to be added to pom.xml, Here are the dependencies:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
Here is the full pom.xml:
<?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.aj.eurekaservice</groupId>
 <artifactId>EurekaService</artifactId>
 <version>1.0.0</version>
    
    <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.4.0.RELEASE</version>
 </parent>

 <properties>
        <jdk.version>1.8</jdk.version>
        <java.version>1.8</java.version>
  <packaging>jar</packaging>
 </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

 <build>
  <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>
</project>

Main Class: EurekaServiceApplication 

The annotation: @EnableEurekaServer needs to be added to the main class to enable the application as a Eureka Server or a Discovery and Service registration server.
Here is the main class, EurekaServiceApplication.java:
package com.aj.eurekaservice;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServiceApplication {

    private static final Logger logger = LoggerFactory.getLogger(EurekaServiceApplication.class);

 public static void main(String[] args) throws Exception {
  SpringApplication.run(EurekaServiceApplication.class,args);
  logger.info("Eureka Service is up and running...");
 }
}

application.yml

The application port is setup and other configurations are added to setup the Eureka service. Here is the file, application.yml:
server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Step 2: Setup Zuul Server

pom.xml

Here is the full pom.xml:
<?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.aj.zuulservice</groupId>
 <artifactId>ZuulService</artifactId>
 <version>1.0.0</version>
    
    <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.4.0.RELEASE</version>
 </parent>

 <properties>
        <jdk.version>1.8</jdk.version>
        <java.version>1.8</java.version>
  <packaging>jar</packaging>
 </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

 <build>
  <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>
</project>

Main Class: ZuulServiceApplication

The annotations: @EnableDiscoveryClient and @EnableZuulProxy need to be added to the main class to enable the application as an Edge Server or a Gateway Service.
Here is the main class, ZuulServiceApplication.java:
package com.aj.zuulservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulServiceApplication {

    private static final Logger logger = LoggerFactory.getLogger(ZuulServiceApplication.class);

 public static void main(String[] args) throws Exception {
  SpringApplication.run(ZuulServiceApplication.class,args);
        logger.info("Zuul Service is up and running...");
 }
}

application.yml

The application port is setup as 8762.
The application is named as zuul-service.
The Eureka Service URL needs to be configured so that the Zuul Service can find it.
Boolean flags are set to true to register with Eureka and fetch its registry to discover all the microservices registered with Eureka.
Here is the file, application.yml:
spring:
  application:
    name: zuul-service
server:
  port: 8762
eureka:
  instance:
    preferIpAddress: true
  serviceurl:
    defaultzone: http://localhost:8761/eureka/
  client:
    registerWithEureka: true
    fetchRegistry: true

Step 3: Register the microservices

In this post I am using Spring Boot based microservices. I am assuming that you have a Basic Knowledge of Spring Boot and have a Basic Spring Boot Application running in your machine. If not, please check my blog post on Basic Spring Boot Application by going to the link: Spring Boot Tutorial
I will be using 2 Microservices:
  1. Student Service: A service to list Student details.
  2. Grading Service: A service to list grades got by the students.
To show you how load balancing is done by Zuul and Ribbon, I will run 2 instances of Student Service on two ports: 8081 and 8082. Only one instance of Grading Service will be setup.

Student Service

Other than the normal annotation in any Spring Boot Application: @SpringBootApplication, the main class: StudentServiceApplication has the annotation @EnableEurekaClient. Here is the main class:
package com.aj.studentservice;

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

@EnableEurekaClient
@SpringBootApplication
public class StudentServiceApplication {

 public static void main(String[] args) throws Exception {
  SpringApplication.run(StudentServiceApplication.class,args);
 }
}
The application name, server port and Eureka configurations are mentioned in the application.yml. Here is the application.yml for Student Service running on port 8081:
spring:
  application:
    name: student-service
server:
  port: 8081
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
  instance:
    preferIpAddress: true
application.yml for Student Service running on port 8082 will be similar to the above file, except that the port will be 8082.

I have created 2 GET APIs:
  • Ping API: GET API to ping the service. In the response, I am mentioning the port number on which the API is getting executed.
  • Students API: GET API to get the list of Students.
Here is StudentController.java:
package com.aj.studentservice.controller;

import com.aj.studentservice.model.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/")
public class StudentController {

    private static final Logger logger = LoggerFactory.getLogger(StudentController.class);

    @RequestMapping(value = "ping", method = RequestMethod.GET)
 public ResponseEntity<Map<String, String>> ping() {
  Map<String, String> response = new HashMap<>();
  response.put("message", "pong: 8081");
        return new ResponseEntity<>(response, HttpStatus.OK);
 }

    @RequestMapping(value = "students", method = RequestMethod.GET)
    public ResponseEntity<List<Student>> getStudents() {
        logger.info("In StudentController.getStudents(), using port 8081, fetching list of students");
        List<Student> students = new ArrayList<>();
        students.add(new Student("P001", "Jane", "Physics"));
        students.add(new Student("C001", "Jim", "Chemistry"));
        students.add(new Student("M001", "John", "Maths"));
        return new ResponseEntity<>(students, HttpStatus.OK);
    }
}

Grading Service

Just like the Student Service, Grading Service has the annotations: @SpringBootApplication and @EnableEurekaClient in the main class. Here is the class: GradingServiceApplication.java:
package com.aj.gradingservice;

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

@EnableEurekaClient
@SpringBootApplication
public class GradingServiceApplication {

 public static void main(String[] args) throws Exception {
  SpringApplication.run(GradingServiceApplication.class,args);
 }
} 
We allow Spring Boot choose a random port for us because later we are accessing this service with its name and so the port is mentioned as 0 in the application.yml:
spring:
  application:
    name: grading-service
server:
  port: 0
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
  instance:
    preferIpAddress: true
Controller Class: GradeController.java:
I have created 3 APIs:
  • Ping API: A GET API to ping the service. 
  • Grades API: A GET API to get the list of Student Grades.
  • Grade API: A POST API to create a Student Grade.
package com.aj.gradingservice.controller;

import com.aj.gradingservice.model.Grade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/")
public class GradeController {

    private static final Logger logger = LoggerFactory.getLogger(GradeController.class);

    @RequestMapping(value = "ping", method = RequestMethod.GET)
 public ResponseEntity<Map<String, String>> ping() {
  Map<String, String> response = new HashMap<>();
  response.put("message", "pong");
        return new ResponseEntity<>(response, HttpStatus.OK);
 }

    @RequestMapping(value = "grades", method = RequestMethod.GET)
    public ResponseEntity<List<Grade>> getGrades() {
        logger.info("In GradeController.getGrades(), fetching list of grades");
        List<Grade> grades = new ArrayList<>();
        grades.add(new Grade(1, "P001", "A+"));
        grades.add(new Grade(2, "C001", "A"));
        grades.add(new Grade(3, "M001", "B+"));
        return new ResponseEntity<>(grades, HttpStatus.OK);
    }

    @RequestMapping(value = "grade", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Grade> createGrade(@RequestBody Grade grade) {
        logger.info("Request received is: " + grade );
        Grade gradeCreated = new Grade(grade.getId(),grade.getStudentId(),grade.getGrade());
        return new ResponseEntity<>(gradeCreated, HttpStatus.OK);
    }
}

Project Setup

For Java Setup, please refer to:  Java Setup
For Maven Setup, please refer to: Maven Setup
For Git and Project Setup, please refer to: Git and Project Setup

Run Application

1. Run Eureka Service. As per the configuration, this runs on port 8761. So once it has started, we can access the Eureka Dashboard using the URL: http://localhost:8761
2. Run Zuul Service
3. Run Student Service on port 8081 and 8082
4. Run Grading Service
5. Now check the Eureka Dashboard and you will see that the Zuul Service, 2 Instances of Student Service and Grading Service have registered with Eureka.

API calls and results

Zuul Service will act as the only single Gateway for all the microservices. Zuul Service is running on port 8762.
1. All the routes available for Zuul that are discovered by Eureka can be checked using the GET           API: 
    http://localhost:8762/routes
Response:
{
    "/student-service/**": "student-service",
    "/grading-service/**": "grading-service"
}
2. GET API to ping Student Service. Observe in the response that when we call this API multiple     times we get some responses from port 8081 and some from 8082
    http://localhost:8762/student-service/ping

3. GET API to get all Students
    http://localhost:8762/student-service/students

4. GET API to ping Grading Service
    http://localhost:8762/grading-service/ping

5. GET API to get all student grades from Grading Service
    http://localhost:8762/grading-service/grades

6. POST API to create a Student Grade in the Grading Service
    http://localhost:8762/grading-service/grade
    JSON Request Body:
   { 
    "id": 1,
    "studentId": "P001",
    "grade": "A+"
   } 

Conclusion and GitHub link:

    In this post I have given a comprehensive overview of setting up microservices using Spring Cloud, Eureka and Zuul.
The code used in this post is available on GitHub:
  1. Eureka Service
  2. Zuul Service
  3. Grading Service
  4. Student Service: Running on Port 8081
  5. Student Service1: Running on Port 8082
    Learn the most popular and trending technologies like Machine Learning, Angular 5, Internet of Things (IoT), Akka HTTP, Play Framework, Dropwizard, Spring Cloud, Docker, Elastic Stack, Spring Boot and Flask in simple steps by reading my most popular blog posts at Software Developer Central.
    If you like my post, please feel free to share it using the share button just below this paragraph or next to the heading of the post. You can also tweet with #SoftwareDeveloperCentral on Twitter. To get a notification on my latest posts or to keep the conversation going, you can follow me on Twitter. Please leave a note below if you have any questions or comments.


Comments

Popular Posts

REST API using Play Framework with Java

Asynchronous Processing (@Async) in Spring Boot

Elasticsearch, Logstash, Kibana Tutorial: Load MySQL Data into Elasticsearch