Consul and Micronaut - Microservices service discovery

Use Consul service discovery to expose your Micronaut apps.

Authors: Sergio del Amo

Micronaut Version: 1.0.0.M4

1 Getting Started

In this guide, we are going to 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 Micronaut eases Consul integration.

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 fullfil an order. I uses a domain consisting of a stock level and isbn.

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

Initially we are going to hardcode the addresses where the different services are in the bookcatalogue service.

hardcoded

As shown in the previous image, the bookcatalogue hardcodes references to its collaborators.

In the second part of this tutorial we are going to use a discovery service.

o learn 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:

discovery service registration

We will use client‑side service discovery, clients query the service registry, select an available instance, and make a request.

discovery service flow

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.

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

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

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

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

    @Get("/") (2)
    List<Book> index() {
        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 The class is defined as a controller with the @Controller annotation mapped to the path /books
2 The @Get annotation is used to map the index method to /books requests that use an HTTP GET.
3 You can return reactive types from your controller methods.

The previous controller responds a Flowable<Book>. Create the Book POJO:

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

import java.util.Objects;

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 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:

bookcatalogue/src/test/java/example/micronaut/bookcatalogue/BooksControllerTest.java
package example.micronaut.bookcatalogue;

import io.micronaut.context.ApplicationContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.RxStreamingHttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class BooksControllerTest {
    private static EmbeddedServer server;
    private static HttpClient client;

    @BeforeClass (1)
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class);
        client = server
                .getApplicationContext()
                .createBean(HttpClient.class, server.getURL());
    }

    @AfterClass (1)
    public static void stopServer() {
        if(server != null) {
            server.stop();
        }
        if(client != null) {
            client.stop();
        }
    }

    @Test
    public void testRetrieveBooks() {
        HttpRequest request = HttpRequest.GET("/books"); (2)
        List books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class)); (3)
        assertEquals(3, books.size());
        assertTrue(books.contains(new Book("1491950358", "Building Microservices")));
        assertTrue(books.contains(new Book("1680502395", "Release It!")));
    }
}
1 For JUnit you can write methods to start and stop the server for the scope of the test
2 It is easy to create HTTP requests with a fluid API.
3 Parse easily JSON into Java objects.

Edit application.yml

bookcatalogue/src/main/resources/application.yml
micronaut:
    application:
        name: bookcatalogue (1)
    server:
        port: 8081 (2)
1 Configure the application name. The app name will be use by the discovery service.
2 Configure the app to listen at port 8081

Create a file named application-test.yml which is used in the test environment:

bookcatalogue/src/main/resources/application-test.yml
micronaut:
    server:
        port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

2.2 Inventory Microservice

Create the bookinventory microservice:

mn create-app example.micronaut.bookinventory.bookinventory

Modify build.gradle to add validation capabilities.

bookinventory/build.gradle
    compile "io.micronaut:validation"
    compile "io.micronaut.configuration:hibernate-validator"

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

Create a Controller:

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

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.validation.Validated;
import javax.validation.constraints.NotBlank;
import java.util.Optional;

@Validated (1)
@Controller("/books") (2)
public class BooksController {

    @Produces(MediaType.TEXT_PLAIN) (3)
    @Get("/stock/{isbn}") (4)
    public Boolean stock(@NotBlank String isbn) {
        Optional<BookInventory> bookInventoryOptional = bookInventoryByIsbn(isbn);
        if (!bookInventoryOptional.isPresent()) {
           return null; (5)
        }
        BookInventory bookInventory = bookInventoryOptional.get();
        return bookInventory.getStock() > 0 ? Boolean.TRUE : Boolean.FALSE;
    }

    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 Define Validated annotation at the class level to signal any class that requires validation.
2 The class is defined as a controller with the @Controller annotation mapped to the path /books
3 By default a Micronaut’s response uses application/json as Content-Type. We are returning a String not a JSON object. Because of that, we set it to text/plain.
4 The @Get annotation is used to map the index method to /books/stock/{isbn} requests that use an HTTP GET.

The previous controller uses a POJO:

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

import java.util.Objects;

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);
    }
}

Write a test:

bookinventory/src/test/java/example/micronaut/bookinventory/BooksControllerTest.java
package example.micronaut.bookinventory;

import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class BooksControllerTest {

    private static EmbeddedServer server;
    private static RxHttpClient rxHttpClient;

    @BeforeClass (1)
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class);
        rxHttpClient = server
                .getApplicationContext()
                .createBean(RxHttpClient.class, server.getURL());
    }

    @AfterClass (1)
    public static void stopServer() {
        if (server != null) {
            server.stop();
        }
        if (rxHttpClient != null) {
            rxHttpClient.stop();
        }
    }

    @Test
    public void testBooksController() {
        boolean noExceptionThrown = true;
        HttpResponse<Boolean> rsp = null;
        try {
            rsp = rxHttpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/1491950358"), Boolean.class);
        } catch (HttpClientResponseException e) {
            noExceptionThrown = false;
        }

        assertTrue(noExceptionThrown);
        assertEquals(rsp.status(), HttpStatus.OK);
        assertTrue(rsp.body());

    }

    @Test
    public void testBooksControllerWithNonExistingIsbn() {
        HttpClientResponseException ex = null;
        boolean noExceptionThrown = true;
        try {
            rxHttpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/XXXXX"), Boolean.class);
        } catch (HttpClientResponseException e) {
            noExceptionThrown = false;
            ex = e;
        }

        assertFalse(noExceptionThrown);

        HttpResponse response = ex.getResponse();

        assertEquals(response.getStatus(), HttpStatus.NOT_FOUND);

    }
}
1 For JUnit you can write methods to start and stop the server for the scope of the test

