Quarkus订阅(4)🔥

737 阅读3分钟

书接上回,我们认识了Quarkus的依赖注入的特性。这一回,我们了解下订阅的概念。

Subscribe

订阅,在Reactive编程中经常遇见这个概念。

我们先来看一段代码。如下,是一个查询接口,检索所有的品牌信息,并返回。

    @Inject
    BrandInfoRepository brandInfoRepository;

    @Route(path = "/brandInfos", methods = HttpMethod.GET)
    void brandInofs2(RoutingContext rc) {
        brandInfoRepository.findAll()
            .list()
            .subscribe()
            .with(brandInfos -> {
                    LOGGER.info("size:{}", brandInfos.size());
                    handleJson(rc, brandInfos);
                },
                t -> handleFail(rc, t));
    }

而其中的subscribe()方法就是订阅。我们将上面的链式写法的代码铺开。

    @Inject
    BrandInfoRepository brandInfoRepository;

    @Route(path = "/brandInfos", methods = HttpMethod.GET)
    void brandInfos(RoutingContext rc) {
        PanacheQuery<BrandInfo> query = brandInfoRepository.findAll();
        Uni<List<BrandInfo>> listUni = query.list();
        UniSubscribe<List<BrandInfo>> uniSubscribe = listUni.subscribe();
        Consumer<List<BrandInfo>> listConsumer = brandInfos -> {
            LOGGER.info("btandInfos size: {}", brandInfos.size());
            handleJson(rc, brandInfos);
        };
        Consumer<Throwable> throwableConsumer = t -> handleFail(rc, t);
        uniSubscribe.with(listConsumer, throwableConsumer);
    }
  1. 第1行代码返回PanacheQuery,这只是在组建有效的查询条件,并没有发起查询。
  2. 第2行代码返回Uni,用于接受0或者1个结果的异步操作。
  3. 第3行代码返回UniSubscribe,这是一个订阅。(我们订阅了Uni,触发计算)
  4. 第4-7行代码返回Consumer,成功时的处理方法。
  5. 第8行代码返回Consumer,失败时的处理方法。
  6. 第9行代码调用.with方法,告知成功时和失败时的处理方法。

Uni

Uni允许接受0或者1个异步结果。


/**
 * A {@link Uni} represents a lazy asynchronous action. It follows the subscription pattern, meaning that the action
 * is only triggered once a {@link UniSubscriber} subscribes to the {@link Uni}.
 * <p>
 * A {@link Uni} can have two outcomes:
 * <ol>
 * <li>An {@code item} event, forwarding the completion of the action (potentially {@code null} if the item
 * does not represent a value, but the action was completed successfully)</li>
 * <li>A {@code failure} event, forwarding an exception</li>
 * </ol>
 * <p>
 * To trigger the computation, a {@link UniSubscriber} must subscribe to the Uni. It will be notified of the outcome
 * once there is an {@code item} or {@code failure} event fired by the observed Uni. A subscriber receives
 * (asynchronously) a {@link UniSubscription} and can cancel the demand at any time. Note that cancelling after
 * having received the outcome is a no-op.
 * <p>
 *
 * @param <T> the type of item produced by the {@link Uni}
 */
public interface Uni<T> {}

我们并不知道Uni到底返回的结果是0个还是1个。而Uni也是Quarkus最常用的异步返回结果。

  1. Uni是一个惰性的异步操作,遵循订阅模式。
  2. 只能触发一次订阅。
  3. 只有两种结果,一是返回了item代表操作完成;另外是返回了一个异常。
  4. 要触发计算,那必须订阅Uni。

返回一个元素

Uni<BrandInfo> findOne(String id);

返回一个集合

Uni<List<BrandInfo>> findAll();

后续操作

QuarkusUni提供了丰富的方法来进行后续的处理,包含的API大致分为创建数据事件方法转换方法

常用的后续操作一般有两种:item 和 subscribe

Item

    @Route(path = "/brandInfos3", methods = HttpMethod.GET)
    Uni<List<BrandInfo>> brandInfos3(RoutingContext rc) {
        return brandInfoRepository.findAll().list()
            // 成功拿到非空的Item
            .onItem().ifNotNull().transformToUni(entity -> {
                return Uni.createFrom().item(entity);
            })
            // 如果Item为空,那么fail
            .onItem().ifNull().fail();
    }

如果能拿到Item,那么直接进行后续的操作。

subscribe

    @Route(path = "/brandInfos2", methods = HttpMethod.GET)
    void brandInofs2(RoutingContext rc) {
        brandInfoRepository.findAll()
            .list()
            .subscribe()
            .with(brandInfos -> {
                    LOGGER.info("size:{}", brandInfos.size());
                    handleJson(rc, brandInfos);
                },
                t -> handleFail(rc, t));
    }

转换为UniSubscribe,采用订阅的模式去处理。

UniSubscribe

UniSubscribe的目的是以订阅的方式去处理异步返回的结果。通常可以将Uni转换为订阅的方式来处理我们的业务代码。


/**
 * Allow subscribing to a {@link Uni} to be notified of the different events coming from {@code upstream}.
 * Two kind of events can be received:
 * <ul>
 * <li>{@code item} - the item of the {@link Uni}, can be {@code null}</li>
 * <li>{@code failure} - the failure propagated by the {@link Uni}</li>
 * </ul>
 *
 * @param <T> the type of item
 */
public class UniSubscribe<T> {
}

允许订阅Uni,针对upstream,我们可以采取不同的事件进行处理。

当异步结果返回后,UniSubscribe接受两种事件(也可以说是消费者):

  1. 返回的结果,也就是Uni中的Item,这个值可以为null
  2. 返回的异常,失败了,不多说。

闲话

从编码体验上来说,还是推荐使用订阅的模式去进行数据操作,毕竟订阅、消费者的概念都比较熟悉。我们口述下Quarkus异步操作的过程:得到了一个Uni后,我们订阅了Uni,这时候触发了Uni中获取item的计算,在with中声明了成功或者失败的处理方法。

主要业务代码体现在消费者的处理过程中,整体设计还是比较优雅的。