1. About
1.1 Micronaut
Micronaut is an open-source JVM-based software framework for building lightweight, modular applications and microservices. It helps to create microservices with small memory footprints and quick startup time.
1.1 Distributed tracing
Troubleshooting interactions between microservices in a distributed architecture can be challenging when operating microservices in production. There are variously distributed tracing solutions, the most popular of which are Zipkin and Jaeger, both of which provide different levels of support for the Open Tracing API (OpenTracing is a tool developed by Zipkin to provide cross-platform compatibility. It offers a vendor-neutral API for adding tracing to the application and distributing that data to a distributed tracing system.). The most popular of which are Zipkin and Jagger, both of which provide varying levels of support for the Open Tracing API.
2. Use case
Let’s take a use case. Rahul is a shopkeeper, he sells items like mobile phones, laptops, etc, he wants to keep track of stock in his shop and wants to know whether his shop has a stock of a particular item or not without checking manually. To fulfill this requirement following three services are developed
- ItemCatalogue – It returns a list of items. It uses a domain consisting of an item name and an itemId. The itemCatalog service consumes endpoints exposed by other services
- ItemInventory – It exposes an endpoint to check whether an item has enough stock to satisfy the order. It uses a domain with a stock level and an itemId
- ItemRecommendation – This consumes previous services and exposes an endpoint, which recommends item names that are in stock
3. Pre-requisite
- Micronaut
- Gradle
- JDK 1.8 or greater installed with JAVA_HOME
- Enable annotation Processing.
- Install Jaeger via Docker
docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 jaegertracing/all-in-one:latest
4. MicroServices
Add tracing
dependency in build.gradle file of ItemCatalogue , ItemInventory and ItemRecommedation
implementation("io.micronaut:micronaut-tracing")
//to send tracing spans to Jaeger
runtimeOnly("io.jaegertracing:jaeger-thrift")
4.1 ItemCatalogue
The project structure for itemCatalogue microservice will be as follows-
The Application.java will be as follows-
package com.example; import io.micronaut.runtime.Micronaut; import io.micronaut.context.env.Environment; public class Application { public static void main(String[] args) { Micronaut.build(args) .mainClass(Application.class) .defaultEnvironments(Environment.DEVELOPMENT) .start(); } }
The item.java will be as follows-
package com.example; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import javax.validation.constraints.NotBlank; @Introspected public class Item { @NonNull @NotBlank private String itemId; @NonNull @NotBlank private String name; public Item() {} public Item(@NonNull @NotBlank String itemId, @NonNull @NotBlank String name) { this.itemId = itemId; this.name = name; } @NonNull public String getItemId() { return itemId; } public void setItemId(String itemId) { this.itemId = itemId; } @NonNull public String getName() { return name; } public void setName(@NonNull String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Item item = (Item) o; if (!itemId.equals(item.itemId)) return false; return name.equals(item.name); } @Override public int hashCode() { int result = itemId.hashCode(); result = 30 * result + name.hashCode(); return result; } }
The item.java will be as follows-
package com.example; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import java.util.Arrays; import java.util.List; @Controller("/items") public class ItemsController { @Get public List<Item> index() { Item buildingMicroservices = new Item("1491950358", "Building Microservices"); Item releaseIt = new Item("1680502395", "Release It!"); Item cidelivery = new Item("0321601912", "Continuous Delivery:"); return Arrays.asList(buildingMicroservices, releaseIt, cidelivery); } }
Configuration of application.yml file will be as follows-
micronaut: application: name: itemCatalogue endpoints: health: enabled: true sensitive: false #tag::jaeger[] tracing: jaeger: enabled: true sampler: probability: 1.0 # <1>
Configuration of application-dev.yml file will be as follows-
micronaut: server: port: 8081 # <1>
4.2 ItemInvetory
The project structure for itemInventory microservice will be as follows-
The ItemInventory.java will be as follows-
package com.example; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.Objects; @Introspected public class ItemInventory { @NonNull @NotBlank private String itemId; @NonNull @NotNull private Integer stock; public ItemInventory() {} public ItemInventory(@NonNull @NotBlank String itemId, @NonNull @NotNull Integer stock) { this.itemId = itemId; this.stock = stock; } @NonNull public String getItemId() { return itemId; } public void setItemId(@NonNull String itemId) { this.itemId = itemId; } @NonNull public Integer getStock() { return stock; } public void setStock(@NonNull Integer stock) { this.stock = stock; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ItemInventory that = (ItemInventory) o; if (!itemId.equals(that.itemId)) return false; return stock.equals(that.stock); } @Override public int hashCode() { int result = itemId.hashCode(); result = 31 * result + stock.hashCode(); return result; } }
The ItemsController.java will be as follows-
package com.example; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Produces; import io.micronaut.tracing.annotation.ContinueSpan; import io.micronaut.tracing.annotation.SpanTag; import javax.validation.constraints.NotBlank; import java.util.Optional; @Controller("/items") public class ItemsController { @Produces(MediaType.TEXT_PLAIN) @Get("/stock/{itemId}") @ContinueSpan // <1> public Boolean stock(@SpanTag("stock.itemId") @NotBlank String itemId) { // <2> return itemInventoryByItemId(itemId).map(bi -> bi.getStock() > 0).orElse(null); } private Optional<ItemInventory> itemInventoryByItemId(String itemId) { if (itemId.equals("1491950358")) { return Optional.of(new ItemInventory(itemId, 4)); } else if (itemId.equals("1680502395")) { return Optional.of(new ItemInventory(itemId, 5)); } return Optional.empty(); } }
Note : The Application.java file , application.yml will be implemented same as what we did for itemCatalogue Microservice.
Configuration of application-dev.yml file
micronaut: server: port: 8082 # <1>
4.3 ItemRecommedation
The project structure for itemRecommedation microservice will be as follows-
Create item.java file
package com.example; import java.util.Objects; public class Item { private String itemId; private String name; public Item() {} public Item(String itemId, String name) { this.itemId = itemId; this.name = name; } public String getItemId() { return itemId; } public void setItemId(String itemId) { this.itemId = itemId; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Item item = (Item) o; return Objects.equals(itemId, item.itemId) && Objects.equals(name, item.name); } @Override public int hashCode() { return Objects.hash(itemId, name); } }
Create ItemCatalogueClient
package com.example; import io.micronaut.http.annotation.Get; import io.micronaut.http.client.annotation.Client import io.micronaut.retry.annotation.Recoverable; import org.reactivestreams.Publisher; @Client("http://localhost:8081") // <1> @Recoverable(api = ItemCatalogueOperations.class) public interface ItemCatalogueClient extends ItemCatalogueOperations { @Get("/items") Publisher<Item> findAll(); }
Create ItemCatalogueOperations class
package com.example;
import org.reactivestreams.Publisher;
public interface ItemCatalogueOperations {
Publisher<Item> findAll();
}
Create ItemController class
package com.example; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import reactor.core.publisher.Flux; import org.reactivestreams.Publisher; @Controller("/items") public class ItemController { private final ItemCatalogueOperations itemCatalogueOperations; private final ItemInventoryOperations itemInventoryOperations; public ItemController(ItemCatalogueOperations itemCatalogueOperations, ItemInventoryOperations itemInventoryOperations) { this.itemCatalogueOperations = itemCatalogueOperations; this.itemInventoryOperations = itemInventoryOperations; } @Get public Publisher<ItemRecommendation> index() { return Flux.from(itemCatalogueOperations.findAll()) .flatMap(b -> Flux.from(itemInventoryOperations.stock(b.getItemId())) .filter(Boolean::booleanValue) .map(rsp -> b) ).map(item -> new ItemRecommendation(item.getName())); } }
Create ItemInventoryClient class
package com.example; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Consumes; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Produces; import io.micronaut.http.client.annotation.Client; import io.micronaut.retry.annotation.Recoverable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import javax.validation.constraints.NotBlank; @Client("http://localhost:8082") @Recoverable(api = ItemInventoryOperations.class) public interface ItemInventoryClient extends ItemInventoryOperations { @Consumes(MediaType.TEXT_PLAIN) @Get("/items/stock/{itemId}") Mono<Boolean> stock(@NotBlank String itemId); }
Create ItemInventoryOperations class
package com.example; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import javax.validation.constraints.NotBlank; public interface ItemInventoryOperations { Mono<Boolean> stock(@NotBlank String itemId); }
Create ItemRecommendation class
package com.example; import io.micronaut.core.annotation.Introspected; import java.util.Objects; @Introspected public class ItemRecommendation { private String name; public ItemRecommendation() {} public ItemRecommendation(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ItemRecommendation that = (ItemRecommendation) o; return Objects.equals(name, that.name); } @Override public int hashCode() { return Objects.hash(name); } }
Note : The Application.java file , application.yml will be implemented same as what we did for itemCatalogue Microservice.
Configuration of application-dev.yml file
micronaut: server: port: 8080 # <1>
5. Work Flow
Run itemCatalogue microservice. via cmd gradle run
You can navigate http://localhost:8081/items
Run itemInventory microservice. via cmd gradle run
You can navigate http://localhost:8082/items/stock/1680502395
Run itemRecommendation microservice. via cmd gradle run
You can navigate http://localhost:8080/items
You can then navigate to http://localhost:16686 to access the Jaeger UI.
In the above image, you can see that:
- Whenever a HTTP client executes a new network request, it creates a new span.
- Whenever a server receives a request, it creates a new span.
In addition, you can see that item inventory requests are made in parallel.
Note:
IF you see this error io.jaegertracing.internal.exceptions.SenderException: Failed to flush spans, in your console while navigating Jaeger UI. Then open terminal and remove the container via following cmd docker stop <Container_ID> docker rm <Container_ID>
6. Conclusion
In this blog, we have covered Distributed tracing with Jaeger and the Micronaut framework. Now you are ready with the understanding of Distributed tracing with Jaeger and the Micronaut framework.. For more, you can refer to the documentation: https://blog.knoldus.com/micornaut-data-jpa-hibernate/