Edit application.yml

bookinventory/src/main/resources/application.yml
micronaut:
    application:
        name: bookinventory (1)
    server:
        port: 8082 (2)
1 Configure the application name. The app name will be used later in the tutorial.
2 Configure the app to listen at port 8082

Create a file named application-test.yml which is used in the test environment:

bookinventory/src/main/resources/application-test.yml
micronaut:
    server:
        port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

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.

Create an interface to map operations with bookcatalogue and a Micronaut Declarative HTTP Client to consume it.

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

import io.reactivex.Flowable;

public interface BookCatalogueOperations {
    Flowable<Book> findAll();
}
bookrecommendation/src/main/java/example/micronaut/bookrecommendation/BookCatalogueClient.java
package example.micronaut.bookrecommendation;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.Client;
import io.reactivex.Flowable;

@Client("http://localhost:8081") (1)
public interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Flowable<Book> findAll();
}
1 Use @Client to use declarative HTTP Clients

The client returns a POJO. Create it in the bookrecommendation:

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

import java.util.Objects;

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 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.

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

import io.reactivex.Maybe;

import javax.validation.constraints.NotBlank;

public interface BookInventoryOperations {
    Maybe<Boolean> stock(@NotBlank String isbn);
}
bookrecommendation/src/main/java/example/micronaut/bookrecommendation/BookInventoryClient.java
package example.micronaut.bookrecommendation;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.Client;
import io.reactivex.Maybe;

import javax.validation.constraints.NotBlank;

@Client("http://localhost:8082") (1)
public interface BookInventoryClient extends BookInventoryOperations {

    @Get("/books/stock/{isbn}")
    Maybe<Boolean> stock(@NotBlank String isbn);
}
1 Use @Client to use declarative HTTP Clients

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 BookCatalogueOperations bookCatalogueOperations;
    private final BookInventoryOperations bookInventoryOperations;

    public BookController(BookCatalogueOperations bookCatalogueOperations,
                          BookInventoryOperations bookInventoryOperations) { (2)
        this.bookCatalogueOperations = bookCatalogueOperations;
        this.bookInventoryOperations = bookInventoryOperations;
    }


    @Get("/") (3)
    public Flowable<BookRecommendation> index() {
        return bookCatalogueOperations.findAll()
                .flatMapMaybe(b -> 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 Constructor injection
3 The @Get annotation is used to map the index method to /books requests that use an HTTP GET.

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 java.util.Objects;

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);
    }
}

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.

bookrecommendation/src/test/java/example/micronaut/bookrecommendation/BookInventoryClientStub.java
package example.micronaut.bookrecommendation;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.retry.annotation.Fallback;
import io.reactivex.Maybe;
import javax.inject.Singleton;
import javax.validation.constraints.NotBlank;

@Requires(env = Environment.TEST)
@Fallback
@Singleton
public class BookInventoryClientStub implements BookInventoryOperations {

    @Override
    public Maybe<Boolean> stock(@NotBlank String isbn) {
        if(isbn.equals("1491950358")) {
            return Maybe.just(Boolean.TRUE);

        } else if(isbn.equals("1680502395")) {
            return Maybe.just(Boolean.FALSE);
        }
        return Maybe.empty();
    }
}
bookrecommendation/src/test/java/example/micronaut/bookrecommendation/BookCatalogueClientStub.java
package example.micronaut.bookrecommendation;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.retry.annotation.Fallback;
import io.reactivex.Flowable;

import javax.inject.Singleton;

@Requires(env = Environment.TEST)
@Fallback
@Singleton
public class BookCatalogueClientStub implements BookCatalogueOperations {

    @Override
    public Flowable<Book> findAll() {
        Book buildingMicroservices = new Book("1491950358", "Building Microservices");
        Book releaseIt = new Book("1680502395", "Release It!");
        return Flowable.just(buildingMicroservices, releaseIt);    }
}

Write a test:

bookrecommendation/src/test/java/example/micronaut/bookrecommendation/BookControllerTest.java
package example.micronaut.bookrecommendation;

import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.RxStreamingHttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import io.reactivex.Flowable;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Collections;
import static org.junit.Assert.assertEquals;

public class BookControllerTest {
    private static EmbeddedServer server;
    private static RxStreamingHttpClient client;

