Consul and the Micronaut Framework - Microservices Service Discovery
Use Consul service discovery to expose your Micronaut applications.
Authors: Sergio del Amo
Micronaut Version: 4.6.3
1. Getting Started
In this guide, we will create three microservices and register them with Consul Service discovery.
Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud
You will discover how the Micronaut framework eases Consul integration.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE (e.g. IntelliJ IDEA)
-
JDK 17 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the App
Let’s describe the microservices you will build through the guide.
-
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 fulfil an order. It 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.
Initially we will hard-code the addresses where the different services are in the bookcatalogue
service.
As shown in the previous image, the bookcatalogue
hardcodes references to its collaborators.
In the second part of this guide we will use a discovery service.
About registration patterns We will use a self‑registration pattern. Thus, each service instance is responsible for registering and deregistering itself with the service registry. Also, if required, a service instance sends heartbeat requests to prevent its registration from expiring. |
Services register when they start up:
We will use client‑side service discovery, clients query the service registry, select an available instance, and make a request.
4.1. Catalogue Microservice
Create the bookcatalogue
microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app --features=discovery-consul,management,graalvm example.micronaut.bookcatalogue --build=maven --lang=java
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-consul
, management
, and graalvm
features.
The previous command creates a directory named bookcatalogue
and a Micronaut application inside it with default package example.micronaut
.
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create a BooksController
class to handle incoming HTTP requests into the bookcatalogue
microservice:
package example.micronaut;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import java.util.Arrays;
import java.util.List;
@Controller("/books") (1)
public class BooksController {
@Get (2)
public List<Book> index() {
return Arrays.asList(
new Book("1491950358", "Building Microservices"),
new Book("1680502395", "Release It!"),
new Book("0321601912", "Continuous Delivery:")
);
}
}
1 | The class is defined as a controller with the @Controller annotation mapped to the path / . |
2 | The @Get annotation maps the index method to an HTTP GET request on /books . |
The previous controller responds a List<Book>
. Create the Book
POJO:
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
@Serdeable
public class Book {
@NonNull
@NotBlank
private final String isbn;
@NonNull
@NotBlank
private final String name;
public Book(@NonNull @NotBlank String isbn,
@NonNull @NotBlank String name) {
this.isbn = isbn;
this.name = name;
}
@NonNull
public String getIsbn() {
return isbn;
}
@NonNull
public String getName() {
return 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);
}
}
Write a test:
package example.micronaut;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@MicronautTest (1)
public class BooksControllerTest {
@Inject
@Client("/")
HttpClient client; (2)
@Test
public void testRetrieveBooks() {
HttpRequest<?> request = HttpRequest.GET("/books"); (3)
List<Book> books = client.toBlocking().retrieve(request, Argument.listOf(Book.class)); (4)
assertEquals(3, books.size());
assertTrue(books.contains(new Book("1491950358", "Building Microservices")));
assertTrue(books.contains(new Book("1680502395", "Release It!")));
}
}
1 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
2 | Inject the HttpClient bean and point it to the embedded server. |
3 | Creating HTTP Requests is easy thanks to the Micronaut framework fluid API. |
4 | Parse easily JSON into Java objects. |
Edit application.yml
micronaut:
application:
name: bookcatalogue (1)
1 | Configure the application name. The application name will be used by the discovery service. |
Modify the Application
class to use dev
as a default environment:
The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.
package example.micronaut;
import io.micronaut.runtime.Micronaut;
import static io.micronaut.context.env.Environment.DEVELOPMENT;
public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application.class)
.defaultEnvironments(DEVELOPMENT)
.start();
}
}
Create src/main/resources/application-dev.yml
. The Micronaut framework applies this configuration file only for the dev
environment.
micronaut:
server:
port: 8081 (1)
1 | Configure the application to listen on port 8081 |
Create a file named application-test.yml
which is used in the test environment:
consul:
client:
registration:
enabled: false
Run the unit test:
./mvnw test
4.2. Inventory Microservice
Create the bookinventory
microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app --features=discovery-consul,management,graalvm example.micronaut.bookinventory --build=maven --lang=java
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-consul
, management
, and graalvm
features.
The previous command creates a directory named bookinventory
and a Micronaut application inside it with default package example.micronaut
.
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create a Controller:
package example.micronaut;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import jakarta.validation.constraints.NotBlank;
import java.util.Optional;
import static io.micronaut.http.MediaType.TEXT_PLAIN;
@Controller("/books") (1)
public class BooksController {
@Produces(TEXT_PLAIN) (2)
@Get("/stock/{isbn}") (3)
public Boolean stock(@NotBlank String isbn) { (1)
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));
}
if (isbn.equals("1680502395")) {
return Optional.of(new BookInventory(isbn, 0));
}
return Optional.empty();
}
}
1 | The class is defined as a controller with the @Controller annotation mapped to the path /books . |
2 | By default, Content-Type of a controller response is application/json : override this with text/plain since we are returning a String, not a JSON object. |
3 | The @Get annotation maps the index method to an HTTP GET request on /books/stock/{isbn} . |
Create the POJO used by the controller:
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
@Serdeable
public class BookInventory {
@NonNull
@NotBlank
private final String isbn;
private final int stock;
public BookInventory(@NonNull @NotBlank String isbn,
int stock) {
this.isbn = isbn;
this.stock = stock;
}
@NonNull
public String getIsbn() {
return isbn;
}
public int getStock() {
return 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 stock == that.stock && Objects.equals(isbn, that.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn, stock);
}
}
Write a test:
package example.micronaut;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import static io.micronaut.http.HttpStatus.NOT_FOUND;
import static io.micronaut.http.HttpStatus.OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@MicronautTest
public class BooksControllerTest {
@Inject
@Client("/")
HttpClient httpClient;
@Test
public void testBooksController() {
HttpResponse<Boolean> rsp = httpClient.toBlocking().exchange(
HttpRequest.GET("/books/stock/1491950358"), Boolean.class);
assertEquals(OK, rsp.status());
assertTrue(rsp.body());
}
@Test
public void testBooksControllerWithNonExistingIsbn() {
HttpClientResponseException thrown = assertThrows(HttpClientResponseException.class, () -> {
httpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/XXXXX"), Boolean.class);
});
assertEquals(NOT_FOUND, thrown.getResponse().getStatus());
}
}
Edit application.yml
micronaut:
application:
name: bookinventory (1)
1 | Configure the application name. The name will be used later in the guide. |
Modify the Application
class to use dev
as a default environment:
The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.
package example.micronaut;
import io.micronaut.runtime.Micronaut;
import static io.micronaut.context.env.Environment.DEVELOPMENT;
public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application.class)
.defaultEnvironments(DEVELOPMENT)
.start();
}
}
Create src/main/resources/application-dev.yml
. The Micronaut framework applies this configuration file only for the dev
environment.
micronaut:
server:
port: 8082 (1)
1 | Configure the application to listen on port 8082 |
Create a file named application-test.yml
which is used in the test environment:
consul:
client:
registration:
enabled: false
Run the unit test:
./mvnw test
4.3. Recommendation Microservice
Create the bookrecommendation
microservice using the Micronaut Command Line Interface or with Micronaut Launch.
mn create-app --features=discovery-consul,management,reactor,graalvm example.micronaut.bookrecommendation --build=maven --lang=java
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-consul
, management
, reactor
, and graalvm
features.
The previous command creates a directory named bookrecommendation
and a Micronaut application inside it with default package example.micronaut
.
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
Create an interface to map operations with bookcatalogue
, and a Micronaut Declarative HTTP Client to consume it.
package example.micronaut;
import org.reactivestreams.Publisher;
public interface BookCatalogueOperations {
Publisher<Book> findAll();
}
package example.micronaut;
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 = BookCatalogueOperations.class)
interface BookCatalogueClient extends BookCatalogueOperations {
@Get("/books")
Publisher<Book> findAll();
}
1 | Use @Client to use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use the id member to provide a service identifier or specify the URL directly as the annotation’s value. |
The client returns a POJO. Create it in the bookrecommendation
:
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotBlank;
import java.util.Objects;
@Serdeable
public class Book {
@NonNull
@NotBlank
private final String isbn;
@NonNull
@NotBlank
private final String name;
public Book(@NonNull @NotBlank String isbn,
@NonNull @NotBlank String name) {
this.isbn = isbn;
this.name = name;
}
@NonNull
public String getIsbn() {
return isbn;
}
@NonNull
public String getName() {
return 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);
}
}
Create an interface to map operations with bookinventory
, and a Micronaut Declarative HTTP Client to consume it.
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import reactor.core.publisher.Mono;
import jakarta.validation.constraints.NotBlank;
public interface BookInventoryOperations {
Mono<Boolean> stock(@NonNull @NotBlank String isbn);
}
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import reactor.core.publisher.Mono;
import jakarta.validation.constraints.NotBlank;
import static io.micronaut.http.MediaType.TEXT_PLAIN;
@Client("http://localhost:8082") (1)
@Recoverable(api = BookInventoryOperations.class)
interface BookInventoryClient extends BookInventoryOperations {
@Consumes(TEXT_PLAIN)
@Get("/books/stock/{isbn}")
Mono<Boolean> stock(@NonNull @NotBlank String isbn);
}
1 | Use @Client to use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use the id member to provide a service identifier or specify the URL directly as the annotation’s value. |
Create a Controller which injects both clients.
package example.micronaut;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@Controller("/books") (1)
public class BookController {
private final BookCatalogueOperations bookCatalogueOperations;
private final BookInventoryOperations bookInventoryOperations;
public BookController(BookCatalogueOperations bookCatalogueOperations,
BookInventoryOperations bookInventoryOperations) { (2)
this.bookCatalogueOperations = bookCatalogueOperations;
this.bookInventoryOperations = bookInventoryOperations;
}
@Get (3)
public Publisher<BookRecommendation> index() {
return Flux.from(bookCatalogueOperations.findAll())
.flatMap(b -> Flux.from(bookInventoryOperations.stock(b.getIsbn()))
.filter(Boolean::booleanValue)
.map(rsp -> b)
).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 maps the index method to an HTTP GET request on /books . |
The previous controller returns a Publisher<BookRecommendation>
. Create the BookRecommendation
POJO:
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotBlank;
@Serdeable
public class BookRecommendation {
@NonNull
@NotBlank
private final String name;
public BookRecommendation(@NonNull @NotBlank String name) {
this.name = name;
}
@NonNull
public String getName() {
return 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 name.equals(that.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
BookCatalogueClient
and BookInventoryClient
will fail to consume the bookcatalogue
and bookinventory
during the tests phase.
Using the @Fallback annotation you can declare a fallback implementation of a client that will be picked up and used once all possible retries have been exhausted
Create @Fallback
alternatives in the test
classpath.
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.retry.annotation.Fallback;
import jakarta.inject.Singleton;
import reactor.core.publisher.Mono;
import jakarta.validation.constraints.NotBlank;
import static io.micronaut.context.env.Environment.TEST;
@Requires(env = TEST) (1)
@Fallback
@Singleton
public class BookInventoryClientStub implements BookInventoryOperations {
@Override
public Mono<Boolean> stock(@NonNull @NotBlank String isbn) {
if (isbn.equals("1491950358")) {
return Mono.just(true); (2)
}
if (isbn.equals("1680502395")) {
return Mono.just(false); (3)
}
return Mono.empty(); (4)
}
}
1 | Make this fallback class to be effective only when the Micronaut environment TEST is active |
2 | Here we arbitrarily decided that if everything else fails, that book’s stock would be true |
3 | Similarly, we decided that other book’s stock method would be false |
4 | Finally, any other book will have their stock method return an empty value |
package example.micronaut;
import io.micronaut.context.annotation.Requires;
import io.micronaut.retry.annotation.Fallback;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import static io.micronaut.context.env.Environment.TEST;
@Requires(env = TEST)
@Fallback
@Singleton
public class BookCatalogueClientStub implements BookCatalogueOperations {
@Override
public Publisher<Book> findAll() {
Book buildingMicroservices = new Book("1491950358", "Building Microservices");
Book releaseIt = new Book("1680502395", "Release It!");
return Flux.just(buildingMicroservices, releaseIt);
}
}
Write a test:
package example.micronaut;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import reactor.core.publisher.Flux;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest
public class BookControllerTest {
@Inject
@Client("/")
HttpClient client;
@DisabledIfEnvironmentVariable(named = "CI", matches = "true")
@Test
public void testRetrieveBooks() {
List<BookRecommendation> books = client.toBlocking()
.retrieve(HttpRequest.GET("/books"), Argument.listOf(BookRecommendation.class));
assertEquals(1, books.size());
assertEquals("Building Microservices", books.get(0).getName());
}
}
Edit application.yml
micronaut:
application:
name: bookrecommendation (1)
1 | Configure the application name. The name will be used later in the guide. |
Modify the Application
class to use dev
as a default environment:
The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.
package example.micronaut;
import io.micronaut.runtime.Micronaut;
import static io.micronaut.context.env.Environment.DEVELOPMENT;
public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.mainClass(Application.class)
.defaultEnvironments(DEVELOPMENT)
.start();
}
}
Create src/main/resources/application-dev.yml
. The Micronaut framework applies this configuration file only for the dev
environment.
micronaut:
server:
port: 8080 (1)
1 | Configure the application to listen on port 8080 |
Create a file named application-test.yml
which is used in the test environment:
consul:
client:
registration:
enabled: false
Run the unit test:
./mvnw test
4.4. Running the application
Run bookcatalogue
microservice:
./mvnw mn:run
14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081
Run bookinventory
microservice:
./mvnw mn:run
14:31:13.104 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082
Run bookrecommendation
microservice:
./mvnw mn:run
14:31:57.389 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 523ms. 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. Consul and the Micronaut framework
5.1. Install Consul via Docker
The quickest way to start using Consul is via Docker:
docker run -p 8500:8500 consul
Alternatively you can install and run a local Consul instance.
The following screenshots show how to install/run Consul via Kitematic, a UI for Docker.
Configure ports:
5.2. Book Catalogue
Append to bookcatalogue
service application.yml
the following snippet:
consul:
client:
registration:
enabled: true
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
This configuration registers a Micronaut application with Consul with minimal configuration. Discover a more complete list of configuration options at ConsulConfiguration.
5.3. Book Inventory
Modify the application.yml
of the bookinventory
application with the following snippet:
consul:
client:
registration:
enabled: true
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
5.4. Book Recommendation
Append to bookrecommendation
.application.yml
the following snippet:
consul:
client:
registration:
enabled: true
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
Modify BookInventoryClient
and BookCatalogueClient
to use the service id instead of a hardcoded URL.
package example.micronaut;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import org.reactivestreams.Publisher;
@Client(id = "bookcatalogue") (1)
@Recoverable(api = BookCatalogueOperations.class)
interface BookCatalogueClient extends BookCatalogueOperations {
@Get("/books")
Publisher<Book> findAll();
}
1 | Use the configuration value micronaut.application.name used in bookcatalogue as service id . |
package example.micronaut;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import reactor.core.publisher.Mono;
import jakarta.validation.constraints.NotBlank;
import static io.micronaut.http.MediaType.TEXT_PLAIN;
@Client(id = "bookinventory") (1)
@Recoverable(api = BookInventoryOperations.class)
interface BookInventoryClient extends BookInventoryOperations {
@Consumes(TEXT_PLAIN)
@Get("/books/stock/{isbn}")
Mono<Boolean> stock(@NonNull @NotBlank String isbn);
}
1 | Use the configuration value micronaut.application.name used in bookinventory as service id . |
5.5. Running the App
Run bookcatalogue
microservice:
./gradlew run
14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081
14:28:34.084 [nioEventLoopGroup-1-3] INFO i.m.d.registration.AutoRegistration - Registered service [bookcatalogue] with Consul
Run bookinventory
microservice:
./gradlew run
14:31:13.104 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082
14:31:13.154 [nioEventLoopGroup-1-3] INFO i.m.d.registration.AutoRegistration - Registered service [bookinventory] with Consul
Run bookrecommendation
microservice:
./gradlew run
14:31:57.389 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080
14:31:57.439 [nioEventLoopGroup-1-3] INFO i.m.d.registration.AutoRegistration - Registered service [bookrecommendation] with Consul
Consul comes with a HTML UI. Open http://localhost:8500/ui in your browser.
You will see the services registered in Consul:
You can run a cURL command to test the whole application:
curl http://localhost:8080/books
[{"name":"Building Microservices"}]
6. Generate a Micronaut Application Native Executable with GraalVM
We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Micronaut application.
Compiling native executables ahead of time with GraalVM improves startup time and reduces the memory footprint of JVM-based applications.
Only Java and Kotlin projects support using GraalVM’s native-image tool. Groovy relies heavily on reflection, which is only partially supported by GraalVM.
|
6.1. GraalVM installation
sdk install java 21.0.5-graal
sdk use java 21.0.5-graal
For installation on Windows, or for manual installation on Linux or Mac, see the GraalVM Getting Started documentation.
The previous command installs Oracle GraalVM, which is free to use in production and free to redistribute, at no cost, under the GraalVM Free Terms and Conditions.
Alternatively, you can use the GraalVM Community Edition:
sdk install java 21.0.2-graalce
sdk use java 21.0.2-graalce
6.2. Native executable generation
To generate a native executable using Maven, run:
./mvnw package -Dpackaging=native-image
The native executable is created in the target
directory and can be run with target/micronautguide
.
Start the native executables for the three microservices and run the same curl
request as before to check that everything works with GraalVM.
7. Next steps
Read more about Consul support in the Micronaut framework.
8. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.
9. License
All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…). |