Spring Boot Microservices — Part8 — Sleuth & Zipkin & TraceableExecutorService

In this tutorial, we’ll implement the Sleuth and Zipkin server and also use TraceableExecutorService to propagate Trace Id within same ThreadPool.

order-service

Docker

The Docker Zipkin project is able to build docker images, provide scripts and a docker-compose.yml for launching pre-built images. The quickest start is to run the latest image directly:

docker run -d -p 9411:9411 openzipkin/zipkin

pom.xml

<?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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-service</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.2</spring-cloud.version>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

application.properties

#MySQL DB
spring.datasource.username=root
spring.datasource.password
=password

# Need to look at the ways to

spring.application.name=order-service
server.port
=0
eureka.instance.instance-id
=${spring.application.name}:${random.uuid}
#spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500
spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&vault://secret/order-service

management.endpoints.web.exposure.include
=*

#RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port
=5672
spring.rabbitmq.username
=guest
spring.rabbitmq.password
=guest

#
spring.cloud.stream.bindings.notificationEventSupplier-out-0.destination=notification-events
spring.cloud.stream.source
=notificationEventSupplier

# Sleuth & Zipkin
spring.sleuth.circuitbreaker.enabled=false
spring.sleuth.integration.enabled
=true
spring.zipkin.base-url
=http://localhost:9411/
spring.sleuth.sampler.probability
=1

OrderController.java

package com.example.controller;

import com.example.client.InventoryClient;
import com.example.dto.OrderDto;
import com.example.model.Order;
import com.example.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreaker;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;

@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
@Slf4j
public class OrderController {
private final OrderRepository orderRepository;
private final InventoryClient inventoryClient;
private final Resilience4JCircuitBreakerFactory circuitBreakerFactory;
private final StreamBridge streamBridge;
private final ExecutorService traceableExecutorService;

@PostMapping
public String placeOrder(@RequestBody OrderDto orderDto) {
circuitBreakerFactory.configureExecutorService(traceableExecutorService);

Resilience4JCircuitBreaker circuitBreaker = circuitBreakerFactory.create("inventory");
Supplier<Boolean> booleanSupplier = () -> orderDto.getOrderLineItemsList()
.stream()
.allMatch(orderLineItems -> {
log.info("Making call to Inventory Service for SkuCode {}", orderLineItems.getSkuCode());
return inventoryClient.checkStock(orderLineItems.getSkuCode());
});

boolean isProductInStock = circuitBreaker.run(booleanSupplier, throwable -> handleErrorCase());

if (isProductInStock) {
Order order = new Order();
order.setOrderLineItems(orderDto.getOrderLineItemsList());
order.setOrderNumber(UUID.randomUUID().toString());

orderRepository.save(order);
log.info("Sending Order Details with OrderId {} to Notification Service", order.getId());

//Send the Notication via RabbitMQ
streamBridge.send("notificationEventSupplier-out-0",
MessageBuilder.withPayload(order.getId()).build());

return "Order Placed Successfully!";
}else{
return "Order Failed !, One of the products in the order is not in the stock!";
}
}

private Boolean handleErrorCase() {
System.out.println("fallback called !!");
return false;
}
}

MainApp.java

package com.example;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService;
import org.springframework.context.annotation.Bean;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@RequiredArgsConstructor
public class OrderServiceApplication {

private final BeanFactory beanFactory;

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}

@Bean
public ExecutorService traceableExecutorService(){
ExecutorService service = Executors.newCachedThreadPool();
return new TraceableExecutorService(beanFactory, service);
}
}

InventoryClient.java, Order.java, OrderLineItems and OrderDto.java will remain unchanged.

Place the Order now

notification-service

pom.xml

<?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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>notification-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>notification-service</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.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-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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-stream</artifactId>
<scope>test</scope>
<classifier>test-binder</classifier>
<type>test-jar</type>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

EmailSender.java

@Service
@Slf4j
public class EmailSender {

public void sendEmail(String orderNumber) {
log.info("Order Placed Successfully - Order Number is {}", orderNumber);
log.info("Email Sent For Order Id {}", orderNumber);
}
}

MainApp.java

@SpringBootApplication
@EnableEurekaClient
public class NotificationServiceApplication {

public static void main(String[] args) {
SpringApplication.run(NotificationServiceApplication.class, args);
}

@Bean
public Consumer<Message<String>> notificationEventSupplier() {
return stringMessage -> new EmailSender().sendEmail(stringMessage.getPayload());
}
}

application.properties

spring.application.name=notification-service
eureka.instance.instance-id
=${spring.application.name}:${random.uuid}
server.port
=0

#
spring.cloud.stream.bindings.notificationEventSupplier-in-0.destination=notification-events

# Sleuth & Zipkin
spring.sleuth.integration.enabled=true
spring.zipkin.base-url
=http://localhost:9411/
spring.sleuth.sampler.probability
=1

Inventory-service

pom.xml

<?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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>inventory-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>inventory-service</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.2</spring-cloud.version>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<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-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

application.properties

#MySQL DB
spring.datasource.username=root
spring.datasource.password
=password

# Need to look at the ways to

spring.application.name=inventory-service
server.port
=0
eureka.instance.instance-id
=${spring.application.name}:${random.uuid}
#spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500
spring.config.import=optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&vault://secret/order-service

management.endpoints.web.exposure.include
=*

#RabbitMQ
spring.rabbitmq.host=localhost
spring.rabbitmq.port
=5672
spring.rabbitmq.username
=guest
spring.rabbitmq.password
=guest

#Sleuth & Zipkin
spring.zipkin.base-url=http://localhost:9411/
spring.sleuth.sampler.probability
=1

Inventory.java

@Entity
@Table(name = "inventory")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Inventory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String skuCode;
private Integer stock;
}

InventoryRepository.java

public interface InventoryRepository extends JpaRepository<Inventory, Long> {
Optional<Inventory> findBySkuCode(String skuCode);
}

InventoryController.java

@RestController
@RequestMapping("/api/inventory")
@RequiredArgsConstructor
@Slf4j
public class InventoryController {
private final InventoryRepository inventoryRepository;

@GetMapping("/{skuCode}")
public Boolean isInStock(@PathVariable("skuCode") String skuCode) {
log.info("Checking stock for product with skucode - " + skuCode);

Optional<Inventory> optionalInventory = inventoryRepository.findBySkuCode(skuCode);
if(!optionalInventory.isPresent()){
new RuntimeException("Cannot Find Product by sku code " + skuCode);
}
return optionalInventory.get().getStock() > 0;
}
}

MainApp.java

@SpringBootApplication
@EnableEurekaClient
public class InventoryServiceApplication {

public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}

}

--

--

--

Java Developer and enthusiast

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Introduction to Nginx 101

Top 5 Tools I use as a Salesforce Data Integration Architect For Data Wrangling

Native Application with Spring Native and JPA

Version 4.0.7 Update (iOS)

Got an Error code EDNS or syscall queryA from MailTrap, HERE THE SOLUTION !

RSS Reading MVVM based improvement

Parameters and Configurations Management

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
PA

PA

Java Developer and enthusiast

More from Medium

Spring Boot Microservices — Part1 — Eureka Discivery Service

Distributed micro-services using Spring Cloud — API Gateway

The proxy pattern in Microservices

Spring-Kafka: Create a spring-boot application to test Kafka broker