RabbitMQ RPC and Micronaut

Use RabbitMQ RPC to use request-reply pattern in your Micronaut applications.

Authors: Iván López

Micronaut Version: 2.0.0.RC1

1 Getting Started

This guide uses Micronaut 2.x. You can read this tutorial for Micronaut 1.x.

In this guide, we are going to create three microservices and communicate each other with RabbitMQ using the request-response pattern with RPC (Remote Procedure Call).

RabbitMQ is an open-source message-broker software that originally implemented the Advanced Message Queuing Protocol (AMQP) and has since been extended with a plug-in architecture to support Streaming Text Oriented Messaging Protocol (STOMP), Message Queuing Telemetry Transport (MQTT), and other protocols.

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 Solution

We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.

or

Then, cd into the complete folder which you will find in the root project of the downloaded/cloned project.

2 Writing the App

Lets describe the microservices you are going to build through the tutorial.

  • bookcatalogue - It returns a list of books. It uses a domain consisting of a book name and isbn.

  • bookinventory - It exposes an endpoint to check whether a book has sufficient stock to fulfill an order. I uses a domain consisting of a stock level and isbn.

  • bookrecommendation - It consumes previous services and exposes an endpoint which recommends book names which are in stock.

If you are using Java or Kotlin and IntelliJ IDEA make sure you have enabled annotation processing.

annotationprocessorsintellij

2.1 Catalogue Microservice

Create the bookcatalogue microservice:

mn create-app example.micronaut.bookcatalogue.bookcatalogue

The previous command creates a folder named bookcatalogue and a Micronaut app inside it with default package: example.micronaut.bookcatalogue.

Modify build.gradle to add rabbitmq dependency. As we only want to use RabbitMQ to receive requests we can remove Micronaut HTTP client and Server.

bookcatalogue/build.gradle
dependencies {
    ...
    ..
    .
    //implementation "io.micronaut:micronaut-http-client"
    //implementation "io.micronaut:micronaut-http-server-netty"
    implementation("io.micronaut.rabbitmq:micronaut-rabbitmq")
}

By default Micronaut will connect to a RabbitMQ instance running on localhost so it is not necessary to add anything to application.yml. In case you want to change the configuration, add the following:

bookcatalogue/src/main/resources/application.yml
---
rabbitmq:
  uri: amqp://localhost:5672

Create RabbitMQ exchange, queue and binding

Before being able to send and receive messages using RabbitMQ it is necessary to define the exchange, queue and binding. One option is create them directly in the RabbitMQ Admin UI available on http://localhost:15672. Use guest for both username and password.

Another option is create them programatically with Micronaut. Create the class ChannelPoolListener.java:

bookcatalogue/src/main/java/example/micronaut/bookcatalogue/ChannelPoolListener.java
package example.micronaut.bookcatalogue;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import io.micronaut.rabbitmq.connect.ChannelInitializer;

import javax.inject.Singleton;
import java.io.IOException;

@Singleton
public class ChannelPoolListener extends ChannelInitializer {

    @Override
    public void initialize(Channel channel) throws IOException {
        channel.exchangeDeclare("micronaut", BuiltinExchangeType.DIRECT, true); (1)

        channel.queueDeclare("inventory", true, false, false, null); (2)
        channel.queueBind("inventory", "micronaut", "books.inventory"); (3)

        channel.queueDeclare("catalogue", true, false, false, null); (4)
        channel.queueBind("catalogue", "micronaut", "books.catalogue"); (5)
    }
}
1 Define an exchange named micronaut. From the producer point of view everything is sent to the exchange with the appropriate routing key.
2 Define a queue named inventory. The consumer will listen for messages in that queue.
3 Define a binding between the exchange and the queue using the routing key books.inventory.
4 Define a queue named catalogue. The consumer will listen for messages in that queue.
5 Define a binding between the exchange and the queue using the routing key books.catalogue.
In this Catalogue Microservice the only necessary element is the catalogue queue but it is a good practice to define all the elements in the same file and share the file between all the projects.

Create consumer

Create a BookCatalogueService class to handle incoming RPC requests into the bookcatalogue microservice:

