Ratpack入門 (6) - Promise

## Ratpack入門シリーズ

1. [Ratpack入門 (1) - Ratpackとは](https://qiita.com/minebreaker/items/d7eeca815872a97069f5)
2. [Ratpack入門 (2) - アーキテクチャー](https://qiita.com/minebreaker/items/a9b256fd8a1513104669)
3. [Ratpack入門 (3) - hello world 詳解](https://qiita.com/minebreaker/items/b26d7d0677f91f17a7b5)
4. [Ratpack入門 (4) - ルーティング & 静的コンテンツ](https://qiita.com/minebreaker/items/8f26d8350f936ad669c2)
5. [Ratpack入門 (5) - Json & Registry](https://qiita.com/minebreaker/items/4353611cc541e3cb35fb)
6. [Ratpack入門 (6) - Promise](https://qiita.com/minebreaker/items/4689058d0d7b34d99c74)
7. [Ratpack入門 (7) - Guice & Spring](https://qiita.com/minebreaker/items/52b303bc18db5b33bd68)
8. [Ratpack入門 (8) - セッション](https://qiita.com/minebreaker/items/1ebaa492b257365e1e5e)
9. [Ratpack入門 (9) - Thymeleaf](https://qiita.com/minebreaker/items/50693a4d6614979f654c)


## Promise

Ratpackは非ブロッキング・イベント駆動のライブラリーですので、各処理も非同期で書かれることが前提になります。Javaの非同期処理を下手に書くと大変なことになるのは、Java経験者であればよく知っていると思います。Ratpackは非同期処理を簡潔に記述するために、`Promise`クラスを提供しています。イメージとしてはJavaScriptの`Promise`に似ており、`then()`で処理が完了したときのコールバックを記述することができます。


## `Promise`を作る

IO処理はもっとも典型的なブロッキングな操作だと思います。`Blocking`ユーティリティークラスを使用することで、簡単に`Promise`を作ることができます。

```java
chain.all( ctx -> {

    String query = ctx.getRequest().getQueryParams().get( "id" );

    Promise<String> result = Blocking.get( () -> {
        return Database.find( query );
    } );

    ctx.render( result );
} );
```

ここで`Database.find()`を架空のデータベースからデータを探す処理だと考えてください。`Blocking.get()`は、引数のクロージャーを非同期に実行し、その戻り値を`Promise`にラップします。`Context.render()`には、`Promise`を渡すこともできます。

戻り値のない操作には`op()`を使います。`Operation`クラスはRatpackにおける戻り値のない`Promise`です。

```java
Blocking.op( () -> {
    String data = ctx.getRequest().getQueryParams().get( "data" );
    Database.persist( data );
} ).then( () -> {
    ctx.render( "OK" );
} );
```

まず、`Blocking.op()`内で架空データベースに情報を保存します。`op()`メソッドはこの処理の`Operation`を返します。続いて`then()`でデータベースにデータを保存した後の処理を記述します。`Context.render()`を呼んで、`OK`というレスポンスを作成しています。

### `Promise.sync()`

ファクトリーから`Promise`を作成します。

```java
Promise<String> result = Promise.sync( () -> "data" );
result.then( data -> {
    ctx.render( "OK" );
} );
```

### `Promise.async()`

他の非同期ライブラリーと連携するときのために、`Promise.async()`スタティックファクトリーが用意されています。

```java
Promise<String> result = Promise.async( downstream -> {
    downstream.success( "data" );
} );
result.then( data -> {
    ctx.render( "OK" );
} );
```

`success()`メソッドを呼ぶことで、処理が終了したことを伝えます。`Promise.async()`自体は、引数の処理を非同期に実行するわけではないことに注意してください。あくまで自身で(あるいはライブラリーで)非同期処理を記述する必要があります(そのため、公式の例では`Thread`を作成して`success()`を呼んでいます)。



## `Promise`の操作

### `then`

`Promise`の処理が完了したときに呼ばれるコールバックを指定します。コールバック内で`Context.render()`を呼びレスポンスを作成するのが、最もよくある処理だと思います。

注意すべきなのが、`then()`はコールバックが実行されることをアプリケーションに登録し、順次実行することです。次のコードを考えてください。

```java
@Data class Obj {
    public int a;
    public int b;
    public int c;
}
Obj o = new Obj();
Promise.value( 1 ).then( o::setA );
Promise.value( 2 ).then( o::setB );
Promise.value( 3 ).then( o::setC );
Operation.of( () -> ctx.render( o.toString() ) ).then();
```

`Promise`は非同期処理を表すので、一見すると`o.toString()`呼び出し時の`o`のフィールドはタイミングに依存してしまうように見えるかもしれません。しかし`then()`の呼び出しは、Ratpackが登録順に順次実行することを保証しているので、`o.toString()`の値は常に`Obj(a=1, b=2, c=3)`となります。とはいえこの動作は非直感的で分かりづらい動作なので、あまり使用はしないほうが良いと思います。

### `map`

指定の関数を、`Promise`の結果に適応した`Promise`を作成します。ストリームなどの`map`と同じですね。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .map( String::toUpperCase );
} ).getValue();

assertThat( result ).isEqualTo( "HOGE" );
```

### `blockingMap`

`map`とほぼ同じですが、実行をブロッキング処理用のスレッドで行います。`map`内の処理を`Blocking.get()`などでラップするイメージです。派生メソッド`blockingOp`があります。

### `flatMap`

`Promise`の結果を、指定した関数が返す`Promise`で置き換えます。Ratpackにはデフォルトで`Promise`を返す処理が多いので、使用頻度は意外と高めです。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .flatMap( v -> {
                      assertThat( v ).isEqualTo( "hoge" );
                      return Promise.value( "piyo" );
                  } );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );
```

### `mapIf`

指定された`Predicate`が正になる場合のみ、マップ関数を適用します。

### `mapError` `flatMapError`

例外が発生した場合、その例外を引数とするマップ関数を適用した結果を返します。正常終了した場合とエラーが発生した場合の分岐を流暢に書けます。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .mapIf( s -> true, s -> { throw new RuntimeException();} )
                  .mapError( t -> "piyo" );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );
```

### `apply`

呼び元の`Promise`自身を受け取り、`Promise`を返す関数を引数にとります。いまいち使いどころがわかりませんが、処理をメソッドに分割したときの記述を簡潔にすることが目的のようです。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" ).apply( p -> {
        assertThat( p == Promise.value( "hoge" ) ).isTrue();
        return p.map( String::toUpperCase );
    } );
} ).getValue();

assertThat( result ).isEqualTo( "HOGE" );
```

### `around`

`Promise`の計算前後に処理を挿入します。それ自体は便利そうに見えますが、afterの結果を`ExecResult`にラップしてあげなければならないため、無意味に冗長なコードになってしまう残念なメソッドです。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .around(
                          () -> "before",
                          ( before, r ) -> {
                              assertThat( before ).isEqualTo( "before" );
                              assertThat( r.getValue() ).isEqualTo( "hoge" );
                              return ExecResult.of( Result.success( "piyo" ) );
                          }
                  );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );
```

### `replace`

`Promise`を、別の`Promise`で置き換えます。要するに、`flatMap()`の引数を取らないバージョンです。これも必要性がよくわからないメソッドですね。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .replace( Promise.value( "piyo" ) );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );
```

### `route`

`Predicate`が`true`の場合、指定されたコンシューマーを実行します。JavaDocを見る限り、データのバリデーションなどに使うことを意図しているようです……が、使い勝手はあまりよくない気がします。

```java
ExecResult<String> result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .route( s -> false, System.out::println );
} );

assertThat( result.getValue() ).isEqualTo( "hoge" );
assertThat( result.isComplete() ).isFalse();

boolean completed = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .route( s -> true, System.out::println );
} ).isComplete();

assertThat( completed ).isTrue();
```

### `to`

`Promise`をほかの型に変換します。いまいち使いどころがなさそうに見えますが、外部ライブラリーの統合などで使用します。以下はRxRatpackの例です。

```java
List<String> resultHolder = new ArrayList<>();
ExecHarness.runSingle( e -> {
    Promise.value( "hoge" )
           .to( RxRatpack::observe )
           .subscribe( s -> resultHolder.add( s ) );
} );

assertThat( resultHolder ).containsExactly( "hoge" );
```

### `next`

`Promise`の結果を引数に持つコンシューマーを引数に持ちます。戻り値の`Promise`はもともとの`Promise`と同じ結果を返します。`nextOp`などの派生メソッドがあります。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .next( System.out::println );
} ).getValue();

assertThat( result ).isEqualTo( "hoge" );
```

### `right` `left`

`Promise`を別の`Promise`と組み合わせ、`Pair`の`Promise`として返します。

```java
Pair<String, String> result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .right( Promise.value( "piyo" ) );
} ).getValue();

assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );
```

### `cache`

`Promise`の結果をキャッシュします。例外が発生した場合、その例外もキャッシュされます。
`cacheResultIf`などの派生メソッドが存在します。

### `onError`

エラー発生時の処理を記述します。エラー時の`Context.render()`を記述するのが主な使い方かと思います。例外のクラスを引数にとるもの、例外のコンシューマーを受け取るもの、`Predicate`で例外を選択するものなど、複数のパターンの引数を持つことができます。

### `close`

`Promise`が完了したり、例外が発生したりした場合、引数に指定された`AutoCloseable`をクローズします。いまいち使いどころがわかりません。

### `retry`

処理に失敗した場合、指定時間ののちリトライを試みます。外部APIの呼び出し時などに便利です。

```java
String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .retry( 3, Duration.ofSeconds( 1 ), ( i, t ) -> System.out.printf( "retry: %d%n", i ) );
} ).getValue();

