Slide 1

Slide 1 text

Javaに鉄道指向プログラミング (Railway Oriented Programming) のエッセンスを取り入れる 株式会社ZOZO 岡本 拓実 Copyright © ZOZO, Inc. 1 JJUG CCC 2025 Spring 2025-06-07

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO 基幹リプレイスブロック 岡本 拓実 2020年4月に株式会社ZOZOに新卒入社 VBScriptで書かれたレガシー環境での開発・運用を経て、現在は リプレイスプロジェクトに従事しモダンなJavaを書いています。 初のJJUG CCC登壇です。お手柔らかに。 2

Slide 3

Slide 3 text

© ZOZO, Inc. 3 エラー処理に対する関数型のアプローチ 鉄道指向プログラミング(ROP: Railway Oriented Programming)とは コードを汚染することなく、エレガントにエラーを補足する実践的なテクニック。 関数を線路にみたて連結することでユースケースを構築するため、鉄道指向プログラ ミングと呼ばれている。 引用元: https://fsharpforfunandprofit.com/rop/

Slide 4

Slide 4 text

© ZOZO, Inc. 4 エラー処理の話であり、関数型プログラミング(FP: Functional Programming)をJavaでどう実践するのか?という内容です。 ● 主な対象者 ○ FPやROPに興味あるが、実践経験の少ないJavaプログラマ ● 発表のスタンス ○ あえて小難しい用語は使わず、具体例やコードを示して実践的な内容を目指 す ○ JavaにFPやROPのエッセンスを取り入れる一歩目につなげたい 本発表について

Slide 5

Slide 5 text

© ZOZO, Inc. 5 目次 ● Javaでドメインエラーを表現する ● JavaでROPを実践する ● 実業務においてJavaでROPを実践して得られた学び ● まとめ 【注意】本題のROPの話題に入るまでに10分ほどかかります。

Slide 6

Slide 6 text

© ZOZO, Inc. 6 Javaでドメインエラーを表現する

Slide 7

Slide 7 text

© ZOZO, Inc. 7 ビジネスプロセスの一部として 発生が予想される? ドメインエラー パニック / インフラエラー Yes No 例: ユーザー名は1~50文字であるべきだが、 60文字がリクエストされた。 例: メモリ不足やnull参照、 ネットワークタイムアウトなど。 ドメインエラーとは

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

© ZOZO, Inc. 14 本発表で扱う題材: 注文ユースケース 注文 注文作成 入力 ・注文可能な商品か? ・注文可能な数量か? 検証NG ファクトリメソッド 検査例外 注文不可能な商品 注文不可能な数量 public class 注文不能な商品 extends Exception { public 注文不能な商品(){ super("注文不能な商品です"); } } public class 注文不能な数量 extends Exception { public 注文不能な数量() { super("注文不能な数量です"); } }1

Slide 15

Slide 15 text

© 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); } } 検査例外でドメインエラーを表現する

Slide 16

Slide 16 text

© ZOZO, Inc. 16 業務システムにはドメインエラーがたくさん 必然的に 検査例外を多用 することになる 【再掲】本発表で扱う題材: 注文ユースケース 在庫確保 できない ハッピーパス 注文作成 在庫確保 永続化 入力 注文 ・注文可能な商品か? ・注文可能な数量か? 検証NG ドメインエラー

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

© ZOZO, Inc. 21 改めて、Javaでのドメインエラーの表現を考える 注文 注文作成 入力 ・注文可能な商品か? ・注文可能な数量か? 検証NG 戻り値の型で 注文・検証NG のどちらか が返ることを表現する ドメインエラー 注文不可能な商品 注文不可能な数量

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

© 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を使った戻り値でドメインエラーを表現する

Slide 25

Slide 25 text

© 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 失敗理由 { 注文不可能な商品, 注文不可能な数量 } 列挙型

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

© 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を使った戻り値でドメインエラーを表現するユースケースを素朴に実装する

Slide 30

Slide 30 text

© 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(注文); }

Slide 31

Slide 31 text

© 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(注文); }

Slide 32

Slide 32 text

© 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を使った戻り値でドメインエラーを表現するユースケースを素朴に実装する

Slide 33

Slide 33 text

© ZOZO, Inc. 33 JavaでROPを実践する

Slide 34

Slide 34 text

© ZOZO, Inc. 34 エラー処理に対する関数型のアプローチ。 【再掲】鉄道指向プログラミング(ROP: Railway Oriented Programming)とは コードを汚染することなく、エレガントにエラーを補足する実践的なテクニック。 関数を線路にみたて連結することでユースケースを構築するため、鉄道指向プログラ ミングと呼ばれている。 引用元: https://fsharpforfunandprofit.com/rop/

Slide 35

Slide 35 text

© ZOZO, Inc. 35 関数を線路のメタファーで理解する: 1. 関数とは 関数とは、入力(引数の型の値)を出力(戻り値の型の値)に変換するもの 関数 色の違いで型の違いを表現している

Slide 36

Slide 36 text

© ZOZO, Inc. 36 関数を線路のメタファーで理解する: 2. 関数合成 「ある関数の出力の型」と、「別のある関数の入力の型」が一致してい れば連結できる 一致

Slide 37

Slide 37 text

© ZOZO, Inc. 37 関数を線路のメタファーで理解する: 2. 関数合成 「ある関数の出力の型」と、「別のある関数の入力の型」が一致してい れば連結できる 一致 Green hoge(Gray input); Blue fuga(Green input); Blue hogeFuga(Gray input){ return fuga(hoge(input)); }