bookcatalogue/src/main/java/example/micronaut/bookcatalogue/BookCatalogueService.java
package example.micronaut.bookcatalogue;

import io.micronaut.rabbitmq.annotation.Queue;
import io.micronaut.rabbitmq.annotation.RabbitListener;

import java.util.Arrays;
import java.util.List;

@RabbitListener (1)
public class BookCatalogueService {

    @Queue("catalogue") (2)
    List<Book> listBooks() {
        Book buildingMicroservices = new Book("1491950358", "Building Microservices");
        Book releaseIt = new Book("1680502395", "Release It!");
        Book cidelivery = new Book("0321601912", "Continuous Delivery:");

        return Arrays.asList(buildingMicroservices, releaseIt, cidelivery);
    }
}
1 Annotate the class with @RabbitListener to indicate that this bean will consume messages from RabbitMQ.
2 Annotate the method with @Queue. This listener will listen to messages in catalogue queue.

The previous service responds a List<Book>. Create the Book POJO:

bookcatalogue/src/main/java/example/micronaut/bookcatalogue/Book.java
package example.micronaut.bookcatalogue;

import io.micronaut.core.annotation.Introspected;

import java.util.Objects;

@Introspected
public class Book {
    private String isbn;
    private String name;

    public Book() {
    }

    public Book(String isbn, String name) {
        this.isbn = isbn;
        this.name = name;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Book{" +
                "isbn='" + isbn + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(isbn, book.isbn) &&
                Objects.equals(name, book.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, name);
    }
}

2.2 Inventory Microservice

Create the bookinventory microservice:

mn create-app example.micronaut.bookinventory.bookinventory

The previous command creates a folder named bookinventory and a Micronaut app inside it with default package: example.micronaut.bookinventory.

Modify build.gradle to add rabbitmq dependency. As we only want to use RabbitMQ to receive requests we can remove Micronaut HTTP client and Server.

bookinventory/build.gradle
dependencies {
    ...
    ..
    .
    //implementation "io.micronaut:micronaut-http-client"
    //implementation "io.micronaut:micronaut-http-server-netty"
    implementation("io.micronaut.rabbitmq:micronaut-rabbitmq")
}

Create RabbitMQ exchange, queue and binding

As we did in Catalogue Microservice, create the class ChannelPoolListener.java in bookinventory/src/main/java/example/micronaut/bookcatalogue/ChannelPoolListener.java with the same content as before.

Create consumer

Create a BookInventoryService class to handle incoming RPC requests into the bookinventory microservice:

bookinventory/src/main/java/example/micronaut/bookinventory/BookInventoryService.java
package example.micronaut.bookinventory;

import io.micronaut.rabbitmq.annotation.Queue;
import io.micronaut.rabbitmq.annotation.RabbitListener;

import javax.validation.constraints.NotBlank;
import java.util.Optional;

@RabbitListener (1)
public class BookInventoryService {

    @Queue("inventory") (2)
    public Boolean stock(@NotBlank String isbn) {
        return bookInventoryByIsbn(isbn).map(bi -> bi.getStock() > 0).orElse(null);
    }

    private Optional<BookInventory> bookInventoryByIsbn(String isbn) {
        if (isbn.equals("1491950358")) {
            return Optional.of(new BookInventory(isbn, 4));
        } else if (isbn.equals("1680502395")) {
            return Optional.of(new BookInventory(isbn, 0));
        }
        return Optional.empty();
    }
}
1 Annotate the class with @RabbitListener to indicate that this bean will consume messages from RabbitMQ.
2 Annotate the method with @Queue. This listener will listen to messages in inventory queue.

The previous service uses BookInventory POJO. Create it:

bookinventory/src/main/java/example/micronaut/bookinventory/BookInventory.java
package example.micronaut.bookinventory;

import io.micronaut.core.annotation.Introspected;

import java.util.Objects;

@Introspected
public class BookInventory {
    private String isbn;
    private Integer stock;

    public BookInventory() {
    }

    public BookInventory(String isbn, Integer stock) {
        this.isbn = isbn;
        this.stock = stock;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BookInventory that = (BookInventory) o;
        return Objects.equals(isbn, that.isbn) &&
                Objects.equals(stock, that.stock);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, stock);
    }
}

2.3 Recommendation Microservice

Create the bookrecommendation microservice:

mn create-app example.micronaut.bookrecommendation.bookrecommendation

The previous command creates a folder named bookrecommendation and a Micronaut app inside it with default package: example.micronaut.bookrecommendation.

Modify build.gradle to add rabbitmq dependency. In this microservice we will use Micronaut HTTP Server to receive REST request so it is not necessary to remove any dependency.

bookrecommendation/build.gradle
dependencies {
    ...
    ..
    .
    implementation("io.micronaut.rabbitmq:micronaut-rabbitmq")
}

Create RabbitMQ exchange, queue and binding

As we did in Catalogue Microservice, create the class ChannelPoolListener.java in bookrecommendation/src/main/java/example/micronaut/bookcatalogue/ChannelPoolListener.java with the same content as before.

Create clients

Let’s create two interfaces to send messages to RabbitMQ. Micronaut will implement the interfaces at compilation time. Create CatalogueClient.java:

bookrecommendation/src/main/java/example/micronaut/bookinventory/CatalogueClient.java
package example.micronaut.bookrecommendation;

import io.micronaut.rabbitmq.annotation.Binding;
import io.micronaut.rabbitmq.annotation.RabbitClient;
import io.micronaut.rabbitmq.annotation.RabbitProperty;
import io.reactivex.Flowable;

import java.util.List;

@RabbitClient("micronaut") (1)
@RabbitProperty(name = "replyTo", value = "amq.rabbitmq.reply-to") (2)
public interface CatalogueClient {

    @Binding("books.catalogue") (3)
    Flowable<List<Book>> findAll(byte[] data); (4)

}
1 Send the messages to exchange micronaut.
2 Set the replyTo property to amq.rabbitmq.reply-to. This is a special queue that always exists and does not need to be created. That it is why we did not create the queue in the ChannelInitializer. RabbitMQ uses that queue in a special way and setting the value of the property replyTo to that queue will enable this call as a RPC one. RabbitMQ will create a temporary queue for the callback.
3 Set the routing key.
4 Define the method that will "mirror" the one in the consumer. Keep in mind that in the consumer it is not possible to return a reactive type, but on the client side it is. Also, it is necessary to send something, even if it’s not used in the consumer.

Create InventoryClient.java:

bookrecommendation/src/main/java/example/micronaut/bookinventory/InventoryClient.java
package example.micronaut.bookrecommendation;

import io.micronaut.rabbitmq.annotation.Binding;
import io.micronaut.rabbitmq.annotation.RabbitClient;
import io.micronaut.rabbitmq.annotation.RabbitProperty;
import io.reactivex.Maybe;

@RabbitClient("micronaut") (1)
@RabbitProperty(name = "replyTo", value = "amq.rabbitmq.reply-to") (2)
public interface InventoryClient {

    @Binding("books.inventory") (3)
    Maybe<Boolean> stock(String isbn); (4)

}
1 Send the messages to exchange micronaut.
2 Set the replyTo property to amq.rabbitmq.reply-to.
3 Set the routing key.
4 Define the method that will "mirror" the one in the consumer. As we did with CatalogueClient we use a reactive type to wrap the result.

Create the controller

Create a Controller which injects both clients.

bookrecommendation/src/main/java/example/micronaut/bookrecommendation/BookController.java
package example.micronaut.bookrecommendation;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.Flowable;

@Controller("/books") (1)
public class BookController {

    private final CatalogueClient catalogueClient; (2)
    private final InventoryClient inventoryClient; (2)

    public BookController(CatalogueClient catalogueClient, InventoryClient inventoryClient) { (2)
        this.catalogueClient = catalogueClient;
        this.inventoryClient = inventoryClient;
    }

    @Get("/") (3)
    public Flowable<BookRecommendation> index() {
        return catalogueClient.findAll(null)
                .flatMap(Flowable::fromIterable)
                .flatMapMaybe(book -> inventoryClient.stock(book.getIsbn())
                        .filter(Boolean::booleanValue)
                        .map(response -> book))
                .map(book -> new BookRecommendation(book.getName()));
    }

}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books
2 Clients are injected via constructor injection
3 The @Get annotation is used to map the index method to an HTTP GET request on /books.

