Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Oracle Cloud Hangout Cafe - 挑戦! JavaでReactiveプログラミング / reactive-java

Oracle Cloud Hangout Cafe - 挑戦! JavaでReactiveプログラミング / reactive-java

Oracle Cloud Hangout Cafe(おちゃかふぇ)のセッションスライドです。

(セッションの録画)
https://youtu.be/XjMz9bACM2c

(イベントページ)
https://ochacafe.connpass.com/event/189340/

oracle4engineer
PRO

October 28, 2020
Tweet

More Decks by oracle4engineer

Other Decks in Technology

Transcript

  1. 挑戦! JavaでReactiveプログラミング
    Oracle Cloud Hangout Cafe - Season 3 #4
    Tadahisa Kotegawa
    Senior Director
    Oracle Corporation Japan
    Oct 28th, 2020

    View Slide

  2. Safe harbor statement
    以下の事項は、弊社の一般的な製品の方向性に関する概要を説明するものです。また、
    情報提供を唯一の目的とするものであり、いかなる契約にも組み込むことはできません。以
    下の事項は、マテリアルやコード、機能を提供することを確約するものではないため、購買
    決定を行う際の判断材料になさらないで下さい。
    オラクル製品に関して記載されている機能の開発、リリース、時期及び価格については、弊
    社の裁量により決定され、変更される可能性があります。
    Copyright © 2020, Oracle and/or its affiliates
    2

    View Slide

  3. 自己紹介
    Copyright © 2020, Oracle and/or its affiliates
    3
    古手川 忠久
    日本オラクル株式会社 ソシューションアーキテクト本部
    デブサミ2020 セッションレポート 読んで下さい!
    https://codezine.jp/article/detail/12054
    [CodeZine] これからのマイクロサービスに必要な
    最新技術が凝縮した「Helidon」を徹底解説
    @tkotegaw
    tkote / oracle-japan

    View Slide

  4. Reactive Programingのフレームワークを使うと色々と良いことがあるのか?
    「ノンブロッキング/非同期通信」の仕組みを
    「簡単に」取り入れたい
    データストリームを「パフォーマンス良く」「負
    荷集中を避けながら」処理したい
    今回のテーマで深堀りしたいところ
    Copyright © 2020, Oracle and/or its affiliates
    4
    Microservice Microservice
    Microservice
    B
    Microservice
    C
    Microservice Microservice
    Microservice
    A
    Microservice
    Microservice
    Microservice
    複数の独立した
    Microserviceを
    呼び出した結果を元に
    レスポンスを作成
    A
    B
    C
    A
    B
    C
    メッセージの送信先の
    負荷を考慮しながら効
    率的に送信

    View Slide

  5. 5 Copyright © 2020, Oracle and/or its affiliates
    1. Reactive Programing 101
    2. Reactive Streams
    3. Reactive Programingフレームワーク
    - java.util.concurrent.Flow
    - ReactiveX RxJava
    - Helidon Reactive Operators
    4. MicroProfile Reactive Streams Operators
    5. MicroProfile Reactive Messaging
    6. Reactive Messaging in Practice
    7. まとめ
    Agenda

    View Slide

  6. 6 Copyright © 2020, Oracle and/or its affiliates
    本日の Reactiveプログラミング は 全部
    です…
    今日はJavaのソースをたくさん読みますので
    最初にちょっとだけ準備体操しておきます…

    View Slide

  7. ラムダ式 = メソッドを変数のように扱えるようにし、コード量を減らし読みやすくする
    ラムダ式、関数型インタフェース、メソッド参照、 Stream API
    Copyright © 2020, Oracle and/or its affiliates
    7
    ラムダ式 関数型インタフェースを実装する際に、処理の記述を簡潔にするための文法
    (引数) -> { 処理; ... }
    関数型インタフェース 実装すべきメソッドが1つしかないインタフェース
    java.util.function パッケージに汎用の関数型インタフェースがある
    - Supplier ( T get () )
    - Consumer ( void accept (T) )
    - Function ( R apply(T) )
    メソッド参照 処理内容がメソッド呼び出し1つのみで、かつそのメソッドの引数が一意に決まる場合、
    引数そのものを省略する記法
    list.forEach(i -> System.out.print(i)); // Consumerを実装した匿名クラス
    list.forEach(System.out::print); // メソッド参照
    Stream API データ構造に対してStreamを「生成する」「操作する」「まとめる」の処理を連続して行
    うことで、目的の結果を得られるよう記述できるAPI
    List.of(1, 2, 3).stream().map(n -> n * 2).forEach(System.out::println); // 2¥n4¥n6¥n

    View Slide

  8. Copyright © 2020, Oracle and/or its affiliates
    8
    import java.util.function.Function;
    public class Test {
    public void someFunction(int x, Function f){
    System.out.println(f.apply(x));
    }
    public void test(){
    class MySqrt implements Function { // local innter class
    @Override public Double apply(Integer x){
    return Math.sqrt(x);
    }
    }
    someFunction(2, new MySqrt());
    someFunction(2, new Function(){ // anonymous inner class
    @Override public Double apply(Integer x){
    return Math.sqrt(x);
    }
    });
    someFunction(2, Math::sqrt); // lambda with method reference
    someFunction(2, x -> Math.pow(x, 3)); // cube
    }
    public static void main(String[] args){ new Test().test(); }
    }
    クラスをまず定義してから、
    そのインスタンスを引数に
    匿名インナークラスを使って
    インスタンス作成時に定義
    ラムダ式&メソッド参照ですっきり
    & 異なる実装も同様に簡潔に書ける
    y = f(x) の x と f を
    引数で受け取って y を出力

    View Slide

  9. Javaで非同期計算を扱う時に登場する「将来起きる結果」を表すもの
    Future, CompletionStage , CompletableFuture
    Copyright © 2020, Oracle and/or its affiliates
    9
    interface
    Future
    T get()
    boolean isDone()
    boolean cancel(boolean mayInterruptIfRunning)

    CompletableFuture
    boolean complete​(T value)
    boolean completeExceptionally​(Throwable ex)
    T join()
    static CompletableFuture runAsync​(Runnable runnable)
    static CompletableFuture supplyAsync​(Supplier supplier)

    interface
    CompletionStage
    CompletionStage thenApply​(Function super T,​? extends U> fn)
    CompletionStage thenAccept​(Consumer super T> action)
    CompletionStage thenRun​(Runnable action)
    CompletableFuture toCompletableFuture()

    非同期計算の連鎖を行う
    非同期計算の結果そのもの
    Future と CompletionStage を継承した便利クラス
    Future を生成するメソッドを
    提供するインターフェースが
    ExecutoreService
    + submit(task)
    Function, Consumer, Runnable,
    Supplier… はみんな
    関数型インターフェース
    なのでラムダ式が使える!

    View Slide

  10. 複数のタスクを同時実行する際に、複数のスレッドを効率よく使う仕組み
    複数のスレッドをあらかじめ作成して待機させておく
    タスクが来たら待っているスレッドにタスクを割り当てて処理を開始させる
    実行させるスレッドに空きがない場合は、タスクをキューに入れてスレッドに空きができるまで待つ
    Javaでは、 ExecutorService というインターフェースを持った、色々なバリエーションのスレッドプー
    ルの仕組みを提供している
    • AbstractExecutorService, ForkJoinPool, ScheduledThreadPoolExecutor, ThreadPoolExecutor
    通常はExecutors というユーティリティクラスを使って ExecutorService のインスタンスを取得する
    • Executors.newSingleThreadExecutor()
    • Executors.newCachedThreadPool()
    • Executors.newFixedThreadPool​(int nThreads)
    • Executors.newScheduledThreadPool​(int corePoolSize)
    • etc.
    スレッドプール (Thread Pool)
    Copyright © 2020, Oracle and/or its affiliates
    10

    View Slide

  11. 複数のバックエンドを並列(≒非同期)に呼び出す方法を探る
    ここからはReactive Programingに入る前の練習問題…
    Copyright © 2020, Oracle and/or its affiliates
    11
    client
    JAX-RS
    resource
    processor
    processor
    processor
    “abc,lmn,xyz”
    “CBA,NML,ZYX”
    “abc”
    “CBA”
    “lmn”
    “NML”
    “xyz”
    “ZYX”
    どう作る?
    REST API On the Same JVM
    return measure(() -> { // wrapper
    return
    Arrays.stream(str.split(“,”)) //
    .map(processor::process) //
    .collect(Collectors.joining(“,”)); //
    });
    * デモでは実際のprocessor
    のインスタンスは1つです
    ストリームを使った基本形
    (これは同期型)

    View Slide

  12. Streamの並列処理機能
    java.util.stream.Stream#parallel()
    • ForkJoinPool.commonPool() の並列度に依存
    Javaの並行処理機能
    java.util.concurrent.CompletableFuture#supplyAsync(Supplier)
    • JDKが提供する ExecutorService を使うパターン
    • コンテナ(Helidon)が提供する ExecutorService を使うパターン
    MicroProfileが提供する非同期処理機能
    org.eclipse.microprofile.faulttolerance @Asynchronous
    メソッドを非同期に呼び出そうと思ったときに、まず思いつく方法
    Copyright © 2020, Oracle and/or its affiliates
    12
    “OCHaCafe2#4 Cloud Native時代のモダンJavaの世界”
    http://tiny.cc/ochacafe-mp-slide
    注: 「順次(sequential)ストリーム」「並列(parallel)ストリーム」と 「順序付け(ordering)」
    → 順序付けされたストリームでは並列ストリームでも検出順に処理するよう制約される

    View Slide

  13. 非同期RESTクライアントはどう作る?
    バックエンドがRESTサービスだったら?
    Copyright © 2020, Oracle and/or its affiliates
    13
    client
    JAX-RS
    resource
    REST
    processor
    REST
    processor
    REST
    processor
    “abc,lmn,xyz”
    “CBA,NML,ZYX”
    “abc”
    “CBA”
    “lmn”
    “NML”
    “xyz”
    “ZYX”
    どう作る?
    REST API REST API
    Network
    * デモでは実際のprocessor
    のインスタンスは1つです

    View Slide

  14. JAX-RS Async Client
    JAX-RS Reactive Client
    MicroProfile Async Rest Client
    非同期RESTクライアントの選択肢
    Copyright © 2020, Oracle and/or its affiliates
    14
    Future response =
    ClientBuilder.newClient()
    .target(uriInfo.getBaseUri()).path("/async-client/process")
    .queryParam("str", x)
    .request()
    .async() // AsyncInvoker
    .get(String.class);
    CompletionStage response =
    ClientBuilder.newClient()
    .target(uriInfo.getBaseUri()).path("/async-client/process")
    .queryParam("str", x)
    .request()
    .rx() // CompletionStageRxInvoker
    .get(String.class);
    CompletionStage response =
    RestClientBuilder.newBuilder()
    .baseUri(uriInfo.getBaseUri())
    .build(ProcessClient.class) // interface with path info
    .process(x); // interface method

    View Slide

  15. Copyright © 2020, Oracle and/or its affiliates
    15
    Reactive Programing 101

    View Slide

  16. https://www.reactivemanifesto.org/
    日本語バージョン:「リアクティブ宣言」 (https://www.reactivemanifesto.org/ja)
    The Reactive Manifesto
    Copyright © 2020, Oracle and/or its affiliates
    16
    即応性と、耐障害性と、弾力性と、メッセージ駆動とを備えた
    システムをリアクティブシステム (Reactive Systems) と呼ぶ。
    • 即応性 (Responsive)
    • 耐障害性 (Resilient)
    • 弾力性 (Elastic)
    • メッセージ駆動 (Message Driven)
    リアクティブシステムは、Reactive Programingを使
    わなくても実現可能なので注意!

    View Slide

  17. Responsive, Resilient, Elastic, Message Driven
    即応性 (Responsive)
    • システムはできる限り速やかに反応する。問題は即座に検知され効果的に対処される。迅速かつ一貫
    性のある応答時間に主眼を置く。
    耐障害性 (Resilient)
    • 障害発生時でも即応性を維持する。障害はそれぞれのコンポーネントに封じ込められ、コンポーネン
    トは互いに隔離されるので、システムの部分的障害は全体に影響を与えることなく回復することが保
    証される。
    弾力性 (Elastic)
    • システムはどのような負荷の下においても即応性を維持する。入力量の変化に対して、それに対応す
    るリソースを増減させる。競合するポイントや大きなボトルネックが存在しないようシステムはデザ
    インされる(コンポーネントの共有やレプリケーション、入力の分散)。
    メッセージ駆動 (Message Driven)
    • コンポーネント間の境界を作るために非同期なメッセージ・パッシングを使い、疎結合、隔離性、位
    置透過性を確保する。明示的なメッセージ・パッシングは負荷の管理、弾力性、フロー制御(メッ
    セージ・キューの作成・管理とバック・プレッシャーの適用)を可能とする。
    即応性、耐障害性、弾力性、メッセージ駆動
    Copyright © 2020, Oracle and/or its affiliates
    17

    View Slide

  18. データをストリームとして認識
    • データを受け取るたびにプログラムが反応して処理を行う=リアクティブ
    • 必要なデータを自ら取得して処理をするスタイルではない
    データの生産側はデータの消費側がどういう処理をしているのかを意識しない
    • データの消費側の処理を待つ必要がなくなる →非同期
    • データを通知をした後は、すぐに別の次の処理を行うことができる →ノンブロッキング
    • データの消費者の負荷状況に関わらず一方的にデータを通知し続ける状況への対応手段の提供
    →バックプレッシャー
    Reactive Programing とは?
    Copyright © 2020, Oracle and/or its affiliates
    18
    データ
    生産者
    データ
    消費者A
    データ
    消費者B
    Reactive
    Programing
    Framework
    publish
    subscribe
    subscribe
    負荷
    非同期
    非同期
    back pressure

    View Slide

  19. 非Reactiveなアプリケーションで問題となる領域に対応可能
    ネットワーキング・ヘビーなアプリケーション
    • NW経由のサービス呼び出しは、本質的にレイテンシが大きい
    → スレッドをブロックしてレスポンスを待っているくせに、タイムアウトや障害が起きても大したリ
    カバリーができるわけでない…
    大規模サーバー・アプリケーション
    • 複数リクエストを同時に処理するためには、同時リクエスト分のスレッドが必要になる
    → スレッドの生成には余分なリソースが必要だし、ブロックされるスレッドが増えるとCPU効率が悪
    くなる
    Reactive Programing のユースケース
    Copyright © 2020, Oracle and/or its affiliates
    19

    View Slide

  20. Copyright © 2020, Oracle and/or its affiliates
    20
    Reactive Streams

    View Slide

  21. http://www.reactive-streams.org/
    https://github.com/reactive-streams/reactive-streams-jvm
    非同期ストリーム処理を実現するため、Reactive Streams Special Interest Group(SIG)が策定した仕様
    Javaだけでなく、JavaScriptや.NET向けのインタフェースも定義
    インターフェースを定義、Java実装は以下のものがあり
    • Akka Streams
    • ReactiveX RxJava
    • Project Reactor
    以下の4つの主要コンポーネント(インターフェース)
    Reactive Streams とは?
    Copyright © 2020, Oracle and/or its affiliates
    21
    コンポーネント 説明
    Publisher データを通知するコンポーネント
    Subscriber 受け取った通知をもとに処理を行うコンポーネント
    Subscription PublisherとSubscriberを介在し、購読のキャンセルや通知するデータ数をリ
    クエストするインターフェースを提供するコンポーネント
    Processor PublisherとSubscriberの両方のインターフェスを持ち、Publisherと
    Subscriberの中間に入ってデータを処理するコンポーネント

    View Slide

  22. loop
    cancel
    end
    normal
    org.reactivestreams パッケージ
    Reactive Steams - 処理シーケンス
    Copyright © 2020, Oracle and/or its affiliates
    22
    Publisher
    subscribe(Subscriber)
    Subscriber
    onSubscribe(Subscription)
    onNext(T)
    onError(Throwable)
    onComplete()
    Subscription
    request(long)
    cancel()
    1 n
    1
    1
    n 1
    << create >>
    Publisher Subscription Subscriber
    subscribe()
    new()
    onSubscribe(Subscription)
    request(long)
    onNext()
    request(long)
    << generate message >>
    onError()
    cancel()
    onComplete()
    OR
    OR
    << consume >>
    * onNext()はsingle threadでシーケンシャルに呼び出される
    全部 interface

    View Slide

  23. 過負荷の下でシステムを安定稼働させるための仕組み
    SubscriberからPublisherに対して、自身が過負荷状態であることを伝える
    • Subscriberは request(long) * を使ってその時点で処理可能なメッセージ数を知らせる
    • Publisherは
    (最新のrequest数) – (最新のrequest数を受領後に送信したメッセージ数)
    でSubscriberの処理可能なメッセージ数が推定できる
    過負荷だと伝えられたPublisherは次に送信しようとしているメッセージをどう処理すべきか?
    • 気にせず送信するか、バッファするか、破棄する (エラーにする) か → 実装判断
    • バッファしたとしても、バッファーがオバーフローする可能性は依然あり
    • オーバーフローする前に、(もしあれば)さらに上流に対して過負荷状態を伝えるか?…
    • Subscriberの処理が捗るまでwaitする(or 上流からのリクエストを拒否する) か?…
    Reactive Steams – Backpressure
    Copyright © 2020, Oracle and/or its affiliates
    23
    * request(n) の n = Long.MAX_VALUE → 無制限を表す、以降request()をcallする必要なし

    View Slide

  24. …と言ってもReactive Steamsはインターフェースなので実装しないと動かない
    Reactive Steams – デモ
    Copyright © 2020, Oracle and/or its affiliates
    24
    client
    JAX-RS
    resource
    processor
    processor
    processor
    “abc,lmn,xyz”
    “CBA,NML,ZYX”
    “abc”
    “CBA”
    “lmn”
    “NML”
    “xyz”
    “ZYX”
    どう作る?
    Reactive Streams
    * デモでは実際のprocessor
    のインスタンスは1つです
    もし自分の実装を世の中に広めたいのであれば、
    Reactive Streams TCK(Technology Compatibility Kit)
    があるのでそれを使ってバリデーションする
    RsMessage(String request)
    + String getRequest()
    + complete(String response)
    + completeExceptionally(Throwable t)
    + CompletableFuture getResponse()
    RsSubscriber
    RsPublisher
    自作のPublisher 自作のSubscriber
    自作の送受信メッセージ・クラス
    PublisherからSubscriberの実行結果を確認
    できるように細工している!
    実行結果を
    セット
    リクエストをラップ
    fire & forget
    Subscriberの実行結果を知る術なし

    View Slide

  25. Copyright © 2020, Oracle and/or its affiliates
    25
    Reactive Programing フレームワーク

    View Slide

  26. Java 9で導入されたAPI で、Reactive Streams仕様に対応
    以下のインターフェースを定義
    • Flow.Publisher
    • Flow.Subscriber
    • Flow.Subscription
    • Flow.Processor
    とは言え、Reactive Streamsとは仕様的に同値なだけ
    • Java 9+の環境で、Reactive Streams APIとFlow APIを混在して使いたいケースあり
    • Reactive Streams API → 稼働環境をJava 9+に制限したくない開発フレームワークを実装する場合の選択肢
    • Flow API → Java 9+の環境では積極的に活用したいけど開発フレームワークがFlow対応していない…
    Reactive Streams側に両者のインターフェースを橋渡しするアダプターが存在する
    • org.reactivestreams.FlowAdapters
    • Reactive Streams → Flow: toFlowProcessor(), toFlowPublisher(), toFlowSubscriber()
    • Flow → Reactive Streams: toProcessor(), toPublisher(), toSubscriber()
    java.util.concurrent.Flow
    Copyright © 2020, Oracle and/or its affiliates
    26

    View Slide

  27. デモ
    java.util.concurrent.Flow
    Copyright © 2020, Oracle and/or its affiliates
    27
    client
    JAX-RS
    resource
    processor
    processor
    processor
    “abc,lmn,xyz”
    “CBA,NML,ZYX”
    “abc”
    “CBA”
    “lmn”
    “NML”
    “xyz”
    “ZYX”
    どう作る?
    Flow
    Reactive Style
    FlowMessage(String request)
    + String getRequest()
    + complete(String response)
    + completeExceptionally(Throwable t)
    + CompletableFuture getResponse()
    FlowSubscriber
    java.util.concurrent.
    SubmissionPublisher
    + subscribe(FlowSubscriber)
    + submit(FlowMessage)
    + estimateMaximumLag()
    + estimateMinimumDemand()
    JDKが提供しているbuffer装備の便利なPublisher
    自作のSubscriber
    * デモでは実際のprocessor
    のインスタンスは1つです
    実行結果を
    セット
    リクエストをラップ

    View Slide

  28. AndroidもサポートするReactive Programing ライブラリ
    元々はObservable と Observer (名前はObserverパターンに由来)
    Reactive Streamsに対応
    • Flowable (implements Publisher) と Subscriber
    • flatMapを使った並列処理を記述できる! → Publisherをネストするイメージ
    Flowable flatMap(Function super T,? extends Publisher extends R>> mapper)
    ReactiveX RxJava
    Copyright © 2020, Oracle and/or its affiliates
    28
    final String[] args = Optional.ofNullable(str).orElse(defaultStr).split(",");
    final ConcurrentHashMap dict = new ConcurrentHashMap<>(); //
    return measure(() -> {
    Flowable
    .fromArray(args)
    //.flatMap(x -> Flowable.just(x).subscribeOn(Schedulers.from(es)).map(processor::process))
    .flatMap(x -> Flowable.just(x)
    .subscribeOn(Schedulers.from(es))
    .doOnNext(s -> dict.put(s, processor.process(s)))
    )
    .blockingSubscribe();
    return Arrays.stream(args).map(dict::get).collect(Collectors.joining(","));
    });
    普通にflatMap()を使うと
    結果が順不同になる
    後で順序を正すため一旦辞書に保管

    View Slide

  29. 外部のReactive系モジュールとは独立しており、Helidonのecosystemだけに依存
    io.helidon.common.reactive.Multi
    io.helidon.common.reactive.Single
    Helidon (SE) の Reactive Operators
    Copyright © 2020, Oracle and/or its affiliates
    29
    final String[] args = Optional.ofNullable(str).orElse(defaultStr).split(",");
    final ConcurrentHashMap dict = new ConcurrentHashMap<>(); //
    return measure(() -> {
    Multi
    .just(args)
    .flatMap(x -> Single.just(x).observeOn(es).peek(s -> dict.put(s, processor.process(s))))
    .collectList().toCompletableFuture().join(); //
    return Arrays.stream(args).map(dict::get).collect(Collectors.joining(","));
    });
    HelidonもflatMap()で並列処理を書ける

    View Slide

  30. Copyright © 2020, Oracle and/or its affiliates
    30
    MicroProfile Reactive Streams Operators

    View Slide

  31. 開発者がマイクロサービス・アプリケーションを効率よく開発するためのフレームワーク
    多言語で構築されるマイクロサービス環境の中
    で、他コンポーネントと協調して動くためのJava
    アプリケーションに必要な機能を規定
    • 開発者はビジネスロジックの実装に集中でき、効率
    的な開発ができる
    • 基盤となる実装は、MicroProfileプロバイダの品質
    維持・向上を期待できる
    Java EE 8由来 + MicroProfileオリジナル
    • Java EE 8はREST開発のコアFW部分
    • 上記 + 主にCNCF由来のオープン規格をJava
    APIとして仕様化
    Eclipse MicroProfile
    Copyright © 2020, Oracle and/or its affiliates
    OpenMetrics
    OpenTracing
    Health
    Check
    Circuit
    Breaker
    Bulk Head
    start.microprofile.io
    31

    View Slide

  32. Sandbox Project “REACTIVE”
    ReactiveワーキンググループがMicroProfileアプリケーションをよりイベント・ドリブン、非同期的にす
    るための仕様を作成
    MicroProfile Reactive Streams Operators
    • Java Streams API を Reactive Streams にマッピング
    • ストリーム・データ処理にReactive Programingの要素を付加
    MicroProfile Reactive Messaging
    • Reactive Streams/Flowをベースに、メッセージの送受信を扱うAPIを提供
    • Java EE MDB(Message Driven Bean)より軽量で使いやすい
    MicroProfile と Reactive Programing
    Copyright © 2020, Oracle and/or its affiliates
    32

    View Slide

  33. 33 Copyright © 2020, Oracle and/or its affiliates
    Helidon
    オープン・ソース (github.com/oracle/helidon)
    • 2019/02/15 に1.0.0をリリース
    • 現在 バージョン 2.1.0, MicroProfile 3.3準拠
    マイクロサービスを開発するためのJavaライブラリの集合体
    JVM上の単体アプリケーションとして動作
    従来からの一般的なツール・基盤で開発&デプロイ可能
    • Java SE, Maven/Gradle, Docker, Kubernetes, etc.
    ※ Helidonは ギリシア語でつばめ(swallow)を意味します
    Docker Image
    Kubernetes
    Helidon jars
    OS base image
    Library jars
    App classes
    Pod Pod Pod
    Replica Set
    Java SE runtime

    View Slide

  34. Helidon SE と MP
    Copyright © 2020, Oracle and/or its affiliates
    34
    • マイクロ・フレームワーク
    • 超軽量フットプリント
    • 関数型
    • Reactive Web Server
    • GraalVM native image
    • MicroProfile 3.2
    • 軽量フットプリント
    • 宣言型
    • Java EEサブセット +
    マイクロサービス関連機能
    • GraalVM JIT compiler
    Helidon MP
    Helidon SE
    CDI Extensions
    フットプリント重視
    機能性重視
    互換性(MicroProfile準拠)重視

    View Slide

  35. Helidon MP Components
    Copyright © 2020, Oracle and/or its affiliates
    35
    MP Config Metrics
    Health
    Check
    Fault
    Tolerance
    JWT Auth
    JAX-RS CDI JSON-P / B
    Open API
    Open
    Tracing
    REST Client
    SE SE
    SE
    JPA
    gRPC
    JTA
    SE
    +
    MicroProfile =
    Reactive
    Streams
    Operators
    Reactive
    Streams
    Messaging
    Kafka
    Connector
    experimental
    SE
    SE

    View Slide

  36. https://github.com/eclipse/microprofile-reactive-streams-operators
    java.util.stream API のReactive Streams版
    • forEach(), filter(), map(), collect(), reduce(), etc. …
    • 非同期に処理を行う
    • 最終ステージ(collect, findFirst, reduce, etc.)はCompletionStageを返す
    • CompletionStageが完了しないと、結果を取得できない
    • 実行中の例外もCompletionStage経由
    • parallel(), findAny()は提供されない
    Publisher, Subscriber, Processor のステージを連結して完結する処理を組み立てる
    MicroProfile Reactive Streams Operators
    Copyright © 2020, Oracle and/or its affiliates
    36
    of(…) Filter()
    org.eclipse.microprofile.reactive.streams.operators
    PublisherBuilder
    map() toList()
    org.eclipse.microprofile.reactive.streams.operators
    SubscriberBuilder
    .to( )

    View Slide

  37. デモ
    MicroProfile Reactive Streams Operators
    Copyright © 2020, Oracle and/or its affiliates
    37
    client
    JAX-RS
    resource
    processor
    processor
    processor
    “abc,lmn,xyz”
    “CBA,NML,ZYX”
    “abc”
    “CBA”
    “lmn”
    “NML”
    “xyz”
    “ZYX”
    どう作る?
    MP
    Reactive Streams
    Operators
    return ReactiveStreams
    .of(str.split(",")) // PublisherBuilder
    .map(processor::process) // PublisherBuilder
    .collect(Collectors.joining(",")) // CompletionRunner
    .run() // CompletionStage
    .toCompletableFuture() // CompletableFuture
    .get(); // String
    * デモでは実際のprocessor
    のインスタンスは1つです

    View Slide

  38. Copyright © 2020, Oracle and/or its affiliates
    38
    MicroProfile Reactive Messaging

    View Slide

  39. https://github.com/eclipse/microprofile-reactive-messaging
    Reactiveな非同期メッセージングのためのMicroProfile規格
    マイクロサービスでしばしば用いられる”CQRS and Event-Sourcing”パターンにも利用できる
    Bean のメソッドに @Incoming もしくは @Outgoing のアノテーションをつけて、Channel 間の
    Message の受け渡しを定義する
    Single Subscriberモデル = Publisherが接続するSubscriberは1つのみ
    • 同一の Channel に複数の @Incoming をつけてはならない
    • これにより Publisher は Subscriber の Backpressure を適切に処理することができる
    MicroProfile Reactive Messaging
    Copyright © 2020, Oracle and/or its affiliates
    39
    channel-A channel-B
    Publisher Bean (Processor Bean) Subscriber Bean
    @Outgoging(“channel-A”)
    pub(){
    }
    @Incoming(“channel-B”)
    sub(){
    }
    @Incoming(“channel-A”)
    @Outgoging(“channel-B”)
    process(){
    }
    1 1 1 1

    View Slide

  40. メソッドの定義方法 – バラエティがある
    ざっくり言うと、Publisher/Subscriber/Processorに自分の実装を使うか、FW提供のものを使うか
    MicroProfile Reactive Messaging
    Copyright © 2020, Oracle and/or its affiliates
    40
    @Outgoing("channel") Publisher> method()
    @Outgoing("channel") Publisher method()
    @Outgoing("channel") PublisherBuilder> method()
    @Outgoing("channel") PublisherBuilder method()
    @Outgoing("channel") Message method()
    @Outgoing(“channel”) O method() // publisher
    @Outgoing(“channel”) CompletionStage> method()
    @Outgoing(“channel”) CompletionStage method()
    @Incoming(“channel”) Subscriber> method()
    @Incoming(“channel”) Subscriber method()
    @Incoming(“channel”) SubscriberBuilder> method()
    @Incoming(“channel”) SubscriberBuilder method()
    @Incoming(“channel”) void method(I payload) // subscriber
    @Incoming("channel") CompletionStage> method(Message msg)
    @Incoming("channel") CompletionStage> method(I payload)
    @Incoming("in") @Outgoing("out") PublisherBuilder> method(PublisherBuilder> pub)
    @Incoming(“in”) @Outgoing(“out”) O method(I payload) // processor
    ...

    View Slide

  41. デモ #1 – @Outgoing & @Incoming 実装方法は色々あり
    MicroProfile Reactive Messaging
    Copyright © 2020, Oracle and/or its affiliates
    41
    // Publisher
    //
    @Outgoing("messaging-demo")
    public Publisher preparePublisher() {
    logger.info("@Outgoing(¥"messaging-demo¥")");
    return ReactiveStreams
    .fromPublisher(FlowAdapters.toPublisher(publisher))
    .buildRs();
    }
    // Subscriber
    //
    // Subscriber onNext()
    @Incoming("messaging-demo")
    public Subscriber prepareSubscriber() {
    logger.info("@Incoming(¥"messaging-demo¥")");
    return FlowAdapters.toSubscriber(new FlowSubscriber());
    }
    // (Subscriber FW )
    // onNext()
    @Incoming("messaging-demo")
    public void consume(FlowMessage message) {
    logger.info("@Incoming(¥"messaging-demo¥")");
    try{
    final String response = processor.process(message.getRequest());
    message.complete(response); //
    }catch(Throwable t){
    message.completeExceptionally(t); //
    }
    }
    messaging-demo OR
    OR…
    OR…
    // –
    //
    @Outgoing("messaging-demo")
    public FlowMessage publish() {
    logger.info("@Outgoing(¥"messaging-demo¥")");
    try{
    return queue.take(); // block!
    }catch(Exception e){ throw new RuntimeException();}
    }
    OR
    こちらがおすすめ
    • Publisher→Subscription経由でbackpressureに対応できる
    backpressure関係ない場合はこれもアリ
    Subscriberからbackpressureを制御できる → request(n)

    View Slide

  42. デモ #2 – チャネルの連結: Publisher – Processor - Subscriber
    MicroProfile Reactive Messaging
    Copyright © 2020, Oracle and/or its affiliates
    42
    @Outgoing("channel-1")
    public KeyValueMessage publish() {
    logger.info("publish() is being called.");
    try{
    KeyValue kv = queue.take(); //
    logger.info("Publishing [channel-1]: " + kv);
    return KeyValueMessage.of(kv);
    }catch(Exception e){ throw new RuntimeException("cannot get message from the queue", e);}
    }
    @Incoming("channel-1")
    @Outgoing("channel-2")
    public String process(KeyValueMessage message) {
    logger.info("Processing [channel-1 -> channel-2]: " + message.getPayload());
    KeyValue kv = message.getPayload();
    return processor.process(kv.getKey() + "=" + kv.getValue());
    }
    @Incoming("channel-2")
    public void consume(String message) {
    logger.info(String.format("Consuming [channel-2]: %s", message));
    }

    View Slide

  43. デモ #3 – Helidonが提供するexample
    MicroProfile Reactive Messaging
    Copyright © 2020, Oracle and/or its affiliates
    43
    https://github.com/oracle/helidon/tree/master/examples/microprofile/messaging-sse
    @Outgoing("multiplyVariants")
    public Publisher preparePublisher() {
    // Publisher
    }
    @Incoming("multiplyVariants")
    @Outgoing("wrapSseEvent")
    public ProcessorBuilder multiply() {
    //
    // , ,
    }
    @Incoming("wrapSseEvent")
    @Outgoing("broadcast")
    public OutboundSseEvent wrapSseEvent(String msg) {
    // SSE
    }
    @Incoming("broadcast")
    public void broadcast(OutboundSseEvent sseEvent) {
    // SSE SSE
    }
    * ブラウザから試せます

    View Slide

  44. Channel を介して外部のメッセージング技術/システムと接続する
    • Publisherとして振る舞う → @incoming のメソッドにメッセージを送る
    → org.eclipse.microprofile.reactive.messaging.connector.IncomingConnectorFactory の実装クラス
    • PublisherBuilderを供給する
    • Subscriberとして振る舞う→ @Outgoing のメソッドからメッセージを受け取る
    → org.eclipse.microprofile.reactive.messaging.connector.OutgoingConnectorFactory の実装クラス
    • SubscriberBuilderを供給する
    • 設定情報は、MicroProfile Configファイルに記述
    • mp.messaging.[incoming/outgoing].[channel-name].[attribute]=[value]
    • mp.messaging.connector.[connector-name].[attribute]=[value]
    MicroProfile Reactive Messaging - Connector
    Copyright © 2020, Oracle and/or its affiliates
    44
    channel channel
    IncomingConnectorFactory
    + getPublisherBuilder(Config)
    META-INF/
    microprofile-config.properties
    OutgoingConnectorFactory
    + getSubscriberBuilder(Config)
    Connector
    (Publisher)
    Connector
    (Subscriber)
    外部
    システム
    外部
    システム
    ユーザ
    コード
    @Incoming(チャネル名) @Outgoing (チャネル名)
    参照
    参照
    ユーザ
    コード
    @Connector(コネクタ名)
    @Connector(コネクタ名)

    View Slide

  45. デモ #1 – File Connectorを自作してみる
    Publisher, Subscriberさえ実装できれば、あとはConnectorのお作法に従って組み立てるだけ
    • Publisherはディレクトリをポーリングして新規ファイルを監視する
    • Message#ack() をオーバーライドしてAckのタイミングでファイルをアーカイブディレクトリに移す
    • ファイルをPath渡しにする場合、ack()のタイミングを考慮しないとreadする前にmoveされてエラーになる
    MicroProfile Reactive Messaging - Connector
    Copyright © 2020, Oracle and/or its affiliates
    45
    @Acknowledgment のタイプと動作
    @Acknowledgement @Incomingメッセージ
    NONE Ack処理は行われない
    MANUAL ユーザーが Message#ack() を呼
    び出す必要がある
    PRE_PROCESSING メソッドが呼び出される前にAck
    される
    POST_PROCESSING (1) 送出したメッセージがAckさ
    れたタイミングか、(2) メッセー
    ジが送出されずにメソッドが終
    了したタイミングで、Ackされる
    Input Output
    Archive
    File Connector
    IN
    File Connector
    OUT
    In() out()
    file-in file-process file-out
    ack
    watch read
    move
    write
    *メソッドのシグナチュアによって
    デフォルトが異なるので注意
    Path or 内容

    View Slide

  46. デモ #1 – File Connectorを自作してみる – 使用例
    MicroProfile Reactive Messaging - Connector
    Copyright © 2020, Oracle and/or its affiliates
    46
    /**
    * File Connector
    */
    @Incoming("file-in")
    @Outgoing("file-process")
    @Acknowledgment(Acknowledgment.Strategy.MANUAL)
    public Message in(InFileMessage message) throws IOException{
    logger.info("--- in() ---");
    return Message.of(Files.readAllBytes(message.getPayload()), message::ack);
    }
    /**
    * File Connector
    */
    @Incoming("file-process")
    @Outgoing("file-out")
    public OutFileMessage out(Message message) throws IOException{
    logger.info("--- out() ---");
    return OutFileMessage.of(new String(message.getPayload()).toUpperCase());
    }
    # File Connector
    mp.messaging.incoming.file-in.connector=file-connector
    mp.messaging.incoming.file-in.dir=/tmp/file/in
    mp.messaging.incoming.file-in.pass-by-reference=true
    mp.messaging.outgoing.file-out.connector=file-connector
    mp.messaging.outgoing.file-out.dir=/tmp/file/out
    mp.messaging.outgoing.file-out.prefix='file-out'-yyyyMMddHHmmssSSS'.dat'
    mp.messaging.connector.file-connector.polling-interval=1000
    mp.messaging.connector.file-connector.archive-dir=/tmp/file/archive
    microprofile-config.properties

    View Slide

  47. デモ#1 – MicroProfile Reactive Messaging 準拠のKafka Connector
    Helidon Kafka Connector
    Copyright © 2020, Oracle and/or its affiliates
    47
    Helidon
    Kafka Connector
    Producer
    Helidon
    Kafka Connector
    Consumer
    Kafka-pub Kafka-sub
    KafkaPublisher
    + submit()
    + @Outgoing("kafka-pub") preparePublisher()
    KafkaSubscriber
    + @Incoming("kafka-sub") consume()
    KafkaResource
    /kafka/publish
    MicroProfile
    Reactive Messaging
    MicroProfile
    Reactive Messaging
    microprofile-config.properties
    mp.messaging.incoming.kafka-sub.connector=helidon-kafka
    mp.messaging.incoming.kafka-sub.topic=ochacafe-demo
    mp.messaging.incoming.kafka-sub.auto.offset.reset=latest
    mp.messaging.incoming.kafka-sub.enable.auto.commit=true
    mp.messaging.incoming.kafka-sub.group.id=GROUP_01
    mp.messaging.outgoing.kafka-pub.connector=helidon-kafka
    mp.messaging.outgoing.kafka-pub.topic=ochacafe-demo
    mp.messaging.connector.helidon-kafka.bootstrap.servers=localhost:9092
    mp.messaging.connector.helidon-kafka.key.serializer=org.apache.kafka.common.serialization.StringSerializer
    mp.messaging.connector.helidon-kafka.value.serializer=org.apache.kafka.common.serialization.StringSerializer
    mp.messaging.connector.helidon-kafka.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
    mp.messaging.connector.helidon-kafka.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
    REST endpoint

    View Slide

  48. デモ#2 – OCHaCafe「Cloud Native × Streaming はじめの一歩」のデモをアップグレード
    Kafka client APIでガリガリ書いてい
    た箇所をKafka Connectorに変更
    Helidon Kafka Connector
    Copyright © 2020, Oracle and/or its affiliates
    48
    Helidon
    Kafka
    Connector
    Helidon
    Kafka
    Connector
    • ソースから org.apache.kafka.* を排除(接続まわりのコーディング不要)
    • Configに接続情報を定義
    https://github.com/oracle-japan/ochacafe-spark-streaming
    @Incoming("from-kafka")
    public void consumeKafka(KafkaMessage message) {
    try{
    if(slackAlerterEnabled) sendToSlack(message);
    }catch(Exception e){
    logger.log(Level.WARNING, “…", e);
    }
    }

    View Slide

  49. Helidonでは「JMS Reactive Messaging connector」のプルリクが進行中
    • https://github.com/oracle/helidon/pull/2282
    • ActiveMQ, Weblogic JMS, Oracle AQ
    その他のConnector
    Copyright © 2020, Oracle and/or its affiliates
    49

    View Slide

  50. Copyright © 2020, Oracle and/or its affiliates
    50
    Reactive Messaging in Practice

    View Slide

  51. リアクティブ・プログラミングとパフォーマンスの関係を確認したい!
    2020年4月実施「OCHaCafe Premium」で実施したパフォーマンス検証を再検証
    CQRS + Event Sourcing (的な)構成をOCI Streaming(Kafka相当)で実装したが、…
    • 非同期&イベント・ドリブン = Kafka って安直過ぎないか? メリット/デメリット確かめよう
    • Kafka自体がNWサービス、それを扱うために構成も複雑化、運用含めてすぐに大掛かりになる
    Reactive Messaging in Practice
    Copyright © 2020, Oracle and/or its affiliates
    51

    View Slide

  52. 「OCHaCafe Premium」ではどんな構成でパフォーマンス測定したか?
    Copyright © 2020, Oracle and/or its affiliates
    52
    今回はこれを
    Reactiveに改良した
    構成にして
    パフォーマンス測定する

    View Slide

  53. async-eshop-order
    【Order】
    async-relay-order
    【relay】
    Copyright © 2020, Oracle and/or its affiliates
    キャッシュと非同期連携を組み合わせて負荷と処理時間を切り離し
    キャッシュ+非同期・パターン
    async-eshop-mall
    【Mall】
    PRODUCT
    INVENTORY
    ORDER
    ORDER_ITEM
    redis-mall
    PRODUCT
    INVENTORY
    kProductStream
    kOrderStream
    async-relay-mall
    【relay】
    async-eshop-cart
    【Cart】
    async-relay-cart
    【relay】
    redis-cart
    PRODUCT
    INVENTORY
    CART
    CART_ITEM
    53
    OCI Streaming
    OCI Streaming

    View Slide

  54. OCI Streaming
    OCI Streaming
    async-eshop-order
    【Order】
    async-relay-order
    【relay】
    Copyright © 2020, Oracle and/or its affiliates
    サービス間連携にOCI Streamingを入れず、代わりにReactive Messagingを使う
    キャッシュ+非同期・パターン → アーキテクチャ修正
    async-eshop-mall
    【Mall】
    PRODUCT
    INVENTORY
    ORDER
    ORDER_ITEM
    redis-mall
    PRODUCT
    INVENTORY
    kProductStream
    kOrderStream
    async-relay-mall
    【relay】
    async-eshop-cart
    【Cart】
    async-relay-cart
    【relay】
    redis-cart
    PRODUCT
    INVENTORY
    CART
    CART_ITEM





    Reactive
    Messaging
    Reactive
    Messaging
    P
    P
    S
    S S
    54
    P

    View Slide

  55. async-eshop-order
    【Order】
    Copyright © 2020, Oracle and/or its affiliates
    サービス間連携にStreamingを入れず、代わりにReactive Messagingを使う
    キャッシュ+非同期・パターン → アーキテクチャ修正
    async-eshop-mall
    【Mall】
    PRODUCT
    INVENTORY
    ORDER
    ORDER_ITEM
    redis-mall
    PRODUCT
    INVENTORY
    async-eshop-cart
    【Cart】
    redis-cart
    PRODUCT
    INVENTORY
    CART
    CART_ITEM
    Reactive
    Messaging
    Reactive
    Messaging
    P
    P
    S
    S S
    55
    P
    REST
    REST
    REST

    View Slide

  56. パフォーマンス測定結果 – 全体
    Copyright © 2020, Oracle and/or its affiliates
    737
    1933
    556
    401
    1693
    5392
    1823
    1621
    0
    1000
    2000
    3000
    4000
    5000
    6000
    モノリシック 機能分類 キャッシュ+非同期 Reactive Streams
    Messaging
    応答速度 (ms)
    100 VUでの限界性能 (応答速度)
    平均 最大
    113.1
    43.0
    149.8
    222.7
    0.0
    50.0
    100.0
    150.0
    200.0
    250.0
    モノリシック 機能分類 キャッシュ+非同期 Reactive Streams
    Messaging
    スループット (tps)
    100 VUでの限界性能 (スループット)
    平均スループット
    +48%
    -28%
    56
    Reactive
    Messaging
    Reactive
    Messaging

    View Slide

  57. レスポンスタイムが28%改善、スループットが48%改善
    パフォーマンス測定結果 – キャッシュ+非同期 v.s. Reactive改良版
    Copyright © 2020, Oracle and/or its affiliates
    57
    556
    401
    1823
    1621
    0
    200
    400
    600
    800
    1000
    1200
    1400
    1600
    1800
    2000
    キャッシュ+非同期 Reactive Streams
    Messaging
    応答速度 (ms)
    100 VUでの限界性能 (応答速度)
    平均 最大
    149.8
    222.7
    0.0
    50.0
    100.0
    150.0
    200.0
    250.0
    キャッシュ+非同期 Reactive Streams
    Messaging
    スループット (tps)
    100 VUでの限界性能 (スループット)
    平均スループット
    +48%
    -28%
    Reactive
    Messaging
    Reactive
    Messaging

    View Slide

  58. メリット
    • 非同期&イベントドリブンはOCI Streaming
    (≒Managed Kafka)を使わずフレームワーク
    レベル(今回はMicroProfile Reactive
    Messaging)で実現することが可能
    • 構成単純化、ポッド数も削減
    • サービス間の通信はRESTのみ、トレーシング
    も容易
    考慮点
    • メッセージが永続化されているKafkaの方が、
    障害発生時の再処理等が簡単そうに見える?
    • バックプレッシャー対応&異常系処理のベスト
    プラクティスの蓄積が課題
    • 「Kafkaは絶対落ちない」前提なら正、Kafkaの
    障害を考慮しなくてよいのはメリット
    レスポンスタイムが28%改善
    • リクエスト受信 → OCI Streaming送信の際の
    接続&通信処理の時間が削減されたと推察
    • 改良版では受信したリクエストをPublisherが即
    publishしてリターン
    → 後続の通信処理は別スレッドで実行
    スループットが48%改善
    • OCI Streaming送受信に関わる処理負荷により
    発生していたスレッド滞留が解消され、全体
    のスループットが改善されたと推察
    その他
    • Pubisher → (RM) → Kafka Connector → Kafka
    の折衷パターンは考慮の価値あり
    パフォーマンス測定 – 考察
    Copyright © 2020, Oracle and/or its affiliates
    58

    View Slide

  59. async-eshop-order
    【Order】
    Copyright © 2020, Oracle and/or its affiliates
    あれ? もっと簡単に出来るのでは? → アーキテクチャ再修正
    async-eshop-mall
    【Mall】
    PRODUCT
    INVENTORY
    ORDER
    ORDER_ITEM
    redis-mall
    PRODUCT
    INVENTORY
    async-eshop-cart
    【Cart】
    redis-cart
    PRODUCT
    INVENTORY
    CART
    CART_ITEM
    MP RS
    Messaging
    MP RS
    Messaging
    P
    P
    S
    S S
    59
    P
    非同期 REST
    非同期 REST
    非同期 REST
    MicroProfile使わなくても
    JAX-RS Reactive Client
    使えば良くね?


    View Slide

  60. Reactive REST Client よりも Reactive Messaging の方が良い結果となる
    パフォーマンス追加測定&比較結果
    Copyright © 2020, Oracle and/or its affiliates
    60
    556
    401 423
    1823
    1621 1605
    0
    200
    400
    600
    800
    1000
    1200
    1400
    1600
    1800
    2000
    キャッシュ+非同期 Reactive
    Streams
    Messaging
    Reactive
    REST
    Client
    応答速度 (ms)
    100 VUでの限界性能 (応答速度)
    平均 最大
    149.8
    222.7
    211.4
    0.0
    50.0
    100.0
    150.0
    200.0
    250.0
    キャッシュ+非同期 Reactive
    Streams
    Messaging
    Reactive
    REST
    Client
    スループット (tps)
    100 VUでの限界性能 (スループット)
    平均スループット
    +48%
    -28% -24%
    +41%
    Reactive
    Messaging
    Reactive
    Messaging

    View Slide

  61. Copyright © 2020, Oracle and/or its affiliates
    61
    まとめ

    View Slide

  62. リアクティブ・システム – 即応性、耐障害性、弾力性、メッセージ駆動
    Reactive Programing –リアクティブ・システムを構築するための手法
    Reactive Streams / Flow –非同期ストリーム処理を実現するためフレームワーク
    MicroProfile Reactive Streams Operators – Publisher, Subscriber, Processor のステージを連結する仕組み
    MicroProfile Reactive Messaging – チャネルを経由したメッセージの受け渡し
    MicroProfile Reactive Messaging/Connector – 外部システムとの連携をチャネル経由で実現
    Reactive Programing フレームワークを活用してハイ・パフォーマンスなシステムを構築できる!
    まとめ
    Copyright © 2020, Oracle and/or its affiliates
    62

    View Slide

  63. デモのソースコード(GitHub) - https://github.com/oracle-japan/ochacafe-reactive
    Oracle Cloud Hangout Cafe 過去会のスライド
    • OCHaCafe2#4 Cloud Native時代のモダンJavaの世界
    http://tiny.cc/ochacafe-mp-slide
    • OCHaCafe Premium #2 クラウド・アプリケーションのパフォーマンス
    http://tiny.cc/ochacafe-premium-20200403
    Helidon プロジェクト → helidon.io
    Reactive Streams → https://github.com/reactive-streams/reactive-streams-jvm
    Eclipse MicroProfile Reactive Project
    • https://download.eclipse.org/microprofile/microprofile-reactive-streams-operators-1.0/
    • https://download.eclipse.org/microprofile/microprofile-reactive-messaging-1.0/
    参照情報など
    Copyright © 2020, Oracle and/or its affiliates
    63

    View Slide

  64. ありがとうございました
    64 Copyright © 2020, Oracle and/or its affiliates

    View Slide

  65. View Slide