Reactive with Mutiny

Quarkus provides a novel reactive API called Mutiny, with the goal of easing the development of highly scalable, resilient, and asynchronous systems.

In this chapter we’re going to see some examples of how Mutiny changes the design of our Quarkus applications. We’re going to develop an asynchronous REST client that connects to an online beer database (https://punkapi.com/documentation/v2) to retrieve beer information. This API does not return all beers at once, so we’ll need to navigate through the pages to fetch all the information. Then we’re going to filter all the beers with an ABV greater than 15.0 and return these values in a Reactive REST endpoint.

Add the RestEasy Mutiny extension

Just open a new terminal window, and make sure you’re at the root of your tutorial-app project, then run:

./mvnw quarkus:add-extension -Dextensions=io.quarkus:quarkus-resteasy-mutiny
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.redhat.developers:tutorial-app >-----------------
[INFO] Building tutorial-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.4.2.Final:add-extension (default-cli) @ tutorial-app ---
✅ Adding dependency io.quarkus:quarkus-resteasy-mutiny:jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.217 s
[INFO] Finished at: 2020-05-12T09:03:59-04:00
[INFO] ------------------------------------------------------------------------

Create Beer POJO

Create a new Beer Java class in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import javax.json.bind.annotation.JsonbCreator;

public class Beer {

    private String name;

    private String tagline;

    private double abv;

    private Beer(String name, String tagline, double abv) {
      this.name = name;
      this.tagline = tagline;
      this.abv = abv;
    }

    @JsonbCreator
    public static Beer of(String name, String tagline, double abv) {
        return new Beer(name, tagline, abv);
     }

     public String getName() {
         return name;
     }

     public String getTagline() {
         return tagline;
     }

     public double getAbv() {
         return abv;
     }

}

Create BeerService

Now we’re going to implement a Java interface that mimics the remote REST endpoint.

Create a new BeerService Java interface in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/v2")
@RegisterRestClient
public interface BeerService {

    @GET
    @Path("/beers")
    @Produces(MediaType.APPLICATION_JSON)
    List<Beer> getBeers(@QueryParam("page") int page);

}

Configure REST Client properties

Add the following properties to your application.properties in src/main/resources:

com.redhat.developers.BeerService/mp-rest/url=https://api.punkapi.com

Create BeerResource

Create a new BeerResource Java class in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.smallrye.mutiny.Multi;

@Path("/beer")
public class BeerResource {

    @RestClient
    BeerService beerService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Multi<Beer> beers() {
        return Multi.createBy().repeating() (1)
            .supplier( (2)
                () -> new AtomicInteger(1),
                i -> beerService.getBeers(i.getAndIncrement())
            )
            .until(List::isEmpty) (3)
            .onItem().<Beer>disjoint() (4)
            .transform().byFilteringItemsWith(b -> b.getAbv() > 15.0); (6)
    }

}
1 Creates a Multi.
2 The supplier will start with 1 and will query the remote endpoing asking for page i.
3 The multi will end when the beer list returned is empty.
4 We dismember all the returned lists and create a sequence of beers.
5 And then we filter the Multi with beers with ABV > 15.0.

Invoke the endpoint

You can check the your new implementation using a REST client by pointing your browser to http://localhost:8080/beer

You can also run the following command:

curl localhost:8080/beer
[
  {
    "abv": 55,
    "name": "The End Of History",
    "tagline": "The World's Strongest Beer."
  },
  {
    "abv": 16.5,
    "name": "Anarchist Alchemist",
    "tagline": "Triple Hopped Triple Ipa."
  },
  {
    "abv": 15.2,
    "name": "Lumberjack Stout",
    "tagline": "Blueberry Bacon Stout."
  },
  {
    "abv": 18.3,
    "name": "Bowman's Beard - B-Sides",
    "tagline": "English Barley Wine."
  },
  {
    "abv": 41,
    "name": "Sink The Bismarck!",
    "tagline": "IPA For The Dedicated."
  },
  {
    "abv": 16.2,
    "name": "Tokyo*",
    "tagline": "Intergalactic Stout. Rich. Smoky. Fruity."
  },
  {
    "abv": 18,
    "name": "AB:02",
    "tagline": "Triple Dry Hopped Imperial Red Ale."
  },
  {
    "abv": 17.2,
    "name": "Black Tokyo Horizon (w/Nøgne Ø & Mikkeller)",
    "tagline": "Imperial Stout Collaboration."
  },
  {
    "abv": 16.1,
    "name": "Dog D",
    "tagline": "Anniversary Imperial Stout."
  },
  {
    "abv": 32,
    "name": "Tactical Nuclear Penguin",
    "tagline": "Uber Imperial Stout."
  },
  {
    "abv": 16.1,
    "name": "Dog E",
    "tagline": "Ninth Anniversary Imperial Stout."
  },
  {
    "abv": 17,
    "name": "Dog G",
    "tagline": "11th Anniversary Imperial Stout."
  }
]