assertThat( result ).isEqualTo( "hoge" );
```

### `time`

`Promise`の実行にかかった時間を返すコンシューマーを引数にとります。パフォーマンス測定が主な用途でしょうか。

## スレッドと`fork`について

Ratpackは非同期処理のために、`Execution`クラスであらわされる単位で`Promise`を実行します。通常この`Execution`を意識することはありませんが、複数の`Promise`を並行で実行するためには、`Execution`を`fork()`する必要があります。コンビニエンスメソッドとして`Promise.fork()`が用意されており、簡単に別スレッドで`Promise`を実行することができます。

下記コードは`fork`のJavaDocにある例を少し変更したものです。

```java
CyclicBarrier b = new CyclicBarrier( 2 );

Pair<String, String> result = ExecHarness.yieldSingle( e -> {
    Promise<String> p1 = Promise.sync( () -> {
        b.await();
        return "hoge";
    } ).fork();
    Promise<String> p2 = Promise.sync( () -> {
        b.await();
        return "piyo";
    } ).fork();
    return p1.right( p2 );
} ).getValue();

assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );
```

ここで、`fork()`の呼び出しを削除すると、`p1`および`p2`は同じスレッドで順番に実行されるので、デッドロックが発生してしまいます。`fork()`で別スレッドを作成していれば、正常に動作します。


### まとめ

Ratpackの`Promise`は、Javaが苦手とする非同期処理のサポートを実現します。とはいえ、癖が強い部分もあり、メソッドの数も多いため、きちんと活用しようとするのは難しい面もあります。あまり賢くやろうとしないのがコツかもしれません。RatpackにはRxJavaをサポートするためのRxRatpackモジュールがあります。使い慣れた非同期ライブラリーがある場合、それらを活用するのも手です。

#### 個人的なおすすめ

* よく使うのは`map`、`flatMap`くらいで、全てのメソッドが実用的なわけではない。
* ブロッキング操作には`Blocking`クラス、もしくは`blocking...`メソッドを使う。
* 困ったら`then`で頑張ればなんとかなる。
* RxRatpackモジュールを適用し、RxJavaを活用する。