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

Javaに鉄道指向プログラミング (Railway Oriented Pro gramming...

Javaに鉄道指向プログラミング (Railway Oriented Pro gramming) のエッセンスを取り入れる/Bringing the Essence of Railway-Oriented Programming to Java

Avatar for takumi.okamoto

takumi.okamoto

June 06, 2025
Tweet

More Decks by takumi.okamoto

Other Decks in Programming

Transcript

  1. © ZOZO, Inc. 3 エラー処理に対する関数型のアプローチ 鉄道指向プログラミング(ROP: Railway Oriented Programming)とは コードを汚染することなく、エレガントにエラーを補足する実践的なテクニック。

    関数を線路にみたて連結することでユースケースを構築するため、鉄道指向プログラ ミングと呼ばれている。 引用元: https://fsharpforfunandprofit.com/rop/
  2. © ZOZO, Inc. 4 エラー処理の話であり、関数型プログラミング(FP: Functional Programming)をJavaでどう実践するのか?という内容です。 • 主な対象者 ◦

    FPやROPに興味あるが、実践経験の少ないJavaプログラマ • 発表のスタンス ◦ あえて小難しい用語は使わず、具体例やコードを示して実践的な内容を目指 す ◦ JavaにFPやROPのエッセンスを取り入れる一歩目につなげたい 本発表について
  3. © ZOZO, Inc. 5 目次 • Javaでドメインエラーを表現する • JavaでROPを実践する •

    実業務においてJavaでROPを実践して得られた学び • まとめ 【注意】本題のROPの話題に入るまでに10分ほどかかります。
  4. © ZOZO, Inc. 7 ビジネスプロセスの一部として 発生が予想される? ドメインエラー パニック / インフラエラー

    Yes No 例: ユーザー名は1~50文字であるべきだが、 60文字がリクエストされた。 例: メモリ不足やnull参照、 ネットワークタイムアウトなど。 ドメインエラーとは
  5. © ZOZO, Inc. 8 ドメイン設計に含め、ビジネス観点で対処手順を用意してコードにそのプロセスを反映すべき。 ドメインエラーとは 例: ユーザー名は1~50文字であるべきだが、 60文字がリクエストされた。 例:

    メモリ不足やnull参照、 ネットワークタイムアウトなど。 ドメインエラー パニック / インフラエラー Yes No ビジネスプロセスの一部として 発生が予想される?
  6. © ZOZO, Inc. 9 本発表で扱う題材: 注文ユースケース 在庫確保 できない ハッピーパス 注文作成

    在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG ・ユーザーID ・商品ID ・数量 注文不可能な商品 注文不可能な数量
  7. © ZOZO, Inc. 10 本発表で扱う題材: 注文ユースケース 在庫確保 できない ハッピーパス 注文作成

    在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG ・ユーザーID ・商品ID ・数量 注文不可能な商品 注文不可能な数量 ドメインエラー
  8. © ZOZO, Inc. 検証NG 注文不可能な商品 注文不可能な数量 11 本発表で扱う題材: 注文ユースケース 在庫確保

    できない ハッピーパス 注文作成 在庫確保 入力 注文 ・注文可能な商品か? ・注文可能な数量か? ・ユーザーID ・商品ID ・数量 DB接続 エラー ドメインエラーでない 永続化
  9. © ZOZO, Inc. 12 本発表で扱う題材: 注文ユースケース 在庫確保 できない ハッピーパス 在庫確保

    永続化 注文 注文作成 入力 ・注文可能な商品か? ・注文可能な数量か? 検証NG まずは ココ に焦点をあて、ドメインエラー を どのように明示的に表現するか考える 注文不可能な商品 注文不可能な数量
  10. © ZOZO, Inc. 13 本発表で扱う題材: 注文ユースケース 注文 注文作成 入力 ・注文可能な商品か?

    ・注文可能な数量か? 検証NG ファクトリメソッド 検査例外 注文不可能な商品 注文不可能な数量
  11. © ZOZO, Inc. 14 本発表で扱う題材: 注文ユースケース 注文 注文作成 入力 ・注文可能な商品か?

    ・注文可能な数量か? 検証NG ファクトリメソッド 検査例外 注文不可能な商品 注文不可能な数量 public class 注文不能な商品 extends Exception { public 注文不能な商品(){ super("注文不能な商品です"); } } public class 注文不能な数量 extends Exception { public 注文不能な数量() { super("注文不能な数量です"); } }1
  12. © ZOZO, Inc. 15 public class 注文 { // 省略

    // このファクトリメソッドが外部からの唯一のインスタンス化の方法 public static 注文 create(UUID userId, UUID productId, int quantity) throws 注文不能な商品 , 注文不能な数量 { if (!注文可能な商品である(productId)) throw new 注文不能な商品(); if (!注文可能な数量である(quantity)) throw new 注文不能な数量(); return new 注文(UUID.randomUUID(), userId, productId, quantity); } } 検査例外でドメインエラーを表現する
  13. © ZOZO, Inc. 16 業務システムにはドメインエラーがたくさん 必然的に 検査例外を多用 することになる 【再掲】本発表で扱う題材: 注文ユースケース

    在庫確保 できない ハッピーパス 注文作成 在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG ドメインエラー
  14. © ZOZO, Inc. 17 チェックされる例外を過剰に使うと、API を使いにくいものにします。 チェックされる例外をスローするメソッドはストリーム (項目 45 –項目

    48) では直接使えないので、その負荷は Java 8 で増加しました。 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版 (p. 294). (Function). Kindle Edition. 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版 (p. 298). (Function). Kindle Edition. 検査例外の多用はつらいよ
  15. © ZOZO, Inc. 18 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版

    (p. 294). (Function). Kindle Edition. 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版 (p. 298). (Function). Kindle Edition. 検査例外の多用はつらいよ チェックされる例外を過剰に使うと、API を使いにくいものにします。 チェックされる例外をスローするメソッドはストリーム (項目 45 –項目 48) では直接使えないので、その負荷は Java 8 で増加しました。
  16. © ZOZO, Inc. 19 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版

    (p. 294). (Function). Kindle Edition. 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版 (p. 298). (Function). Kindle Edition. 検査例外の多用はつらいよ チェックされる例外を過剰に使うと、API を使いにくいものにします。 チェックされる例外をスローするメソッドはストリーム (項目 45 –項目 48) では直接使えないので、その負荷は Java 8 で増加しました。
  17. © ZOZO, Inc. 20 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版

    (p. 294). (Function). Kindle Edition. 引用元: ジョシュア・ブロック; 柴田芳樹. Effective Java 第3版 (p. 298). (Function). Kindle Edition. 検査例外の多用はつらいよ チェックされる例外を取り除く最も簡単な方法は、返したい結 果の型のオプショナルを返すことです(項目 55)。例外をス ローする代わりに、メソッドは単純に空オプショナルを返しま す。 この技法の短所は、望まれる計算を行えないことに関する 詳細な追加の情報を返せないことです。
  18. © ZOZO, Inc. 21 改めて、Javaでのドメインエラーの表現を考える 注文 注文作成 入力 ・注文可能な商品か? ・注文可能な数量か?

    検証NG 戻り値の型で 注文・検証NG のどちらか が返ることを表現する ドメインエラー 注文不可能な商品 注文不可能な数量
  19. © ZOZO, Inc. 22 • 直訳すると「どちらか」「いずれか」 • Javaに導入するならVavr(https://github.com/vavr-io/vavr) がおすすめ •

    Java標準の Optional<T> が empty または T どちらか一方の値だけ保持す るのと同じ要領で Left または Right どちらか一方の値だけを保持する型 • Rightという英単語には「正しい」という意味もあるので、Eitherで正常と 異常どちらかを表す場合、Rightに正常を入れるのが慣習 JavaにEither<L,R>を導入する
  20. © ZOZO, Inc. 23 • 直訳すると「どちらか」「いずれか」 • Javaに導入するならVavr(https://github.com/vavr-io/vavr) がおすすめ •

    Java標準にもあるOptional<T> がemptyかTどちらか一方の値だけ保持す るのと同じ要領でLeft・Right どちらか一方の値だけを保持する型 • Rightという英単語には「正しい」という意味もあるので、Eitherで正常と 異常どちらかを表す場合、Rightに正常を入れる慣習がある。 JavaにEither<L,R>を導入する JAVA JAVA JAVA JAVA JAVA VAVr vavr
  21. © ZOZO, Inc. 24 public class 注文 { // フィールド定義

    // このファクトリメソッドが外部からの唯一のインスタンス化の方法 public static Either<失敗理由 , 注文> create(UUID userId, UUID productId, int quantity){ if (!注文可能な商品である (productId)) return Either.left(失敗理由.商品が存在しない ); if (!注文可能な数量である (quantity)) return Either.left(失敗理由.数量が不正); return Either.right(new 注文(UUID.randomUUID(), userId, productId, quantity)); } } Eitherを使った戻り値でドメインエラーを表現する
  22. © ZOZO, Inc. 25 public class 注文 { // フィールド定義

    // このファクトリメソッドが外部からの唯一のインスタンス化の方法 public static Either<失敗理由 , 注文> create(UUID userId, UUID productId, int quantity){ if (!注文可能な商品である (productId)) return Either.left(失敗理由.商品が存在しない ); if (!注文可能な数量である (quantity)) return Either.left(失敗理由.数量が不正); return Either.right(new 注文(UUID.randomUUID(), userId, productId, quantity)); } } Eitherを使った戻り値でドメインエラーを表現する public enum 失敗理由 { 注文不可能な商品, 注文不可能な数量 } 列挙型
  23. © ZOZO, Inc. 26 ユースケース への入力 【再掲】本発表で扱う題材: 注文ユースケース 在庫確保 できない

    ハッピーパス 注文作成 在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG 注文不可能な商品 注文不可能な数量
  24. © ZOZO, Inc. 27 ユースケース への入力 【再掲】本発表で扱う題材: 注文ユースケース ハッピーパス 注文作成

    永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG 注文不可能な商品 注文不可能な数量 在庫確保 できない 在庫確保 Either<L, R>
  25. © ZOZO, Inc. 28 ユースケース への入力 【再掲】本発表で扱う題材: 注文ユースケース ハッピーパス 注文作成

    在庫確保 永続化 入力 ・注文可能な商品か? ・注文可能な数量か? 注文不可能な商品 注文不可能な数量 ユースケース の出力 Either<失敗理由, 注文> 在庫確保 できない 注文 検証NG
  26. © ZOZO, Inc. 29 @Service public class 注文UseCase { @Autowired

    private 在庫確保 在庫確保; @Autowired private 注文を保存 注文を保存; public record Input(UUID userId, UUID productId, int quantity) {} public enum 失敗理由 { 注文不可能な商品, 注文不可能な数量, 在庫確保できない, } public Either<失敗理由, 注文> execute(Input input) {} } Eitherを使った戻り値でドメインエラーを表現するユースケースを素朴に実装する
  27. © ZOZO, Inc. 30 Eitherを使った戻り値でドメインエラーを表現するユースケースを素朴に実装する public Either<失敗理由, 注文> execute(Input input)

    { Either<注文.失敗理由, 注文> 失敗理由Or注文 = 注文.create(input.userId, input.productId, input.quantity); if (失敗理由Or注文.isLeft()) return switch (失敗理由Or注文.getLeft()) { case 注文不可能な商品 -> Either.left(注文確定UseCase.失敗理由.注文不可能な商品); case 注文不可能な数量 -> Either.left(注文確定UseCase.失敗理由.注文不可能な数量); }; 注文 注文 = 失敗理由Or注文.get(); Either<在庫確保.失敗理由, Void> 失敗理由OrVoid = 在庫確保.execute(注文); if (失敗理由OrVoid.isLeft()) return Either.left(注文確定UseCase.失敗理由.在庫確保できない); 注文を保存.execute(注文); return Either.right(注文); }
  28. © ZOZO, Inc. 31 Eitherを使った戻り値でドメインエラーを表現するユースケースを素朴に実装する public Either<失敗理由, 注文> execute(Input input)

    { Either<注文.失敗理由, 注文> 失敗理由Or注文 = 注文.create(input.userId, input.productId, input.quantity); if (失敗理由Or注文.isLeft()) return switch (失敗理由Or注文.getLeft()) { case 注文不可能な商品 -> Either.left(注文確定UseCase.失敗理由.注文不可能な商品); case 注文不可能な数量 -> Either.left(注文確定UseCase.失敗理由.注文不可能な数量); }; 注文 注文 = 失敗理由Or注文.get(); Either<在庫確保.失敗理由, Void> 失敗理由OrVoid = 在庫確保.execute(注文); if (失敗理由OrVoid.isLeft()) return Either.left(注文確定UseCase.失敗理由.在庫確保できない); 注文を保存.execute(注文); return Either.right(注文); }
  29. © ZOZO, Inc. 32 public Either<失敗理由, 注文> execute(Input input) {

    Either<注文.失敗理由, 注文> 失敗理由Or注文 = 注文.create(input.userId, input.productId, input.quantity); if (失敗理由Or注文.isLeft()) return switch (失敗理由Or注文.getLeft()) { case 注文不可能な商品 -> Either.left(注文確定UseCase.失敗理由.注文不可能な商品); case 注文不可能な数量 -> Either.left(注文確定UseCase.失敗理由.注文不可能な数量); }; 注文 注文 = 失敗理由Or注文.get(); Either<在庫確保.失敗理由, Void> 失敗理由OrVoid = 在庫確保.execute(注文); if (失敗理由OrVoid.isLeft()) return Either.left(注文確定UseCase.失敗理由.在庫確保できない); 注文を保存.execute(注文); return Either.right(注文); } コードがエラーハンドリングに汚染されてハッピーパスがぼやける Eitherを使った戻り値でドメインエラーを表現するユースケースを素朴に実装する
  30. © ZOZO, Inc. 34 エラー処理に対する関数型のアプローチ。 【再掲】鉄道指向プログラミング(ROP: Railway Oriented Programming)とは コードを汚染することなく、エレガントにエラーを補足する実践的なテクニック。

    関数を線路にみたて連結することでユースケースを構築するため、鉄道指向プログラ ミングと呼ばれている。 引用元: https://fsharpforfunandprofit.com/rop/
  31. © ZOZO, Inc. 42 原則「ある関数の出力の型」と、「別のある関数の入力の型」が一致していれば連結できる 方針 Step1の出力がRightの場合はその値を入力としてStep2を実行する Step1の出力がLeftの場合はStep2をバイパスして、Step2のLeftと同じ線路で合流する on success

    bypass 2つのスイッチ関数を連結(合成)する Step1 Step2 連結のために必要な条件 ① Step1の戻り値のRightと Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致
  32. © ZOZO, Inc. 43 原則「ある関数の出力の型」と、「別のある関数の入力の型」が一致していれば連結できる 方針 Step1の出力がRightの場合はその値を入力としてStep2を実行する Step1の出力がLeftの場合はStep2をバイパスして、Step2のLeftと同じ線路で合流する on success

    bypass 2つのスイッチ関数を連結(合成)する Step1 Step2 連結のために必要な条件 ① Step1の戻り値のRightと Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致
  33. © ZOZO, Inc. 44 2つのスイッチ関数を連結(合成)する // step1を実行 Either<L, R1> step1Output

    = step1(input); Either<L, R2> step2Output; if (step1Output.isRight()) { // step2にstep1のRightを渡して実行 step2Output = step2(step1Output.get()); } else { // step2をbypass step2Output = Either.left(step1Output.getLeft()); } 方針 Step1の出力がRightの場合はその値を入力としてStep2を実行する Step1の出力がLeftの場合はStep2をバイパスして、Step2のLeftと同じ線路で合流する
  34. © ZOZO, Inc. 45 Either<L, R2> = step1(input).flatMap(step2) これが等価 2つのスイッチ関数を連結(合成)する

    // step1を実行 Either<L, R1> step1Output = step1(input); Either<L, R2> step2Output; if (step1Output.isRight()) { // step2にstep1のRightを渡して実行 step2Output = step2(step1Output.get()); } else { // step2をbypass step2Output = Either.left(step1Output.getLeft()); } 方針 Step1の出力がRightの場合はその値を入力としてStep2を実行する Step1の出力がLeftの場合はStep2をバイパスして、Step2のLeftと同じ線路で合流する
  35. © ZOZO, Inc. public interface Either<L, R> extends Value<R>, Serializable

    { default <U> Either<L, U> flatMap( Function<? super R, ? extends Either<L, ? extends U>> mapper) { Objects.requireNonNull(mapper, "mapper is null"); if (isRight()) { return (Either<L, U>) mapper.apply(get()); } else { return (Either<L, U>) this; } } } 46 VavrのEither型のflatMapの実装をみてみる 引用元: https://github.com/vavr-io/vavr/blob/main/vavr/src/main/java/io/vavr/control/Either.java#L367-L375 bypass on success
  36. © ZOZO, Inc. 47 【再掲】本発表で扱う題材: 注文ユースケース 在庫確保 できない ハッピーパス 注文作成

    在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG
  37. © ZOZO, Inc. 50 注文作成 在庫確保 「注文ユースケース」をROPで実装する 連結のために必要な条件 ① Step1の戻り値のRightと

    Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致 OK 注文 注文
  38. © ZOZO, Inc. 51 注文作成 在庫確保 「注文ユースケース」をROPで実装する 連結のために必要な条件 ① Step1の戻り値のRightと

    Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致 NG 注文不能な商品 | 注文不能な数量 在庫確保 できない
  39. © ZOZO, Inc. 52 注文作成 在庫確保 「注文ユースケース」をROPで実装する 連結のために必要な条件 ① Step1の戻り値のRightと

    Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致 NG 注文不能な商品 | 注文不能な数量 在庫確保 できない 最小公倍数な型に統一する 注文不能な商品 | 注文不能な数量 | 在庫確保できない
  40. © ZOZO, Inc. 53 Function<Input, Either<失敗理由, 注文>> _注文作成 = input

    -> 注文.create(input.userId, input.productId, input.quantity) .mapLeft( 失敗理由 -> switch (失敗理由) { case 注文不可能な商品 -> 失敗理由.注文不可能な商品; case 注文不可能な数量 -> 失敗理由.注文不可能な数量; }); 「注文作成」を変換する 注文作成 _注文作成 =
  41. © ZOZO, Inc. 54 「在庫確保」を変換する 在庫確保 _在庫確保 Function<注文, Either<失敗理由, Void>>

    _在庫確保 = 注文 -> 在庫確保.execute(注文).mapLeft(ignored -> 失敗理由.在庫確保できない); =
  42. © ZOZO, Inc. 56 永続化 「永続化」も連結(合成)する 注文作成 and 在庫確保 連結のために必要な条件

    ① Step1の戻り値のRightと Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致 考えなくて良い NG 空 注文 注文を返すようにする
  43. © ZOZO, Inc. 57 Function<注文, Either<失敗理由, 注文>> _在庫確保 = 注文

    -> 在庫確保.execute(注文) .mapLeft(ignored -> 失敗理由.在庫確保できない) .map(ignored -> 注文); 改めて、「在庫確保」を変換する 在庫確保 _在庫確保 =
  44. © ZOZO, Inc. 58 Function<注文, 注文> _永続化 = 注文 ->

    { 永続化.execute(注文); return 注文; }; 「永続化」を変換する _永続化 永続化 =
  45. © ZOZO, Inc. 59 「永続化」も連結(合成)する _永続化 注文作成 and 在庫確保 public

    Either<失敗理由, 注文> execute(Input input) { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); }
  46. © ZOZO, Inc. 63 「注文ユースケース」をROPで実装する public Either<失敗理由, 注文> execute(Input input)

    { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); } _永続化 _在庫確保 _注文作成
  47. © ZOZO, Inc. 66 public Either<失敗理由, 注文> execute(Input input) {

    Either<注文.失敗理由, 注文> 失敗理由Or注文 = 注文.create(input.userId, input.productId, input.quantity); if (失敗理由Or注文.isLeft()) return switch (失敗理由Or注文.getLeft()) { case 注文不可能な商品 -> Either.left(注文確定UseCase.失敗理由.注文不可能な商品); case 注文不可能な数量 -> Either.left(注文確定UseCase.失敗理由.注文不可能な数量); }; 注文 注文 = 失敗理由Or注文.get(); Either<在庫確保.失敗理由, Void> 失敗理由OrVoid = 在庫確保.execute(注文); if (失敗理由OrVoid.isLeft()) return Either.left(注文確定UseCase.失敗理由.在庫確保できない); 注文を保存.execute(注文); return Either.right(注文); } コードがエラーハンドリングに汚染されてハッピーパスがぼやける 【再掲】Eitherを使った戻り値でドメインエラーを表現するユースケースを素朴に実装する
  48. © ZOZO, Inc. 67 【再掲】Eitherを使った戻り値でドメインエラーを表現してROPで実装 ハッピーパス 注文作成 在庫確保 永続化 入力

    注文 public Either<失敗理由, 注文> execute(Input input) { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); } 失敗理由
  49. © ZOZO, Inc. public Either<失敗理由, 注文> execute(Input input) { return

    _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); } 68 Function<Input, Either<失敗理由, 注文>> _注文作成 = input -> 注文.create(input.userId, input.productId, input.quantity) .mapLeft( 失敗理由 -> switch (失敗理由) { case 注文不可能な商品 -> 失敗理由.注文不可能な商品; case 注文不可能な数量 -> 失敗理由.注文不可能な数量; }); Function<注文, 注文> _永続化 = 注文 -> { 永続化.execute(注文); return 注文; }; Function<注文, Either<失敗理由, 注文>> _在庫確保 = 注文 -> 在庫確保.execute(注文) .mapLeft(ignored -> 失敗理由.在庫確保できない) .map(ignored -> 注文); ※ 関数を変換する記述は必要です。 【再掲】Eitherを使った戻り値でドメインエラーを表現してROPで実装
  50. © ZOZO, Inc. 70 実業務においてJavaでROPを実践して得られた学び • ROPを実践すると必然的にFPのテクニックを利用することになる。自然とFPの 良さが体感でき、体得につながる。 ◦ >

    私は「具体的なことから始めて、抽象的なものへと進む」という 教育アプローチを強 く信じています。私の経験では、このアプローチに慣れると、より高度な抽象化が後か ら理解しやすくなります。 • Vavrの助けを借りても関数型の力を引き出すには限界がある。 • ROPを知らない人がコードを読み解く難易度はそこそこ高い。書くのも型パズルが 大変ではある。 ◦ 一度自分でROP的なコードを書くと読めるようになる。ペアプロやモブプロは必須。 ◦ 全てではなく、重要なユースケースにROPを導入する。 • チームとしてやる・やらないの意思決定をしっかり行う必要がある。 ◦ 個人的には、規約を増やして平準化ではなく、各人が自律して常に改善していく。プロ ダクトの価値につながりそうなことは前向きに色々試していけるチームが理想。 引用元: https://fsharpforfunandprofit.com/rop/ を 日本語翻訳
  51. © ZOZO, Inc. 71 まとめ • ドメインエラーはコードできちんと表現しよう • Javaでドメインエラーを表現するにはVavrのEitherが役に立つ •

    Eitherのハンドリングでハッピーパスがぼやけてきたら、ROPを取り入れみよう • 前向きに色々試していこうぜ!