Microservice Communication Using Consul, Ribbon, and Feign - A Step-by-Step Guide
In this tutorial, you'll build an application and learn how to set up communication between its microservices using Consul, Ribbon, and Feign.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we will build two simple microservices that communicate with each other via Consul service discovery, Ribbon load balancer, and Feign as REST client. At a high level, we will go through the following steps:
- Build a Volunteer microservice using Spring Boot.
- Build a Patient microservice using Spring Boot.
- Set up Consul agent for service registry and discovery.
- Integrate microservices to register with Consul.
- Patient microservice calls a service in Volunteer microservice using a traditional Spring RestTemplate.
- Patient microservice calls a service in the Volunteer microservice using Feign as a REST client.
Before we proceed to building the solution, let’s quickly go over all these components.
What Is a Microservice?
A microservice is a single autonomous component which is loosely coupled with other microservices working together to provide a business solution.
What Is Consul?
Consul is a peer to peer, strongly consistent data store that uses a gossip protocol to manage membership and form dynamic clusters and a raft protocol to provide consistency. It supports a variety of network protocols such as HTTP, TTL, and TCP. It supports health checking, such as whether the web server is unavailable (not returning a 200 HTTP code), memory utilization (let’s say > 90%, etc). Consul also supports multi-datacenter out of the box.
There are a few other products in the market to choose from for service discovery, such as Eureka or Zookeeper. Eureka is a Netflix product which has a less consistent communication protocol, but is easy to integrate with other Netflix products, such as Ribbon and Hysterix. Zookeeper is a clustered system that is mostly geared towards big data architecture such as Hadoop and Kafka. The comparison among these products is out of the scope of this article. You may find more detail here.
What Is Ribbon?
Ribbon is a client-side load balancer which rotates requests between a list of servers based on logic such as round robin or weigh based.
What Is a Feign Client?
Feign provides an abstraction over REST-based calls via annotation, by which microservices can use to communicate with each other without writing detailed REST client code.
Now, let's start on our simple project to learn these components.
Building a Patient-to-Volunteer Application
Build the Volunteer Microservice Using Spring Boot
Step 1 - Create a Maven project add the below dependencies to its pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
Step 2 - Create a Spring Boot application Java class:
@Configuration
@EnableAutoConfiguration
@ComponentScan
@SpringBootApplication
@EntityScan(basePackages = {
"com.vs.domain"
}
public class VolunteerApplication {
public static void main(String[] args) {
SpringApplication.run(VolunteerApplication.class, args);
}
}
Step 3 - Create a Spring REST controller file:
@RestController
public class VolunteerController {
@RequestMapping("/")
public String home() {
return "Hello World from Volunteer application";
}
}
Step 4 - Add configuration properties to the application.yml file in the resources directory:
spring:
application:
name: VolunteerService
profiles:
active: dev
server:
contextPath: /vs
Step 5 - Run the application and test it in a browser. Run the VolunteerApplication.java file as a java application from eclipse. Once It starts up test the application by typing the below URL in a browser:
Step 6 - Add a domain class to hold data:
public class Volunteer {
private Long id;
private String firstname;
private String lastname;
private String userid;
private Set < String > services = new HashSet < String > ();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
Step 7 - Add a DataInitializer class to initialize data:
Note that in a real-world scenario, you will be fetching data from a database or from a persistent store. For the purposes of this demo, we are initializing data on this class file on startup of the application. You may use the ApplicationInitializer class for this purpose:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class DataInitializer {
public static List < Volunteer > vols = new ArrayList < Volunteer > ();
public DataInitializer() {
super();
System.out.println("Initializing data...");
initializeData();
}
public void initializeData() {
Volunteer vol1 = new Volunteer();
Volunteer vol2 = new Volunteer();
Volunteer vol3 = new Volunteer();
vol1.setFirstname("Jason");
vol1.setLastname("Brenner");
vol1.addService("droptoschool");
vol1.addService("donateblood");
vol2.setFirstname("David");
vol2.setLastname("Kelly");
vol2.addService("droptoschool");
vol3.setFirstname("John");
vol3.setLastname("Lebannon");
vol3.addService("donateblood");
vols.add(vol1);
vols.add(vol2);
vols.add(vol3);
System.out.println("Number of Volunteers:" + vols.size());
}
}
Step 8 - Create a service class to return a list of volunteers for a particular service:
public class VolunteerService {
@Autowired
DataInitializer initializer;
public VolunteerService() {
super();
}
public List < String > findVolunteers(String servicename) {
List < String > volnames = new ArrayList < String > ();
List < Volunteer > vols = initializer.vols;
for (Volunteer vol: vols) {
System.out.println(vol.getServices() + ":" + vol.getFirstname());
if (vol.getServices().contains(servicename)) {
volnames.add(vol.getFirstname() + " " + vol.getLastname());
}
}
return volnames;
}
}
Step 9 - Add a method to RESTController, wrapping up the above method:
@RequestMapping(value = "/volunteers/{servicename}", method = RequestMethod.GET)
public List < String > listVolunteers(@PathVariable("servicename") String servicename) {
VolunteerService service = new VolunteerService();
return service.findVolunteers(servicename);
}
Step 10 - Test the web service created above from a browser. In the browser address bar, type the below URL:
http://localhost:8080/vs/volunteers/droptoschool
Note - /vs is the application context assigned in the application.yml file of the microservice.
Build the Patient Microservice Using Spring Boot
Step 1 - Create a Maven project and add the below dependencies to the pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
Step 2 - Create a Spring Boot application:
@Configuration
@EnableAutoConfiguration
@SpringBootApplication
@ComponentScan
public class PatientApplication {
public static void main(String[] args) {
SpringApplication.run(PatientApplication.class, args);
}
}
Step 3 - Create a Spring REST controller file:
@RestController
public class PatientController {
@RequestMapping("/")
public String home() {
return "Hello World from Patient application";
}
}
Step 4 - Add configuration properties to the application.yml file in the resources directory:
spring:
application:
name: PatientService
profiles.active: dev
server:
contextPath: /ps
Port: 8082
Step 5 - Run the application and test it in a browser. Run PatientApplication.java as a Java application from Eclipse. Once it starts up, test the application by typing below URL in a browser:
http://localhost:8082/ps
Set Up Consul Agent for Service Registry and Discovery
Step 1 - Install Consul binaries. Install Consul version v0.9.2 binaries (a zip file) to any folder you desire from the below download site:
https://www.consul.io/downloads.html
Step 2 - Unzip the downloaded file. It will be unzipped to Consul.exe. You may add this path to your PATH variable for easy access.
Step 3 - Verify the installation. Run Consul version from a command line and verify the version.
Optionally, you can run consul catalog services to see the list of services running under Consul.
Step 4 - Run the Consul.exe from a command line:
consul agent -dev
Note that the default port it uses is 8500. Default configurations can be overridden either by passing command line arguments, adding a configuration file or setting up environment variables.
Integrate Microservices to Register With Consul
Step 1 - Add the below dependency to the pom.xml file for both the applications (Volunteer and Patient):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.4.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-all</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
Note that Actuator dependency is added to the application here. Actuator automatically configures and enables monitoring endpoints for an application. Application health is exposed /health URI. In the next step, we will add this endpoint to register with Consul.
Step 2- Add Consul properties to application.yml. Add the below lines to the application.yml file of the Patient application:
cloud:
consul:
discovery:
healthCheckPath: /ps/health
healthCheckInterval: 15s
ribbon:
enabled: true
endpoints:
health:
sensitive: false
management:
security:
enabled: false
health:
consul:
enabled: false
db:
enabled: false
diskspace:
enabled: false
defaults:
enabled: false
The value provided in healthCheckPath gets registered with Consul. Here, the application registers /ps/health with Consul. Note that /ps is the application context for the patient application. You may use any other application endpoint for monitoring with the consideration that the heartbeat check by Consul to monitor the endpoint does not impact the business processing. We have a few more Consul monitoring properties disabled, as we are not interested in those at this point.
Add the same properties to the application.yml file of the Volunteer application, then update healthCheckPath to /vs/health.
Step 3 - Update the Bootstrap application file to enable itself as Discovery client. Add the below annotation to the PatientApplication.java and VolunteerApplication.java files:
@EnableDiscoveryClient
Step 4 - Restart both the Volunteer application and Patient application.
Step 5 - Verify the discovery of the Volunteer application and Patient application in the Consul output:
2018/01/22 18:41:10 [DEBUG] http: Request GET /v1/event/list?wait=5s&index=1 (5.00075585s) from=127.0.0.1:53353
2018/01/22 18:41:11 [DEBUG] http: Request GET /v1/catalog/services (76.552µs) from=127.0.0.1:50668
2018/01/22 18:41:11 [DEBUG] agent: //Check 'service:Patientservice-dev-8082' is passing
- - - - - -
- - - - -- -
2018/01/22 18:41:23 [DEBUG] http: Request GET /v1/catalog/services (71.545µs) from=127.0.0.1:53332
2018/01/22 18:41:23 [DEBUG] agent: //Check 'service:VolunteerService-dev' is passing
Note that the services are registered to Consul by the property value defined in spring.application.name.
PatientService Calls a Service in VolunteerService via Traditional Spring RESTTemplate
Step 1 - Add logical URL of the volunteer service in the Patient application.
Add the below properties to the application.yml file in the Patient application:
services:
volunteer:
url: http://VolunteerService/vs
Note that we are not providing the ctaul host r port information here to reach out to VolunteerService. Service discovery will do it for us during runtime.
Read the value in the PatientController class by adding the below lines:
@Value("${services.volunteer.url}")
protected String serviceurl;
Step 2 - Autowire Spring RestTemplate. Add the below lines to the PatientController class to autowire Spring RestTemplate:
@Autowired
protected RestTemplate restTemplate;
Add the below lines to the Patient application class
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
Note that @LoadBalanced allows us to get the reference to service URL used in the RESTTemplate.
Step 3 - Add the below code snippet to the PatientController class to call the service in the Volunteer application:
@RequestMapping(value = "/volunteers/{aidname}", method = RequestMethod.GET)
public List < String > listVolunteers(@PathVariable("aidname") String aidname) {
System.out.println("listVolunteers via RestTemplate");
String[] volnames = restTemplate.getForObject(serviceurl + "/volunteers/{servicename}", String[].class, aidname);
List < String > volunteers = Arrays.asList(volnames);
return volunteers;
}
Step 4 - Test the microservice communication. Test the service communication by calling the URL of the patient application, which in turn calls the volunteer web service:
http://localhost:8082/ps/volunteers/droptoschool
PatientService Calls a Service in VolunteerService via the Feign Client
We learned that the web client discovers the web application from Consul via logical name and calls a service. However, it still has to know how to construct the service URL, pass parameters, and unmarshall return values.
The Feign client removes the need to write boilerplate code for the REST client. The REST client can just keep the same signature as the REST service and the internals will be handled by the Feign client.
Step 1 - Create a proxy class in patient application:
@FeignClient(name = "VolunteerService/vs")
public interface VolunteerServiceProxy {
@RequestMapping(value = "/volunteers/{servicename}", method = RequestMethod.GET)
List < String > getVolunteers(@PathVariable("servicename") String servicename);
}
Step 2- Add the below code snippet to the PatientController class:
@RequestMapping(value = "/volunteernames/{aidname}", method = RequestMethod.GET)
public List < String > listVolunteernames(@PathVariable("aidname") String aidname) {
System.out.println("listVolunteers via Feign");
List < String > volunteers = proxy.getVolunteers(aidname)
return volunteers;
}
Step 3 - Test the service call. Type the below URL into a browser:
http://localhost:8082/ps/volunteernames/droptoschool
References
https://www.consul.io/intro/index.html
https://projects.spring.io/spring-boot/
The volunteer service source code is available here.
The patient service source code is available here.
Opinions expressed by DZone contributors are their own.
Comments