    @BeforeClass (1)
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class);
        client = server
                .getApplicationContext()
                .createBean(RxStreamingHttpClient.class, server.getURL());
    }

    @AfterClass (1)
    public static void stopServer() {
        if(server != null) {
            server.stop();
        }
        if(client != null) {
            client.stop();
        }
    }

    @Test
    public void testRetrieveBooks() throws Exception {
        Flowable<BookRecommendation> books = client.jsonStream(HttpRequest.GET("/books"), BookRecommendation.class);
        assertEquals(books.toList().blockingGet().size(), 1);
        assertEquals(books.toList().blockingGet().get(0).getName(), "Building Microservices");
    }
}
1 For JUnit you can write methods to start and stop the server for the scope of the test

Edit application.yml

bookrecommendation/src/main/resources/application.yml
micronaut:
    application:
        name: bookrecommendation (1)
    server:
        port: 8080 (2)
1 Configure the application name. The app name will be used later in the tutorial.
2 Configure the app to listen at port 8080

Create a file named application-test.yml which is used in the test environment:

bookrecommendation/src/main/resources/application-test.yml
micronaut:
    server:
        port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

3 Running the app

Run bookcatalogue microservice:

bookcatalogue $ ./gradlew run
 Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

 > Task :bookcatalogue:compileJava
 Note: Creating bean classes for 1 type elements

 > Task :bookcatalogue:run
 14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081

Run bookinventory microservice:

bookinventory $ ./gradlew run
 Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

 > Task :bookinventory:compileJava
 Note: Creating bean classes for 1 type elements

 > Task :bookcatalogue:run
 14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8082

Run bookrecommendation microservice:

bookrecommendation $ ./gradlew run
Starting a Gradle Daemon, 2 busy and 2 stopped Daemons could not be reused, use --status for details

> Task :bookrecommendation:compileJava
Note: Creating bean classes for 3 type elements

> Task :bookrecommendation: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"}

4 Consul and Micronaut

4.1 Install Consul via Docker

The quickest way to start using Consul is via Docker:

docker run -p 8500:8500 consul

The following screenshots show how to install/run Consul via Kitematic; graphical user interface for Docker.

kitematic consul 1

Configure ports:

kitematic consul 2

4.2 Integrate Consul

4.3 Book Catalogue

Modify build.gradle to add discovery-client dependency.

bookcatalogue/build.gradle
dependencies {
    ...
    ..
    .
    runtime "io.micronaut:discovery-client"
}

Append to bookcatalogue service application.yml the following snippet:

bookcatalogue/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Previous configuration registers a Micronaut app with Consul with minimal configuration. Discover a more complete list of Configuration options at ConsulConfiguration.

Disable consul registration in tests:

bookcatalogue/src/main/resources/application-test.yml
consul:
    client:
        registration:
            enabled: false

4.4 Book Inventory

Modify build.gradle to add discovery-client dependency.

bookinventory/build.gradle
dependencies {
    ...
    ..
    .
    runtime "io.micronaut:discovery-client"
}

Also, modify the application.yml of the bookinventory application with the following snippet:

bookinventory/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Disable consul registration in tests:

bookinventory/src/main/resources/application-test.yml
consul:
    client:
        registration:
            enabled: false

4.5 Book Recommendation

Modify build.gradle to add discovery-client dependency.

bookrecommendation/build.gradle
dependencies {
    ...
    ..
    .
    runtime "io.micronaut:discovery-client"
}

Also, append to bookrecommendation.application.yml the following snippet:

bookrecommendation/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Modify BookInventoryClient and BookCatalogueClient to use the service id instead of a harcoded ip.

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

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.Client;
import io.reactivex.Flowable;

@Client(id = "bookcatalogue") (1)
public interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Flowable<Book> findAll();
}
1 Use the configuration value micronaut.application.name used in bookcatalogue as service id.
bookrecommendation/src/main/java/example/micronaut/bookrecommendation/BookInventoryClient.java
package example.micronaut.bookrecommendation;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.Client;
import io.reactivex.Maybe;

import javax.validation.constraints.NotBlank;

@Client(id = "bookinventory") (1)
public interface BookInventoryClient extends BookInventoryOperations {

    @Get("/books/stock/{isbn}")
    Maybe<Boolean> stock(@NotBlank String isbn);
}
1 Use the configuration value micronaut.application.name used in bookinventory as service id.

Disable consul registration in tests:

bookrecommendation/src/main/resources/application-test.yml
consul:
    client:
        registration:
            enabled: false

4.6 Running the App

Run bookcatalogue microservice:

bookcatalogue $ ./gradlew run
 Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

 > Task :bookcatalogue:compileJava
 Note: Creating bean classes for 1 type elements

 > Task :bookcatalogue:run
 14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081

Run bookinventory microservice:

bookinventory $ ./gradlew run
 Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

 > Task :bookinventory:compileJava
 Note: Creating bean classes for 1 type elements

 > Task :bookcatalogue:run
 14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8082

Run bookrecommendation microservice:

bookrecommendation $ ./gradlew run
Starting a Gradle Daemon, 2 busy and 2 stopped Daemons could not be reused, use --status for details

> Task :bookrecommendation:compileJava
Note: Creating bean classes for 3 type elements

> Task :bookrecommendation:run
14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080

Consul comes with a HTML UI. Open http://localhost:8500/ui in your browser.

You will see the services registered in Consul:

consului

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

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

5 Next Steps

Read more about Consul support inside Micronaut.