This post shows how to use Spring Boot Service Discovery with Consul. We’ll have two Spring Boot applications. One application registers itself with Consul. It then becomes discoverable to another application. Meanwhile, the other application does not register itself with Consul. Instead, it only contacts Consul to look for a service by name. Then, Consul returns the correct URL the application can use. Clear enough? Let’s go!
Requisites – Things We Used
For some, the requirements are already apparent. However, listing out the requisites is excellent and helpful for newbies.
- IntelliJ IDEA
- Spring Boot 2.4.5
- JDK 14
- Consul
- Docker for Windows
- Windows 10
Start Consul Docker Container Up
Next, we start up a Consul Docker container in our local development machine using the following command.
1 | docker run -p 8500:8500 consul |
Create First Spring Boot Service Application For Consul
We can either use Spring Initialzr or IntelliJ IDE to create the first Spring Boot service application for Consult. Then, we add only two dependencies – Spring Web and Consul Discovery. First, we have our pom.xml file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>service1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>service1</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> <spring-cloud.version>2020.0.2</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-consul-discovery</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> |
Second, we create an application.yml file as follows. For this service, we have to make it register itself at startup. Also, we provide a particular URL for Consult to perform a health check on the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | spring: application: name: service1 cloud: consul: host: localhost port: 8500 discovery: # instance-id must not be empty, must start with letter, end wit ha letter or digit, and have as interior characters ony # letter, digits, and hyphen instance-id: id:${random.uuid} # instance-id must not be empty, must start with letter, end wit ha letter or digit, and have as interior characters ony # letter, digits, and hyphen serviceName: name:${spring.application.name} # Consul needs this for health-check. Used for load-balancing stuff. healthCheckPath: /health-check register: true register-health-check: true deregister: true |
Note the instance-id and serviceName properties – they are prefixed with letters. See comments on the file. When the values are invalid, the application fails to start.
Next, we have the Java source code files. First, we have the main class.
1 2 3 4 5 6 7 8 9 10 11 | package com.turreta.springboot.consul.servicediscovery.service1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Service1Application { public static void main(String[] args) { SpringApplication.run(Service1Application.class, args); } } |
Then, we have the health check controller for Consul.
1 2 3 4 5 6 7 8 9 10 11 12 | package com.turreta.springboot.consul.servicediscovery.service1; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HealthCheckController { @GetMapping("/health-check") public String healthCheck() { return "I am fine!"; } } |
Lastly, we have our API controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.turreta.springboot.consul.servicediscovery.service1; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ApiController { @Value("${spring.application.name}") private String applicationName; @GetMapping("/info") public String getInfo() { return "{name: \"" + applicationName + "\" }"; } } |
Before moving on to the second Spring Boot application, we can start this application up. Then, we can check on Consul to see our registered service application.
Create Second Spring Boot Client Application That Uses Service Discovery
The second Spring Boot application uses the same set of dependencies similar to the first application. However, we don’t allow it to register itself with Consul at startup. After creating the project, create or update the application.yml as follows.
1 2 3 4 5 6 7 8 9 | server: port: 8081 spring: application: name: service2 cloud: consul: host: localhost port: 8500 |
This Spring Boot application will only discover the other application in Consul via Service Discovery. Then, it uses the correct URL to use the service application. Next, we create the Java source files. The main class has the following content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.turreta.springboot.consul.servicediscovery.service2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class Service2Application { public static void main(String[] args) { SpringApplication.run(Service2Application.class, args); } } |
Note that we are using the @EnableDiscoveryClient to reach out to Consul to discover the service application. Next, we have a REST controller to test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.turreta.springboot.consul.servicediscovery.service2; 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.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.net.URI; import java.util.List; @RestController public class GetOtherServiceInfoController { private RestTemplate restTemplate = new RestTemplate(); @Autowired private DiscoveryClient discoveryClient; @GetMapping("/get") public String get() { List<ServiceInstance> service1List = discoveryClient.getInstances("name-service1"); ServiceInstance service1 = null; for (ServiceInstance serviceInstance : service1List) { service1 = serviceInstance; break; } if(service1 == null) { URI uri = service1.getUri(); URI resolve = uri.resolve("/info"); return restTemplate.getForEntity(resolve, String.class) .getBody(); } else { return "Service 1 is not available"; } } } |
When we started this second Spring Boot application, we can test it using the HTTP://localhost:8081/get URL to retrieve information from the first Spring Boot application registered in Consul.