preface

In the previous article, we implemented a simple Quarkus application and tried to package the Quarkus application to run locally. This article aims to further discuss simple responsive programming in Quarkus.

Responsive programming versus traditional programming

Most programmers are familiar with code that looks like this:

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
Copy the code

In Quarkus, you can use reactive flow Publisher as the return type

@GET
@Produces(MediaType.TEXT_PLAIN)
public Publisher<String> hello() {
return "hello";
}
Copy the code

In this article, we’ll use the Mutiny framework as a tool to talk about reactive programming, but actually using the ReactiveStreams APIs on Java, Java 8 CompletableFuture, and RxJava 2 all achieve the same effect.

Introduction of Mutiny

Mutiny is very different from other reactive programming libraries. It takes a different approach to designing your program. In Mutiny, everything is event-driven: you receive events, and you react to them. This event-driven aspect embraces the asynchronous nature of distributed systems and provides an elegant and precise way to express continuity.

On the front page of Mutiny you can see a snippet of this application, which perfectly expresses Mutiny’s features

Uni<String> request = (...) IfNoItem ().after(ofMillis(100)).failWith(new Exception("💥")).onFailure().recoverWithitem (fail) - > "📦") / / sub program and print run. The subscribe () with (item - > log (" 👍 + item) ");Copy the code

Mutiny is also the primary model for writing reactive applications using Quarkus.

The use of Mutiny

Most of Quarkus’s reactive programming has moved away from reactive and towards relying on Mutiny. You can also explicitly add the Quarkus-Mutiny dependency

mvn quarkus:add-extension -Dextensions=mutiny

Or explicitly introduce dependencies using POM files:

<dependency> 
  <groupId>io.quarkus</groupId> 
  <artifactId>quarkus-mutiny</artifactId>
</dependency>
Copy the code

After quoting, simply run a sample:

public static void main(String[] args) {
    Uni.createFrom().item("hello")
            .onItem().transform(item -> item + " mutiny")
            .onItem().transform(String::toUpperCase)
            .subscribe().with(item -> System.out.println(">> " + item));
}
Copy the code

To briefly discuss how this message is built, we build a processing pipe that receives an item, processes the item, and ultimately consumes it.

First, we created a Uni, which is one of the two types provided by Mutiny. Uni generates a message or sends 0 messages on failure.

We create a Uni that emits “hello” content. This is the input to our pipeline. Then we deal with this item:

Then we add “Mutiny” and convert to uppercase string.

That’s the processing part of our pipe, and then finally subscribed to the previous pipe and counted out the data: HELLO MUTINY

The last part is very important. If you don’t subscribe, nothing happens. The Mutiny type is inert, which means you need to do some triggering action. If you don’t, the calculation won’t start.

If it’s not running, pay attention to whether you subscribe or not!

Mutiny and Quarkus

Just now, we have introduced the Quarkus-Mutiny package in POM, which can directly run our program. This time, our goal is to use Quarkus and Mutiny to make a simple weather forecast system, and return the weather forecast value through the restful interface:

Set the query database to return the city code

First add the dependency to the POM file

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-mysql</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-spring-data-jpa</artifactId>
        </dependency>
Copy the code

Then create the entity object:

@Entity(name = "t_city")
public class TCity {

    @Id
    private Integer id;
    @Column
    private String name;
    @Column
    private String code;
}
Copy the code

Configure database connections in application.properties:

quarkus.datasource.url=jdbc:mysql://localhost:3306/test? serverTimezone=UTC quarkus.datasource.driver=com.mysql.cj.jdbc.Driver quarkus.datasource.username=root quarkus.datasource.password=123456 quarkus.datasource.min-size=8 quarkus.datasource.max-size=8Copy the code

Then we can use JPA to manipulate the database:

public interface CityMapper extends CrudRepository<TCity, Integer> {

    Optional<TCity> findByName(String name);
}

@Inject
CityMapper cityMapper;


public Optional<TCity> findByName(String name ) {
    return cityMapper.findByName(name );
}

Copy the code

Query code according to the name of the city, can be rewritten to redis cache, if the cache does not exist in the query database.

@ApplicationScoped public class CityService { private static final Logger LOGGER = Logger.getLogger(ReactiveGreetingService.class.getName()); private final Map<String,String> map=new HashMap<>(); @Inject CityMapper cityMapper; public String getCode(String name) { if (map.containsKey(name)){ return map.get(name); } else{ Optional<City> optional= cityMapper.findByName(name); if (! optional.isPresent()) { throw new WebApplicationException("org.reactive.model.City with name of " + name + " does not exist.", 404); } String code =optional.get().getCode(); map.put(name,code); return code; }}}Copy the code

The next step is to call the third party API: First we introduce the POM file

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client</artifactId>
</dependency>

Copy the code

Then write methods that access the Stream based on the url and return it

@ApplicationScoped public class RestClient { private static final Logger LOGGER = Logger.getLogger(RestClient.class.getName()); private static final String weather_url = "http://t.weather.sojson.com/api/weather/city/"; private final Client httpClient; public RestClient() { this.httpClient = ResteasyClientBuilder.newBuilder().build(); } public InputStream getStream(String id) { return httpClient.target(weather_url+id).request( ).get(InputStream.class); }}Copy the code

Weather websites return specific information of weather forecast information according to the city code, so the method of querying the code according to the name was first prepared above

According to the above part of the preparation of processing class, processing output to the front-end interface

@ApplicationScoped public class ReactiveGreetingService { private static final Logger LOGGER = Logger.getLogger(ReactiveGreetingService.class.getName()); @Inject RestClient restClient; @Inject CityService cityService; private static Executor executor = new ForkJoinPool(); public Uni<Response> getOne() { return Uni.createFrom().item(restClient.get()); } public Uni<Response> getWeather(String id) { LOGGER.info("id "+id); return Uni.createFrom().item(restClient.getById(id)); // 1} public Uni<Weather> getByName(String name) {logger. info(" get data "+name); String code= cityService.getCode(name); Logger.info (" request third party data interface below "+code); return Uni.createFrom().item(restClient.getStream(code)) .onItem().transformToUni(this::invokeRemoteGreetingService) .onFailure().recoverWithItem(new Weather()); // 1 } public Uni<Weather> getByCode(String code) { return Uni.createFrom().item(restClient.getStream(code)) .onItem().transformToUni(this::invokeRemoteGreetingService) .onFailure().recoverWithItem(new Weather()); } Uni<Weather> invokeRemoteGreetingService(InputStream inputStream) { return Uni.createFrom().item(inputStream) .emitOn(executor) .onItem().delayIt().by(Duration.ofSeconds(1)) .onItem().transform(s -> { try { return JSONObject.parseObject(s, Weather.class); } catch (IOException e) { e.printStackTrace(); } return new Weather(); }); }}Copy the code

Then introduce swagger dependency in POM

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
Copy the code

Add relevant comments to your code:

@path ("weather") @tag (name = "WeatherResource",description = "Get weather forecast ") public class ReactiveGreetingResource {@inject ReactiveGreetingService service; @get @produces (MediaType.TEXT_PLAIN) @operation (summary = "GET weather forecast ", Public Uni<Response> getOne() {return service.getone (); } @get@produces (MediaType.TEXT_PLAIN) @path ("/getByName/{name}") @operation (summary = "city name weather forecast ", Public Uni<Weather> getByName(@pathParam ("name") String name) {return service.getByName(name); } @get@produces (MediaType.TEXT_PLAIN) @path ("/getByCode/{code}") @operation (summary = "city code weather forecast ", Public Uni<Weather> getByCode(@pathParam ("code") String code) {return service.getByCode(code); }}Copy the code

Visit http://localhost:8080/q/swagger-ui/ to see the corresponding documents

Then run it to see the result of the program: