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. to 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 all these beers 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:

  • Maven

  • Quarkus CLI

mvn quarkus:add-extension -Dextension=quarkus-resteasy-mutiny
quarkus extension add quarkus-resteasy-mutiny

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 jakarta.json.bind.annotation.JsonbCreator;
import java.math.BigDecimal;

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 jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.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 jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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)
            .select().where(b -> b.getAbv() > 15.0); (6)
    }

}
1 Creates a Multi.
2 The supplier will start with 1 and will query the remote endpoint 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 your new implementation 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."
  }
]