java

Spicing Up Microservices with OpenTelemetry in Micronaut

Tame Distributed Chaos: OpenTelemetry and Micronaut's Symphony for Microservices

Spicing Up Microservices with OpenTelemetry in Micronaut

Understanding the behavior and performance of microservices in a distributed architecture is crucial. It’s where implementing distributed tracing steps in, especially in Micronaut applications. One of the best tools for this job is OpenTelemetry, which makes generating and exporting tracing data pretty straightforward. Here’s a spiced-up, super casual guide on integrating OpenTelemetry with Micronaut to get distributed tracing up and running.

Getting Started

First things first, you need to set up a few essentials to get going. Ensure that JDK 17 or a newer version is installed and that JAVA_HOME is configured properly on your machine. You’ll also need a capable text editor or Integrated Development Environment (IDE), and IntelliJ IDEA fits the bill perfectly for most. Lastly, pick your building tool - whether it’s Gradle or Maven, you’re good.

Setting Up Your Micronaut Application

Start off by creating a new Micronaut application. You can use the Micronaut Command Line Interface (CLI) or Micronaut Launch for this task. If you go with the CLI, you can fire off a command like this:

mn create-app example.micronaut.micronautguide --features=yaml,tracing-opentelemetry-zipkin,http-client --build=maven --lang=java --test=junit

Once the command is executed, you’ll have a brand new Micronaut application with all the necessary features for tracing and HTTP client functionality baked in.

Adding OpenTelemetry Dependencies

To infuse OpenTelemetry into your Micronaut application, you need to plug in the appropriate dependencies into your build.gradle or pom.xml file. Going the Gradle route? You’d add something like this:

dependencies {
    implementation "io.micronaut:micronaut-tracing"
    implementation "io.micronaut.tracing:micronaut-tracing-opentelemetry"
    annotationProcessor "io.micronaut.tracing:micronaut-tracing-opentelemetry-annotation"
}

If you’re more of a Maven person, then this is what you need for your pom.xml:

<dependencies>
    <dependency>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-tracing</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micronaut.tracing</groupId>
        <artifactId>micronaut-tracing-opentelemetry</artifactId>
    </dependency>
</dependencies>

<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut.tracing</groupId>
        <artifactId>micronaut-tracing-opentelemetry-annotation</artifactId>
    </path>
</annotationProcessorPaths>

Configuring OpenTelemetry

Next step: Configuration. OpenTelemetry can be configured via the application.yml file. Here’s a sample configuration to get you started:

otel:
  traces:
    exporter: zipkin
  service:
    name: micronaut-guide
  zipkin:
    url: http://localhost:9411

This setup configures OpenTelemetry to shoot the tracing data to a Zipkin server humming along at http://localhost:9411.

Running Jaeger or Zipkin

For visualizing all that tracing data, you need a tracing server like Jaeger or Zipkin. A quick Docker command can spin up Jaeger:

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 \
  jaegertracing/all-in-one:1.34

This Docker command does the trick, starting a Jaeger all-in-one container loaded with the collector, query, and UI components.

Writing Your Application

Picture this: You have three microservices—bookcatalogue, bookinventory, and bookrecommendation. Each handling different tasks and communicating with one another.

Book Catalogue Service

Let’s stitch together a simple Book Catalogue service that returns a list of books:

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

@Controller("/books")
public class BookCatalogueController {

    @Get
    @Produces(MediaType.APPLICATION_JSON)
    public List<Book> getBooks() {
        // Return a list of books
        return Arrays.asList(
            new Book("Book1", "ISBN1"),
            new Book("Book2", "ISBN2")
        );
    }
}

Book Inventory Service

Next, create a Book Inventory service that checks if a book has enough stock to fulfill an order:

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

@Controller("/books/stock")
public class BookInventoryController {

    @Get("/{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Boolean hasStock(@PathVariable String isbn) {
        // Check if the book has stock
        return true; // Replace with actual logic
    }
}

Book Recommendation Service

Finally, craft a Book Recommendation service that recommends books in stock. It will consume endpoints from the previous services:

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.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;

@Controller("/books/recommend")
public class BookRecommendationController {

