Mutiny の Uni と Multi をつなぐ方法

前回の調査で Uni をつなぐ方法は理解したのですが、 今度は Multi の調査を進めていく中で Uni と Multi をつなぐにはどうしたらいいのか分からなかったので調べてみました。

Mutiny のガイドを読むと、似たようなことが書かれていますが、これは Multi の各 item を uni に変換する方法です。今回は Multi のデータが一通りそろったら次の uni を実行する方法を考えたいと思います。

考え方としては、 uni の連結方法(chain を使う)は判明しているので、 Multi がすべて完了したら Uni に変換する方法を調べます。

Uni.createFrom().nullItem()
    .chain(() -> {
        return Multi.createFrom().items(1, 2, 3)
            /** 何らかの処理 */;
    })
    .chain(i -> Uni.createFrom().nullItem());

前後の処理は何でもいいので、仮で Uni.createFrom().nullItem() で表現しています。

onCompletion() ではない

完了したら次の uni を実行する、ということでまず最初に onCompletion を調べてみました。

onCompletion() の返却は MultiOnCompletion ですが、ソースを見ると Uni を返すメソッドはないので onCompletion は望むものではないようです。

collect()

Multi を Uni にするということは複数のものをまとめて1つにするということなので、 集約操作である collect を覗いてみます。

collect() の返却は MultiCollect なのでそのソースを覗いてみると、 Uni を返却するメソッドがたくさんあります。どうやら collect を使うとよいようです。

Uni.createFrom().nullItem()
    .chain(() -> {
        return Multi.createFrom().items(1, 2, 3)
            .collect()
            /** 何らかの処理 */;
    })
    .chain(i -> Uni.createFrom().nullItem());

asList や asMap はデータが少量の時のみ

Multi は大量のデータ処理で使うことが考えられます。asList や asMap を使うと、 Multi で渡ってきたデータを一つの List や Map に格納します。そのため、データが大量だとメモリが不足する可能性があるので使用を避けた方がよいでしょう。

データが少量であることが見えているならば使っても大丈夫です。

.collect().last()

last の JavaDoc を見ると、以下のように書かれています。

Creates a Uni receiving the last item emitted by the upstream Multi. The last item is item fired just before the completion event.

smallrye-mutiny/MultiCollect.java at 1.1.2 · smallrye/smallrye-mutiny · GitHub

completion の直前に、Multi の最後のアイテムを渡す Uni を作成するようです。まさに期待した動作ですね。

Uni.createFrom().nullItem()
    .chain(() -> {
        return Multi.createFrom().items(1, 2, 3)
            .collect()
            .last();
    })
    .chain(i -> Uni.createFrom().nullItem());

これを使えば他の uni と連結できます。

すべてのデータを集約したい場合

last() だと最後のデータしか返ってきませんが、すべてのデータを参照して何か1つのものに集約1 reduce や reduction の方が伝わるかもしれませんしたいケースもあると思います。例えば件数をカウントするなどです。

collect().with(collector) を使用すると、 Java の Stream の Collector を使用することができます。Collectors の JavaDoc を見ると Java 標準の Collector がいくつか用意されているのが確認できます。件数をカウントしたい場合は Collectors.counting() を使用できます。

Uni.createFrom().nullItem()
    .chain(l -> {
        // 件数を返す
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect().with(Collectors.counting());
    })
    .chain(l -> {
        // 件数を返す
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect().with(Collectors.summarizingLong(a -> 1L))
            .map(LongSummaryStatistics::getCount);
    })
    .chain(l -> {
        // 件数を返す (Collector 独自実装)
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect().with(Collector.of(
                () -> new long[1],
                (count, item) -> { count[0]++; },
                (a, b) -> a,
                arr -> arr[0]));
    })
    .chain(l -> {
        // 件数を返す (in を使う)
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect()
            .in(() -> new long[1], (count, item) -> {count[0]++;})
            .map(count -> count[0]);
    })
    .chain(l -> {
        // 総和を返す
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect().with(Collectors.reducing((a, b) -> {
                return a + b;
            }));
    })
    .chain(l -> {
        // 総和を返す
        return Multi.createFrom().items(1, 2, 3, 5, 7)
            .collect()
            .with(Collectors.summingLong(a -> a));
    })
    .chain(l -> Uni.createFrom().nullItem());

Collector を実装すれば他にもいろいろなことができそうです。

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください