Microservices Using Spring Cloud Netflix - Eureka With Demo

1. Introduction

Microservice architecture is a buzzword now-a days. As opposed to traditional monolithic applications, an application is deployed as a suite of smaller and independent applications each one managing a separate business operation. These mini-applications can be independently developed (even in different languages), scaled up/down and deployed on single server or across different servers.


In order to accomplish a functionality, it’s often required for these services to communicate with each other. They typically communicate using REST API.In order for service A to access APIs of service B it has to know its host and port. Hardcoding it is not the right way, since this means they are tightly coupled.

Webp.net-gifmaker

The solution here is to have an equivalent of a Telephone Directory for services, where each service registers itself and Our Java programmers in India can simply fetch the desired service by its name.

There are various popular service registries, like Netflix Eureka, CONSOL and Zookeeper. In this article, we’ll focus on Netflix Eureka.



2. Netflix Eureka

Netflix Eureka is a popular service registry and discovery service.
Netflix built it and later open sourced it.


Following are its salient features:

3. Project

In this project, we will be creating 3 Microservice:

These services will be created using Spring-Boot as well as we’ll be using Spring-Cloud configuration.


3.1 Eureka Server:


To implement the Eureka server, all we need to do is:


The project structure is as follows:

3.1.1 Maven Dependencies


<?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.hemant</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> </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-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-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>Camden.SR5</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>

3.1.2 Spring boot application class


package com.hemant.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }

3.1.3 application.yaml


In addition to above, we have to specify the following configuration in application.yaml file (we can alternatively use application. Properties file).


# Server port server: port: 8761 eureka: client: # Dont register itself with eureka registerWithEureka: false fetchRegistry: false

Explanation:

3.2 User Service

This is a simple spring boot service which acts as a Eureka Client and gets registered with our Eureka Server.


The project structure is as follows:

3.2.1 Maven Dependencies


<?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.hemant</groupId> <artifactId>user-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>user-service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.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-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.2.6.RELEASE</version> </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> </project>

3.2.2 Sping boot Application class


package com.hemant.userservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }

The convenience annotation @EnableEurekaClient makes this application as a Eureka Client which can be registered with our Eureka Server.


3.2.3 REST API


This application also exposes a few REST END points.
The model is a simple User class:


package com.hemant.userservice.model; public class User { private String id; private String name; private String email; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof User)) return false; User other = (User) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", email=" + email + "]"; } public User(String id, String name, String email) { super(); this.id = id; this.name = name; this.email = email; } }

The REST Controller exposing the User based endpoints is as follows:


package com.hemant.userservice.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.annotation.PostConstruct; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.hemant.userservice.model.User; @RestController public class UserController { private Map<String, User> userMap; @PostConstruct public void initMap() { userMap = new HashMap<>(); for (int i = 1; i <= 5; i++) { String uuid = UUID.randomUUID().toString(); userMap.put(uuid, new User(uuid, "User" + i, "user" + i + "@gmail.com")); } } @RequestMapping(value = "", method = RequestMethod.GET) public List<User> getAllUsers() { return new ArrayList<>(userMap.values()); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUserById(@PathVariable("id") String id) { User user = userMap.get(id); if (null == user) { throw new IllegalArgumentException("User not found for : " + id); } return user; } }

Here the mock data is created using @PostConstruct and 2 endpoints are provided


3.2.4 Application properties


server : port : 8080 spring : application : name : user-service eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka

Explanation:

3.3 Info Service


This is another Eureka Client service similar to the User Service.
However it provides additional endpoints which:


The project structure is as follows:

3.3.1 Maven dependencies


<?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.hemant</groupId> <artifactId>info-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>info-service</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.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>1.2.6.RELEASE</spring.cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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-eureka</artifactId> <version>${spring.cloud.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

3.3.2 Spring boot application class


package com.hemant.info; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class InfoServiceApp { public static void main(String[] args) { SpringApplication.run(InfoServiceApp.class, args); } }

3.3.3 Controllers

Here 2 controllers are provides


1. EurekaClientController : This provides information about the Eureka


package com.hemant.info.controller; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/eureka") public class EurekaClientController { @Autowired private DiscoveryClient discoveryClient; @RequestMapping(value = "", method = RequestMethod.GET) public List<String> getAllEurekaClients() { return discoveryClient.getServices(); } @RequestMapping(value = "/{serviceName}", method = RequestMethod.GET) public Map<String, Object> getEurekaClientURLByName(@PathVariable String serviceName) { List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName); if (serviceInstances.isEmpty()) { throw new IllegalArgumentException("No instance found with serviceId :" + serviceName); } ServiceInstance instance = serviceInstances.get(0); Map<String, Object> infoMap = new LinkedHashMap<>(); infoMap.put("host", instance.getHost()); infoMap.put("port", instance.getPort()); infoMap.put("uri", instance.getUri()); infoMap.put("serviceId", instance.getServiceId()); infoMap.put("secured", instance.isSecure()); return infoMap; } }

2. UserClientController : This consumes the User service’s endpoints.


package com.hemant.info.controller; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserClientController { @Autowired private DiscoveryClient discoveryClient; private RestTemplate restTemplate = new RestTemplate(); private String userServiceName = "user-service"; @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public Map<String, String> getUserById(@PathVariable String id) { List<ServiceInstance> serviceInstances = discoveryClient.getInstances(userServiceName); if (serviceInstances.isEmpty()) { throw new IllegalArgumentException("No instance found with serviceId :" + userServiceName); } ServiceInstance userServiceInstance = serviceInstances.get(0); String url = userServiceInstance.getUri().toString() + "/" + id; ResponseEntity<String> authResponse = restTemplate.exchange(url, HttpMethod.GET, null, String.class); Map<String, String> map = new LinkedHashMap<>(); map.put("url", url); map.put("user-service-response", authResponse.getBody()); return map; } }

The userService’sServiceId (spring.application.name in itsapplication.properties) i.e. “user-service” is used to query the DiscoverClient instance so as to fetch the instance of UserService. The only hard-coding here is serviceId. The other details like URL, PORT etc. is maintained by Eureka.


3.3.4 Application properties


server.port : 8081 spring.application.name : info-service eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka

Explanation:

4. Starting the microservices :

4.1 Starting Eureka Server

1. Start the Eureka Server application, go to the project’s root directory and execute

>>>mvn clean install

2. If the build is success, we can launch the application by:


2018-07-26 23:47:47.792 INFO 5715 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8761 2018-07-26 23:47:47.801 INFO 5715 --- [ main] c.h.e.EurekaServerApplication : Started EurekaServerApplication in 13.399 seconds (JVM running for 14.281)

You can access, the Eureka Server console at http://localhost:8761/

5. Presently no client services are started.


4.2 Starting User Service :


The steps for starting this Eureka Client service are:

1. Go to the project’s root directory and execute


>>>mvn clean install

2. If the build is success, we can launch the application by:


>>>java -jar target/user-service-0.0.1-SNAPSHOT.jar

3. The following logs indicate that application is up and running and also registered with Eureka server.


2018-07-26 23:53:19.068 INFO 5752 --- [ main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8080 2018-07-26 23:53:19.080 INFO 5752 --- [ main] c.h.userservice.UserServiceApplication : Started UserServiceApplication in 10.22 seconds (JVM running for 11.243) 2018-07-26 23:53:19.173 INFO 5752 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER- SERVICE/192.168.1.117:user-service:8080 - registration status: 204

4. At the same point, in Eureka Server logs, you can see that User service has registered itself.


2018-07-26 23:53:19.165 INFO 5715 --- [nio-8761-exec-4] c.n.e.registry.AbstractInstanceRegistry : Registered instance USER- SERVICE/192.168.1.117:user-service:8080 with status UP (replication=false)

5. Upon refreshing the Eureka server console (http://localhost:8761/), you can see the UserService is now available.


4.3 Starting InfoService :


The steps are similar to that of starting of UserService.
After starting Info Service, it should be registered with Eureka and Eureka console on refresh should reflect the same


5. Demonstration

1. Access the User Service endpoints individually


GET http://localhost:8080/ Response: [ { "id": "30f9f24e-538b-458c-aa9b-1e06749ced52", "name": "User1", "email": "user1@gmail.com" }, { "id": "b514af00-2d32-4fe6-b9c1-43aeb83d58d4", "name": "User2", "email": "user2@gmail.com" }, { "id": "2ab01121-0b71-4581-a127-ad46c20f67a3", "name": "User4", "email": "user4@gmail.com" }, { "id": "d994c3ce-153f-4eaf-9aca-f2ae9a3735d3", "name": "User5", "email": "user5@gmail.com" }, { "id": "6dc503a1-2f59-4712-aac2-e2cc5496f303", "name": "User3", "email": "user3@gmail.com" } ] --------------------------------------------------------- GET http://localhost:8080/6dc503a1-2f59-4712-aac2-e2cc5496f303 Response: { "id": "6dc503a1-2f59-4712-aac2-e2cc5496f303", "name": "User3", "email": "user3@gmail.com" }

2. Access the info-service endpoint to give information on the client services currently registered on Eureka Server.


GET http://localhost:8081/eureka Response: [ "user-service", "info-service" ]

This gives the list of serviceIds (spring application names) which are registered with Eureka.


3. Access the API which provides more information on particular service, whose names is passed as path-variable


GET http://localhost:8081/eureka/user-service Response { "host": "192.168.1.117", "port": 8080, "uri": "http://192.168.1.117:8080", "serviceId": "USER-SERVICE", "secured": false } GET http://localhost:8081/eureka/demo-service Response { "timestamp": 1532630674705, "status": 500, "error": "Internal Server Error", "exception": "java.lang.IllegalArgumentException", "message": "No instance found with serviceId :demo-service", "path": "/eureka/demo-service" } Here since no service by serviceId "demo-service" is registered on Eureka, it's instance couldn't be found.

4. Access the UserClientController’s API. This will inturn fetch us the instance of UserService (running on 8080) from Eureka and call its API.


GET http://localhost:8081/user/6dc503a1-2f59-4712-aac2-e2cc5496f303 Response { "url": "http://192.168.1.117:8080/6dc503a1-2f59-4712-aac2-e2cc5496f303", "user-service-response": "{\"id\":\"6dc503a1-2f59-4712-aac2-e2cc5496f303\",\"name\":\"User3\",\"email\":\"user3@gmail.com\"}" }

6. Conclusion:

Thus we have implemented the Service registry and Discovery using Spring Cloud Netflix.

  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img