The previous controller returns a Flowable<BookRecommendation>. Create the BookRecommendation POJO:

bookrecommendation/src/main/java/example/micronaut/bookrecommendation/BookRecommendation.java
package example.micronaut.bookrecommendation;

import io.micronaut.core.annotation.Introspected;

import java.util.Objects;

@Introspected
public class BookRecommendation {
    private String name;

    public BookRecommendation() {
    }

    public BookRecommendation(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;
        BookRecommendation that = (BookRecommendation) o;
        return Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

3 RabbitMQ and Micronaut

3.1 Install RabbitMQ via Docker

The fastest way to start using RabbitMQ is via Docker:

docker run --rm -it \
        -p 5672:5672 \
        -p 15672:15672 \
        rabbitmq:3.7.11-management

4 Running the app

Configure bookinventory to run on port 8082:

bookinventory/src/main/resources/application.yml
micronaut:
  server:
    port: 8082

Run bookinventory microservice:

complete $ ./gradlew complete:bookinventory:run

> Task :bookinventory:run
13:30:22.426 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 942ms. Server Running: 1 active message listeners.

Configure bookcatalogue to run on port 8081:

bookcatalogue/src/main/resources/application.yml
micronaut:
  server:
    port: 8081

Run bookcatalogue microservice:

complete $ ./gradlew complete:bookcatalogue:run
13:31:19.887 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1149ms. Server Running: 1 active message listeners.

Configure bookrecommendation to run on port 8080:

bookrecommendation/src/main/resources/application.yml
micronaut:
  server:
    port: 8080
8080 is the default port if you don’t specify micronaut.server.port property

Run bookrecommendation microservice:

complete $ ./gradlew complete:bookrecommendation:run
13:32:06.045 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1259ms. Server Running: http://localhost:8080

You can run a curl command to test the whole application:

$ curl http://localhost:8080/books
[{"name":"Building Microservices"}

5 Generate a Micronaut app's Native Image with GraalVM

We are going to use GraalVM, the polyglot embeddable virtual machine, to generate a Native image of our Micronaut application.

Native images compiled with GraalVM ahead-of-time improve the startup time and reduce the memory footprint of JVM-based applications.

5.1 Micronaut + GraalVM changes

Micronaut itself does not rely on reflection or dynamic classloading so works automatically with GraalVM native.

GraalVM Native Image allows you to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application, the libraries, the JDK and does not run on the Java VM, but includes necessary components like memory management and thread scheduling from a different virtual machine, called “Substrate VM”. Substrate VM is the name for the runtime components (like the deoptimizer, garbage collector, thread scheduling etc.). The resulting program has faster startup time and lower runtime memory overhead compared to a Java VM.

We are going to add Micronaut Graal’s annotation processor and svm to both application builds. It helps to handle generating the reflection-config.json metadata that is automatically picked up by the native-image tool.

Also, to simplify building the image you need to create a native-image.properties file. The convention is to use the folder src/main/resources/META-INF/native-image and then a folder following the maven coordinates of the application.

Changes to bookcatalogue to generate a GraalVM native image

Modify bookcatalogue/build.gradle, add the annotation processor and svm dependency:

bookcatalogue/build.gradle
    annotationProcessor("io.micronaut:micronaut-graal")
    compileOnly("org.graalvm.nativeimage:svm")

Add a native-image.properties inside src/main/resources/META-INF/native-image/example.micronaut/bookcatalogue folder.

src/main/resources/META-INF/native-image/example.micronaut/bookcatalogue/native-image.properties
Unresolved directive in <stdin> - include::/home/travis/build/micronaut-guides/micronaut-rabbitmq-rpc/complete/bookcatalogue/src/main/resources/META-INF/native-image/example.micronaut/bookcatalogue/native-image.properties[]
1 The -H:IncludeResources argument allows you to tweak which static resources to include. You can use regular expressions.
2 The -H:Name argument specifies the name of the native image that will be generated.
3 The -H:Class argument specifies the entry point of your application (the class that defines a static void main method).

Changes to bookinventory to generate a GraalVM native image

Modify bookinventory/build.gradle, add the annotation processor and svm dependency:

bookinventory/build.gradle
    annotationProcessor("io.micronaut:micronaut-graal")
    compileOnly("org.graalvm.nativeimage:svm")

Add a native-image.properties inside src/main/resources/META-INF/native-image/example.micronaut/bookinventory folder.

src/main/resources/META-INF/native-image/example.micronaut/bookinventory/native-image.properties
Unresolved directive in <stdin> - include::/home/travis/build/micronaut-guides/micronaut-rabbitmq-rpc/complete/bookinventory/src/main/resources/META-INF/native-image/example.micronaut/bookinventory/native-image.properties[]

Changes to bookrecommendation to generate a GraalVM native image

Modify bookrecommendation/build.gradle, add the annotation processor and svm dependency:

bookrecommendation/build.gradle
    annotationProcessor("io.micronaut:micronaut-graal")
    compileOnly("org.graalvm.nativeimage:svm")

Add a native-image.properties inside src/main/resources/META-INF/native-image/example.micronaut/bookrecommendation folder.

src/main/resources/META-INF/native-image/example.micronaut/bookrecommendation/native-image.properties
Unresolved directive in <stdin> - include::/home/travis/build/micronaut-guides/micronaut-rabbitmq-rpc/complete/bookrecommendation/src/main/resources/META-INF/native-image/example.micronaut/bookrecommendation/native-image.properties[]

5.2 Native Image generation

The easiest way to install GraalVM is to use SDKMan.io.

$ sdk list java
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
....
 GraalVM       |     | 20.1.0.r11   | grl     | installed  | 20.1.0.r11-grl
               |     | 20.1.0.r8    | grl     |            | 20.1.0.r8-grl
               |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     | installed  | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     | installed  | 19.3.1.r11-grl
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl
....

# For Java 8
$ sdk install java 20.1.0.r8-grl

# For Java 11
$ sdk install java 20.1.0.r11-grl

You need to install the native-image component which is not installed by default.

$ gu install native-image

Generate GraalVM native image for bookcatalogue

To generate the native image you need to generate the FAT JAR first.

bookcatalogue $ ./gradlew assemble

Invoke native-image. It may take a minute to complete.

userecho $  native-image --no-server -cp build/libs/bookcatalogue-0.1-all.jar
[userecho:14552]    classlist:   8,403.72 ms
[userecho:14552]        (cap):   2,196.75 ms
[userecho:14552]        setup:   3,163.33 ms
[userecho:14552]   (typeflow):  14,912.31 ms
[userecho:14552]    (objects):  17,479.74 ms
[userecho:14552]   (features):   2,182.07 ms
[userecho:14552]     analysis:  36,708.00 ms
[userecho:14552]     (clinit):   1,147.36 ms
[userecho:14552]     universe:   2,441.84 ms
[userecho:14552]      (parse):   1,114.89 ms
[userecho:14552]     (inline):   2,769.22 ms
[userecho:14552]    (compile):  13,846.04 ms
[userecho:14552]      compile:  19,961.65 ms
[userecho:14552]        image:   3,631.87 ms
[userecho:14552]        write:   1,139.84 ms
[userecho:14552]      [total]:  75,733.82 ms

--no-server options tells to not use server-based image building.

Generate GraalVM native image for bookinventory

bookinventory $ ./gradlew assemble
gateway $ native-image --no-server -cp build/libs/bookinventory-0.1-all.jar
[gateway:14380]    classlist:   4,769.83 ms
[gateway:14380]        (cap):   2,178.91 ms
[gateway:14380]        setup:   3,102.47 ms
[gateway:14380]   (typeflow):  13,167.91 ms
[gateway:14380]    (objects):  17,513.79 ms
[gateway:14380]   (features):   2,323.89 ms
[gateway:14380]     analysis:  35,342.33 ms
[gateway:14380]     (clinit):   1,119.21 ms
[gateway:14380]     universe:   2,471.35 ms
[gateway:14380]      (parse):   2,535.37 ms
[gateway:14380]     (inline):   2,379.36 ms
[gateway:14380]    (compile):  14,645.46 ms
[gateway:14380]      compile:  21,854.85 ms
[gateway:14380]        image:   5,309.31 ms
[gateway:14380]        write:   1,257.64 ms
[gateway:14380]      [total]:  74,390.31 ms

--no-server options tells to not use server-based image building.

Generate GraalVM native image for bookinventory

bookinventory $ ./gradlew assemble
gateway $ native-image --no-server -cp build/libs/bookinventory-0.1-all.jar

Generate GraalVM native image for bookrecommendation

bookrecommendation $ ./gradlew assemble
gateway $ native-image --no-server -cp build/libs/bookrecommendation-0.1-all.jar

5.3 Execute Native Images

Execute `bookcatalogue’s native image:

 ./bookcatalogue
15:05:06.684 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 26ms. Server Running: http://localhost:8081

Execute bookinventory native image:

 ./bookinventory
15:06:25.364 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 24ms. Server Running: http://localhost:8082

Execute bookrecommendation native image:

 ./bookrecommendation
15:06:25.364 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 24ms. Server Running: http://localhost:8080

You can run a curl command to test the whole application:

$ curl http://localhost:8080/books
[{"name":"Building Microservices"}

5.4 Native Image generation with Docker

Alternatively, you can use Docker to construct the GraalVM Native Image.

Changes to bookcatalogue to generate a GraalVM native image with Docker

Replace bookcatalogue/Dockerfile with this content:

FROM oracle/graalvm-ce:20.1.0-java8 as graalvm
RUN gu install native-image

COPY . /home/app/bookcatalogue
WORKDIR /home/app/bookcatalogue

RUN native-image --no-server -cp build/libs/bookcatalogue-*-all.jar

FROM frolvlad/alpine-glibc
RUN apk update && apk add libstdc++
EXPOSE 8080
COPY --from=graalvm /home/app/bookcatalogue/bookcatalogue /app/bookcatalogue
ENTRYPOINT ["/app/bookcatalogue"]

Add a script `docker-build.sh to run it:

#!/bin/sh
docker build . -t bookcatalogue
echo
echo
echo "To run the docker container execute:"
echo "    $ docker run -p 8080:8080 bookcatalogue"

Changes to bookinventory to generate a GraalVM native image with Docker

Replace bookinventory/Dockerfile with this content:

FROM oracle/graalvm-ce:20.1.0-java8 as graalvm
RUN gu install native-image

COPY . /home/app/bookinventory
WORKDIR /home/app/bookinventory

RUN native-image --no-server -cp build/libs/bookinventory-*-all.jar

FROM frolvlad/alpine-glibc
RUN apk update && apk add libstdc++
EXPOSE 8080
COPY --from=graalvm /home/app/bookinventory/bookinventory /app/bookinventory
ENTRYPOINT ["/app/bookinventory"]

Add a script `docker-build.sh to run it:

#!/bin/sh
docker build . -t bookinventory
echo
echo
echo "To run the docker container execute:"
echo "    $ docker run -p 8080:8080 bookinventory"

Changes to bookrecommendation to generate a GraalVM native image with Docker

Replace bookrecommendation/Dockerfile with this content:

FROM oracle/graalvm-ce:20.1.0-java8 as graalvm
RUN gu install native-image

COPY . /home/app/bookrecommendation
WORKDIR /home/app/bookrecommendation

RUN native-image --no-server -cp build/libs/bookrecommendation-*-all.jar

FROM frolvlad/alpine-glibc
RUN apk update && apk add libstdc++
EXPOSE 8080
COPY --from=graalvm /home/app/bookrecommendation/bookrecommendation /app/bookrecommendation
ENTRYPOINT ["/app/bookrecommendation"]

Add a script `docker-build.sh to run it:

#!/bin/sh
docker build . -t bookrecommendation
echo
echo
echo "To run the docker container execute:"
echo "    $ docker run -p 8080:8080 bookrecommendation"

Running native images with docker

You can use docker to run the images:

$ docker run -p 8080:8080 bookcatalogue
$ docker run -p 8081:8081 bookinventory
$ docker run -p 8081:8081 bookrecommendation

6 Next Steps

Read more about RabbitMQ RPC support inside Micronaut.

7 Help with Micronaut

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Micronaut.

Meet the Team