Part 1: Creating microservice using Spring Cloud, Eureka and Zuul
Spring framework provides a set of libraries for creating microservices in Java. They are a part of the Spring Cloud project. Today I’m going to show you how to create simple microservices using Spring Boot and following technologies:
- Zuul – gateway service that provides dynamic routing, monitoring, resiliency, security, and more
- Ribbon – client side load balancer
- Feign – declarative REST client
- Eureka – service registration and discovery
- Sleuth – distributed tracing via logs
- Zipkin – distributed tracing system with request visualization.
Sample application is available at https://github.com/piomin/sample-spring-microservices.git. Here’s picture with application architecture. Client calls endpoint available inside customer-service which stores basic customer data via Zuul gateway. This endpoint interacts with account-service to collect information about customer accounts served by endpoint in account-service. Each service registering itself on Eureka discovery service and sending its logs to Zipkin using spring-cloud-sleuth.
This is an account-service
controller. We use findByCustomer
method for collecting customer accounts by its id.
@RestController
public class Api {
private List<Account> accounts;
protected Logger logger = Logger.getLogger(Api.class.getName());
public Api() {
accounts = new ArrayList<>();
accounts.add(new Account(1, 1, "111111"));
accounts.add(new Account(2, 2, "222222"));
accounts.add(new Account(3, 3, "333333"));
accounts.add(new Account(4, 4, "444444"));
accounts.add(new Account(5, 1, "555555"));
accounts.add(new Account(6, 2, "666666"));
accounts.add(new Account(7, 2, "777777"));
}
@RequestMapping("/accounts/{number}")
public Account findByNumber(@PathVariable("number") String number) {
logger.info(String.format("Account.findByNumber(%s)", number));
return accounts.stream().filter(it -> it.getNumber().equals(number)).findFirst().get();
}
@RequestMapping("/accounts/customer/{customer}")
public List<Account> findByCustomer(@PathVariable("customer") Integer customerId) {
logger.info(String.format("Account.findByCustomer(%s)", customerId));
return accounts.stream().filter(it -> it.getCustomerId().intValue()==customerId.intValue()).collect(Collectors.toList());
}
@RequestMapping("/accounts")
public List<Account> findAll() {
logger.info("Account.findAll()");
return accounts;
}
}
This is customer-service
controller. There is a findById method which interacts with account-service
using Feign
client.
@RestController
public class Api {
@Autowired
private AccountClient accountClient;
protected Logger logger = Logger.getLogger(Api.class.getName());
private List<Customer> customers;
public Api() {
customers = new ArrayList<>();
customers.add(new Customer(1, "12345", "Adam Kowalski", CustomerType.INDIVIDUAL));
customers.add(new Customer(2, "12346", "Anna Malinowska", CustomerType.INDIVIDUAL));
customers.add(new Customer(3, "12347", "Paweł Michalski", CustomerType.INDIVIDUAL));
customers.add(new Customer(4, "12348", "Karolina Lewandowska", CustomerType.INDIVIDUAL));
}
@RequestMapping("/customers/pesel/{pesel}")
public Customer findByPesel(@PathVariable("pesel") String pesel) {
logger.info(String.format("Customer.findByPesel(%s)", pesel));
return customers.stream().filter(it -> it.getPesel().equals(pesel)).findFirst().get();
}
@RequestMapping("/customers")
public List<Customer> findAll() {
logger.info("Customer.findAll()");
return customers;
}
@RequestMapping("/customers/{id}")
public Customer findById(@PathVariable("id") Integer id) {
logger.info(String.format("Customer.findById(%s)", id));
Customer customer = customers.stream().filter(it -> it.getId().intValue()==id.intValue()).findFirst().get();
List<Account> accounts = accountClient.getAccounts(id);
customer.setAccounts(accounts);
return customer;
}
}
Here’s the interface of Feign client for communication with account-service
.
@FeignClient("account-service")
public interface AccountClient {
@RequestMapping(method = RequestMethod.GET, value = "/accounts/customer/{customerId}")
List<Account> getAccounts(@PathVariable("customerId") Integer customerId);
}
To be able to use Feign clients we only have to enable it in our main class.
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
There is also important configuration inside application.yml in customer-service. The ribbon load balancer needs to be enabled. I also suggest setting lease renewal and expiration on the Eureka client to enable unregistration from discovery service when our service is shutting down.
server:
port: ${PORT:3333}
eureka:
client:
serviceUrl:
defaultZone: ${vcap.services.discovery-service.credentials.uri:http://127.0.0.1:8761}/eureka/
instance:
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
ribbon:
eureka:
enabled: true
We’ve got our two microservices implemented and configured. But first we have to create and run discovery service based on Eureka server. This functionality is provided by our discovery-service. We only have to import one dependency spring-cloud-starter-eureka-server
and enable it in the application main class using @EnableEurekaServer
annotation. Here is configuration of Eureka server in application.yml file:
server:
port: ${PORT:8761}
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
server:
enableSelfPreservation: false
After running discovery-service we see its monitoring console available on 8761 port. And now let’s run our two microservices on default ports set in their application.yml configuration file and more two instances of them on another ports using -DPORT VM argument, for example account-service on port 2223, and customer-service on port 3334. Now we take a look at the Eureka monitoring console. We’ve got two instances of account-service running on 2222, 2223 ports and two instances of customer-service running on 3333, 3334 ports.
We have two instances of each microservice registered on the discovery server. But we need to hide our system complexity to the outside world. There should be only one IP address exposed on one port available for inbound clients. That’s why we need API gateway – Zuul. Zuul will forward our request to the specific microservice based on its proxy configuration. Such request will also be load balances by ribbon client. To enable Zuul gateway dependency spring-cloud-starter-zuul
should be added inside pom.xml and annotation @EnableZuulProxy
in the main class. This is Zuul configuration for ourservices set in application.yml.
server:
port: 8765
zuul:
prefix: /api
routes:
account:
path: /account/**
serviceId: account-service
customer:
path: /customer/**
serviceId: customer-service
...
Like we see Zuul is configured to be available under its default port 8765 and it forwards requests from /api/account/
path to account-service
and from /api/customer/
to customer-service
. When URL http://localhost:8765/api/customer/customers/1 is call several times we’ll see its load balanced between two instances of each microservice. Also when we shut down one microservice instance we can take a look that it is unregistered from the Eureka server.
In the second part of the article I’ll present how to use Spring Cloud Sleuth, Zipkin and ELK. If you are interested in see Part 2: Creating microservices – monitoring with Spring Cloud Sleuth, ELK and Zipkin.
70 COMMENTS