    private final HttpClient httpClient;

    public BookRecommendationController(@Client("http://localhost:8081") HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Get
    @Produces(MediaType.APPLICATION_JSON)
    public List<String> recommendBooks() {
        // Fetch books from catalogue
        List<Book> books = httpClient.toBlocking().retrieve("/books", List.class);

        // Check stock for each book
        List<String> recommendedBooks = new ArrayList<>();
        for (Book book : books) {
            if (httpClient.toBlocking().retrieve("/books/stock/" + book.getIsbn(), Boolean.class)) {
                recommendedBooks.add(book.getName());
            }
        }

        return recommendedBooks;
    }
}

Using Tracing Annotations

To overlay tracing, sprinkle in a few tracing annotations provided by Micronaut:

import io.micronaut.tracing.annotation.NewSpan;
import io.micronaut.tracing.annotation.SpanTag;

@Controller("/books/recommend")
public class BookRecommendationController {

    @Get
    @Produces(MediaType.APPLICATION_JSON)
    @NewSpan("recommend-books")
    public List<String> recommendBooks() {
        // Fetch books from catalogue
        List<Book> books = httpClient.toBlocking().retrieve("/books", List.class);

        // Check stock for each book
        List<String> recommendedBooks = new ArrayList<>();
        for (Book book : books) {
            if (httpClient.toBlocking().retrieve("/books/stock/" + book.getIsbn(), Boolean.class)) {
                recommendedBooks.add(book.getName());
            }
        }

        return recommendedBooks;
    }

    @NewSpan("check-stock")
    public Boolean checkStock(@SpanTag("isbn") String isbn) {
        // Check stock logic here
        return true; // Replace with actual logic
    }
}

Running the Application

To kick things off, navigate into each service directory and run:

gradle run

This launches each microservice on different ports. Access the services via their respective URLs and watch the magic happen.

Visualizing Traces

When everything’s up and running, check out the Jaeger UI at http://localhost:16686 to visualize the traces. You can see the spans created by each service and their interactions.

Conclusion

Incorporating distributed tracing with OpenTelemetry in Micronaut applications isn’t rocket science. By adding the necessary dependencies, tweaking configurations, and using tracing annotations, you unlock deep insights into your microservices’ behavior. This is a game-changer for troubleshooting and fine-tuning your distributed system’s performance.

Distributed tracing is a cornerstone for modern microservice architectures. Micronaut paired with OpenTelemetry provides a robust and user-friendly solution to achieve top-tier tracing capabilities. Follow these steps, and you’ll have a seamless, efficient tracing setup, empowering you to monitor and debug like a pro!

Keywords: Micronaut, OpenTelemetry, distributed tracing, microservices, JDK 17, IntelliJ IDEA, Maven, Gradle, Zipkin, Jaeger



Similar Posts
Blog Image
Tag Your Tests and Tame Your Code: JUnit 5's Secret Weapon for Developers

Unleashing the Power of JUnit 5 Tags: Streamline Testing Chaos into Organized Simplicity for Effortless Efficiency

Blog Image
Java Concurrency Mastery: Essential Techniques for High-Performance Applications

Master Java concurrency with essential techniques for responsive applications. Learn thread pool management, synchronization patterns, and advanced utilities to build scalable systems. Improve your code today.

Blog Image
Can JWTs Make Securing Your Spring Boot REST API Easy Peasy?

Shielding Spring Boot REST APIs Like a Pro with JWT Authentication

Blog Image
10 Essential Java Testing Techniques Every Developer Must Master for Production-Ready Applications

Master 10 essential Java testing techniques: parameterized tests, mock verification, Testcontainers, async testing, HTTP stubbing, coverage analysis, BDD, mutation testing, Spring slices & JMH benchmarking for bulletproof applications.

Blog Image
Is Apache Kafka the Master Chef Your Real-Time Data Needs?

Whipping Up Real-Time Data Delights with Apache Kafka's Event Streaming Magic

Blog Image
Turbocharge Your Cloud-Native Java Apps with Micronaut and GraalVM

Boosting Java Microservices for the Cloud: Unleashing Speed and Efficiency with Micronaut and GraalVM