Slide 38

Slide 38 text

© ZOZO, Inc. 38 関数を線路のメタファーで理解する: 3. スイッチ関数 Eitherを返すスイッチ関数は2分岐の線路として解釈できる。 Eitherを返すスイッチ関数からユースケースを構築することを考える ことは、2分岐の線路をどのように連結するのかを考えること。

Slide 39

Slide 39 text

© ZOZO, Inc. 39 ROPでやりたいこと

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

© ZOZO, Inc. 44 2つのスイッチ関数を連結(合成)する // step1を実行 Either step1Output = step1(input); Either 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と同じ線路で合流する

Slide 45

Slide 45 text

© ZOZO, Inc. 45 Either = step1(input).flatMap(step2) これが等価 2つのスイッチ関数を連結(合成)する // step1を実行 Either step1Output = step1(input); Either 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と同じ線路で合流する

Slide 46

Slide 46 text

© ZOZO, Inc. public interface Either extends Value, Serializable { default Either flatMap( Function> mapper) { Objects.requireNonNull(mapper, "mapper is null"); if (isRight()) { return (Either) mapper.apply(get()); } else { return (Either) 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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

© ZOZO, Inc. 48 注文作成 在庫確保 保存する 「注文ユースケース」をROPで実装する

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

© ZOZO, Inc. 54 「在庫確保」を変換する 在庫確保 _在庫確保 Function<注文, Either<失敗理由, Void>> _在庫確保 = 注文 -> 在庫確保.execute(注文).mapLeft(ignored -> 失敗理由.在庫確保できない); =

Slide 55

Slide 55 text

© ZOZO, Inc. 55 Either<失敗理由, Void> output = _注文作成.apply(input).flatMap(_在庫確保) 「_注文作成」と「_在庫確保」を連結(合成)する _注文作成 _在庫確保 注文作成 and 在庫確保

Slide 56

Slide 56 text

© ZOZO, Inc. 56 永続化 「永続化」も連結(合成)する 注文作成 and 在庫確保 連結のために必要な条件 ① Step1の戻り値のRightと Step2の入力の型が一致 ② Step1の戻り値のLeftと Step2の戻り値のLeftの型が一致 考えなくて良い NG 空 注文 注文を返すようにする

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

© ZOZO, Inc. 58 Function<注文, 注文> _永続化 = 注文 -> { 永続化.execute(注文); return 注文; }; 「永続化」を変換する _永続化 永続化 =

Slide 59

Slide 59 text

© ZOZO, Inc. 59 「永続化」も連結(合成)する _永続化 注文作成 and 在庫確保 public Either<失敗理由, 注文> execute(Input input) { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); }

Slide 60

Slide 60 text

© ZOZO, Inc. 60 「注文ユースケース」をROPで実装する 永続化 在庫確保 注文作成

Slide 61

Slide 61 text

© ZOZO, Inc. 61 「注文ユースケース」をROPで実装する _永続化 _在庫確保 _注文作成 永続化 在庫確保 注文作成

Slide 62

Slide 62 text

© ZOZO, Inc. 62 「注文ユースケース」をROPで実装する 永続化 在庫確保 注文作成 _永続化 _在庫確保 _注文作成

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

© ZOZO, Inc. 64 map mapLeft flatMap fold and more! Eitherの操作まとめ

Slide 65

Slide 65 text

© ZOZO, Inc. 65 素朴な実装とROPを用いた実装を比較する

Slide 66

Slide 66 text

© 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を使った戻り値でドメインエラーを表現するユースケースを素朴に実装する

Slide 67

Slide 67 text

© ZOZO, Inc. 67 【再掲】Eitherを使った戻り値でドメインエラーを表現してROPで実装 ハッピーパス 注文作成 在庫確保 永続化 入力 注文 public Either<失敗理由, 注文> execute(Input input) { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); } 失敗理由

Slide 68

Slide 68 text

© ZOZO, Inc. public Either<失敗理由, 注文> execute(Input input) { return _注文作成.apply(input).flatMap(_在庫確保).map(_永続化); } 68 Function> _注文作成 = input -> 注文.create(input.userId, input.productId, input.quantity) .mapLeft( 失敗理由 -> switch (失敗理由) { case 注文不可能な商品 -> 失敗理由.注文不可能な商品; case 注文不可能な数量 -> 失敗理由.注文不可能な数量; }); Function<注文, 注文> _永続化 = 注文 -> { 永続化.execute(注文); return 注文; }; Function<注文, Either<失敗理由, 注文>> _在庫確保 = 注文 -> 在庫確保.execute(注文) .mapLeft(ignored -> 失敗理由.在庫確保できない) .map(ignored -> 注文); ※ 関数を変換する記述は必要です。 【再掲】Eitherを使った戻り値でドメインエラーを表現してROPで実装

Slide 69

Slide 69 text

© ZOZO, Inc. 69 まとめに入ります

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

© ZOZO, Inc. 71 まとめ ● ドメインエラーはコードできちんと表現しよう ● Javaでドメインエラーを表現するにはVavrのEitherが役に立つ ● Eitherのハンドリングでハッピーパスがぼやけてきたら、ROPを取り入れみよう ● 前向きに色々試していこうぜ!

Slide 72

Slide 72 text

No content