In this tutorial, you’ll learn how to set up service discovery using Spring Cloud and Netflix Eureka. Service discovery is a key component of microservices architecture that allows services to discover and communicate with each other without hardcoding their network locations. We’ll create an Eureka Server for service registration and two microservices—Card Service and User Service—that will register with the Eureka server and interact with each other.
Project Structure
We’ll create three Spring Boot applications:
- Eureka Server: A service registry that keeps track of the available services.
- User Service: A simple microservice that provides user data.
- Card Service: A microservice that interacts with the User Service to retrieve user data.
Step 1: Create the Eureka Server
Let’s start by creating the Eureka Server, which will act as the service registry.
1.1 Create a Spring Boot Project
Add the necessary dependencies to your pom.xml
or build.gradle
.
Open your IDE and create a new Maven or Gradle project named eureka-server
.
<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-web</artifactId> </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>
Replace ${spring-cloud.version}
with the latest Spring Cloud version, e.g., 2023.0.0
.
1.2 Enable Eureka Server
- Open the main application class
EurekaServerApplication.java
and annotate it with@EnableEurekaServer
.
@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
1.3 Configure Application Properties
- Create an
application.yml
orapplication.properties
file in thesrc/main/resources
directory.
application.yml
:
server: address: 0.0.0.0 port: 8761 eureka: client: register-with-eureka: false fetch-registry: false spring: application: name: eureka-server
This configuration specifies that the Eureka Server will run on port 8761.
1.4 Run the Eureka Server
- Run the
EurekaServerApplication
class. - Open a browser and navigate to
http://localhost:8761
. You should see the Eureka dashboard, showing that no services are currently registered.
Step 2: Create the User Service
Next, let’s create a simple UserService
that will register itself with the Eureka Server.
2.1 Create a Spring Boot Project
- Create a new Maven or Gradle project named
user-service
. - Add the necessary dependencies.
<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> </dependencies>
2.2 Create the User Service Controller
- Create a simple UserRegistrationController that performs a validation of the User based on the age. The UserRegistrationService contains the validation logic:
@RestController @RequestMapping("/registration") class UserRegistrationController { private final UserRegistrationService userRegistrationService; UserRegistrationController(UserRegistrationService userRegistrationService) { this.userRegistrationService = userRegistrationService; } @PostMapping ResponseEntity<User> registerUser(@RequestBody UserDto userDto, UriComponentsBuilder uriComponentsBuilder) { User user = userRegistrationService.registerUser(userDto); UriComponents uriComponents = uriComponentsBuilder.path("/users/{uuid}") .buildAndExpand(user.getUuid()); return ResponseEntity.created(uriComponents.toUri()).body(user); } } @Service class UserRegistrationService { User registerUser(UserDto userDto) { User user = new User(userDto); verifyUser(user); return user; } private void verifyUser(User user) { System.out.println("Check user age"); if (user.getAge() > 18) { user.setStatus(User.Status.OK); } else { user.setStatus(User.Status.FRAUD); } } }
Then, to register the UserService application we will add the @EnableDiscoveryClient annotation in the main Application Class:
@SpringBootApplication @EnableDiscoveryClient public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }
2.3 Configure Application Properties
- Configure the
application.yml
file.
server: address: 0.0.0.0 port: 9082 spring: application: name: user-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
This configuration specifies that the UserService
will run on port 9082 and will register itself with the Eureka Server running on localhost:8761
.
2.4 Run the User Service
- Run the
UserServiceApplication
class.
Step 3: Create the Card Service
Now, we’ll create the CardService
that will communicate with UserService
via Eureka.
3.1 Create a Spring Boot Project
- Create a new Maven or Gradle project named
card-service
. - Add the necessary dependencies.
<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-loadbalancer</artifactId> </dependency> </dependencies>
3.2 Create a Client to Call User Service
- Create a Client to interact with the
UserService
using Service Discovery
@Component public class UserServiceClient { private final RestTemplate restTemplate; private final DiscoveryClient discoveryClient; UserServiceClient(@Qualifier("restTemplate") RestTemplate restTemplate, DiscoveryClient discoveryClient) { this.restTemplate = restTemplate; this.discoveryClient = discoveryClient; } public ResponseEntity<User> registerUser(CardApplicationDto.User userDto) { ServiceInstance instance = discoveryClient.getInstances("user-service") .stream().findAny() .orElseThrow(() -> new IllegalStateException("user-service unavailable")); return restTemplate.postForEntity(instance.getUri().toString() + "/registration", userDto, User.class); } }
How it works?
- DiscoveryClient: This is a tool that lets the application find other services registered with Eureka. In this case, it helps locate the
user-service
by its name. - RestTemplate: This is a Spring utility that allows you to make HTTP requests. It’s like a web browser but in code; it lets you send data to another service or ask for data from it.
- ServiceInstance: When
DiscoveryClient
finds theuser-service
, it gives back information like the URL where the service can be accessed. This information is wrapped in aServiceInstance
object.
Besides, in order to use the RestTemplate, we will also need the following Configuration:
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced RestTemplate loadBalancedRestTemplate() { return new RestTemplate(); } @Bean @Qualifier("restTemplate") RestTemplate restTemplate() { return new RestTemplate(); } }
@LoadBalanced
: This annotation tells Spring to wrap the RestTemplate
with a client-side load balancer
3.3 Create a Controller for the Card Service
The initial point of contact for our microservice ecosystem will be the Card Service Controller which acts as a gateway. It will intercept a call with the POST HTTP verb. Then, it will use the UserService Client to invoke the User Service.
@RestController @RequestMapping("/application") class CardApplicationController { private final CardApplicationService cardApplicationService; public CardApplicationController(CardApplicationService cardApplicationService) { this.cardApplicationService = cardApplicationService; } @PostMapping public ResponseEntity<ApplicationResult> apply(@RequestBody CardApplicationDto applicationDTO, UriComponentsBuilder uriComponentsBuilder) { CardApplication cardApplication = cardApplicationService .registerApplication(applicationDTO); UriComponents uriComponents = uriComponentsBuilder.path("/applications/{uuid}") .buildAndExpand(cardApplication.getUuid()); return ResponseEntity.created(uriComponents.toUri()) .body(cardApplication.getApplicationResult()); } }
3.4 Configure the Card Service
Include the following application.yml file which bind the Card Service to port 9080
spring: application: name: card-service server: address: 0.0.0.0 port: 9080 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
3.5 Run the Card Service
- Run the
CardServiceApplication
class.
Connect to the Eureka Console and Test
Finally, when all applications are up and running, connect to the eureka Console at localhost:8761 and verify that the User Service and the Card Service are available:
To test our application we will create a simple JSON Document which contains the User information. Namely, we will include the dateOfBirth which we check in the User Service:
{ "user": { "name": "Mary", "surname": "Smith", "idNo": "ZXC123", "dateOfBirth": "1970-11-03" }, "cardCapacity": 111 }
You can test it by sending the following POST Request to the Card Service:
curl -X POST -H "Content-Type: application/json" http://localhost:9080/application -d @cardApplication.json
You can verify that, with an age which is valid (18 or greater), the UserService will return a “valid” result. Otherwise it will return a “rejected”
{"status":"GRANTED"}
The full Source code for this tutorial is available here: https://github.com/fmarchioni/masterspringboot/tree/master/cloud/cloud-basic
Conclusion
In this tutorial, you learned how to set up a simple microservices architecture with Spring Cloud, using Netflix Eureka for service discovery. We created an Eureka Server
, a User Service
, and a Card Service
that interact with each other through Eureka.