Slide 1

Slide 1 text

関数型DDDの理論と実践: 「決定を遅らせる」を先につくり、 ビジネスの機動力と価値をあげる Object-Oriented Conference 2024 knih

Slide 2

Slide 2 text

2 アジェンダ ● 決定を遅らせる、適切なタイミングで ● 関数型アプローチとドメイン ● 副作用とアーキテクチャとDDD ● 関数型のリスクと展望 ● まとめ

Slide 3

Slide 3 text

決定を遅らせる 3

Slide 4

Slide 4 text

プロダクトスケール 湯浅エムレ秀和(2021). シリーズA. https://note.com/emreyuasa/n/n03aca5b7a981 4

Slide 5

Slide 5 text

不確実性に立ち向かうための仮説検証サイクル 5 IPA (2021). アジャイル領域へのスキル変革の指針. 広木大地 (2018). エンジニアリング組織論への招待. 技術評論社.

Slide 6

Slide 6 text

アーキテクチャに柔軟性がないと・・・ 6 https://philippe.kruchten.com/2013/12/11/the-missing-val ue-of-software-architecture/ こっちに目がいきがち 後回しになりがち

Slide 7

Slide 7 text

リスクへの対応がズルズル後回しになりやすい 7

Slide 8

Slide 8 text

リスクと問題 潜在 顕在 未 対 応 対 応 リスク 問題 課題 対応を決めた リスク インシデント 課題設定 リスク判定 応急措置 消⽕のマネジメント 防⽕のマネジメント 消火活動に追われると、健全な開発が困難になる 8

Slide 9

Slide 9 text

適切なタイミングで決定したい 手遅れになると悪循環に陥りやすくなる 時間 コスト 最終責任点 (Lost Responsible Moment; LRM) Tom Poppendieck, Mary Poppendieck. Lean Software Development: An Agile Toolkit. Addison-Wesley Professional. 2003. を参考に作成 誤ったものを 作るリスク 機会損失の リスク ⼿戻りコスト 決定コスト 9

Slide 10

Slide 10 text

ビジネスのスピードを落とさないために 10 ● 理想的にはサンクコスト効果を感じないぐらいの柔軟性 がほしい ● 変更失敗率はあげたくない ○ 変更による影響の不安で、決定が遅れることを避けたい

Slide 11

Slide 11 text

11 高いモジュラリティ 高い安全性

Slide 12

Slide 12 text

12 そこで

Slide 13

Slide 13 text

13 関数型 DDD

Slide 14

Slide 14 text

関数型アプローチとドメイン 14

Slide 15

Slide 15 text

15 Eric Evans (2015). Domain-Driven Design Reference.

Slide 16

Slide 16 text

関数型 16 ● 関数を組み合わせて目的のプログラムを構成するアプローチ ● 関数と関数合成の性質によってモジュラリティの高いプログラムになる ○ SaaSプロダクト開発等、解像度を上げながら細かく改修していく場面で非常に有用 ○ (強力な型システムと組み合わせると安全性の高い開発がしやすくなる) ● 宣言的スタイル ● 純粋関数(副作用を避ける) ● イミュータブル ● 参照透過性

Slide 17

Slide 17 text

関数型の良いところ(個人主観) ● 予測可能な設計にしやすい ● ハイレベルなモジュラリティ 17 その他の利点: ● 再利用性と高いテスタビリティ ● 保守性が高い ● より良い抽象化と柔軟性 ● 型安全性(実行時エラーを減らす) ● 並行性・並列性 ● 合成可能性

Slide 18

Slide 18 text

関数 18 集合 A の各々の要素に対して、集合 B の要素に対応させる仕方 A B 入力として取りうる値の集まり 出力しうる値の集まり f: A → B と書く f fun f(a: A): B = … val f: (A) -> B = … domain 定義域、始域 codomain 余域、終域

Slide 19

Slide 19 text

関数 入力に対して結果が 一意に定まっている (決定的) 同じ入力に対して 結果が一意に定まらない a1 a2 a3 b1 b2 b3 関数の例 a1 a2 a3 b1 b2 b3 A B 関数ではない例 A B 19

Slide 20

Slide 20 text

関数合成 20 f: A → B g: B → C g○f: A → C 関数f:A→Bおよびg:B→Cがあるとき、fとgの合成 g○f:A→Cは以下に定義される 図式化すると… A B C f g g○f

Slide 21

Slide 21 text

全域関数と部分関数 21 a3 (1)全域関数の例 a2 a1 b3 b2 b1 再利用性の高いドメインモジュー ルは全域関数化しましょう A B a3 (2)部分関数の例 a2 a1 b3 b2 b1 A B プログラム的には例外になる。 (クラッシュしたり、処理が終わ らなかったりする場合も含まれ る) ?

Slide 22

Slide 22 text

部分関数は割れ窓 22 部分関数f 全域関数g 合成関数 g.f A B? B C A C? 例外が伝播する

Slide 23

Slide 23 text

例外を出す関数は部分関数 関数を全域化する 23 a3 a2 a1 b3 b2 b1 A B ? a3 a2 a1 b3 b2 b1 A B None たとえば例外 Throwable 例外をなくした f: A → Option[B] f; A → B (例外が出る)

Slide 24

Slide 24 text

エラーを表現するために使える型 Option[A] Either[A,B] Try[A] 24

Slide 25

Slide 25 text

合成可能性によって、プログラムに柔軟性がもたらされる 品質を担保しながら、プログラムを容易に組み換えできる 25 関数の単位でデータ変換の 正しさが保証されている f: A → B g: B → C g○f: A → C A B C f g g○f

Slide 26

Slide 26 text

品質を担保しながら、プログラムを容易に組み換えできる 合成可能性によって、プログラムに柔軟性がもたらされる 26 関数の単位でデータ変換の 正しさが保証されている f: A → B h: B → D g○h: A → D A B C f g g○h D h g: B → C 組み換えが容易 A C

Slide 27

Slide 27 text

代数的データ型=直積・直和の性質を持つデータ型 27 直積 (direct product) 直和 (direct sum) 代数的データ型 (Algebraic Data Types; ADTs)

Slide 28

Slide 28 text

代数的データ型=直積・直和の性質を持つデータ型 28 ● 直積(direct production, Cartesian product, デカルト積) ● 集合Aの要素aと、集合Bの要素bから作られるペア(a,b)の全体から成る集合 ○ A = {a,b,c}、B={1,2} A×B = {(a,1),(a,2), (b,1),(b,2), (c,1),(c,2)} case class RGB( red: Int, green: Int, blue: Int) 256 × 256 × 256 16,777,216通り 例1 例2

Slide 29

Slide 29 text

代数的データ型=直積・直和の性質を持つデータ型 29 ● 直和(direct sum, sum) ※Aから来た要素か、Bから来た要素かを区別するために、0や1 等のタグを付与する表現もある A⊕B = { (0,a) | a ∈ A } ∪ { (1,b) | b ∈ B } A⊕B = { a | a ∈ A } ∪ { (b) | b ∈ B }   ただし A ∩ B = { 0 } A = {a,b,c}、B={1,2} A⊕B = {a,b,c,1,2} 例1 例2 enum Color: case Red, Green, Blue, Yellow, Cyan 1+1+1+1+1 5通り

Slide 30

Slide 30 text

型=値の集合 30 enum ContactInfo: case Email(value: EmailAddress) case Tel(value: TelephoneNumber) case EmailAndTel(email: EmailAddress, tel: TelephoneNumber) ) EmailAndTel Tel Email b3 b2 b1 A B f: ContractInfo → B EmailもTelも指定され ないケースが発生しない ことを保証できる

Slide 31

Slide 31 text

代数的データ型=直積・直和の性質を持つデータ型 31 直積 直和 問題空間を適切に表現しうる 安全性の高い型表現できる 不正な値がドメインの中 に入り込みづらくなる データの質を コントロールしやすい

Slide 32

Slide 32 text

データ変換を実装するうえで重要な、参照透過性 ● 純粋関数 ○ 全域性があり、副作用がない ● 決定的 ○ 同じインプットを与えれば、常に同じアウトプットが返ってくる ● 冪等性 ○ 複数回適用しても同じ結果が得られる 32

Slide 33

Slide 33 text

副作用とアーキテクチャとDDD 33

Slide 34

Slide 34 text

(再掲)適切なタイミングで決定したい 悪循環に陥る 時間 コスト 最終責任点 (Lost Responsible Moment; LRM) Tom Poppendieck, Mary Poppendieck. Lean Software Development: An Agile Toolkit. Addison-Wesley Professional. 2003. を参考に作成 誤ったものを 作るリスク 機会損失の リスク ⼿戻りコスト 決定コスト 34

Slide 35

Slide 35 text

必要になるまで決定を遅らせつつ、 いざ決めたらビジネスのスピードを落とさずに それを実現したい 35

Slide 36

Slide 36 text

継続的アーキテクチャ 36 以下の6原則に従ったアーキテクチャアプローチ 参考:Murat Erder (2015). Continuous Architecture: Sustainable Architecture in an Agile and Cloud-Centric World. Morgan Kaufmann. 1. プロジェクトではなく、プロダクトを設計 2. 機能要件ではなく、品質属性にフォーカス 3. 必要になるまで、設計の決定を遅らせる 4. 変化のために設計する — “小さな力”を活用する 5. ビルド、テスト、デプロイのための設計 6. システムの設計後に組織をモデル化 顧客に集中する 品質属性の要件がアーキテクチャ を推進 使われない無駄を避ける 小規模で疎結合に 継続的デリバリーも考慮 チームの編成方法がアーキテク チャと設計を駆動する

Slide 37

Slide 37 text

全域関数は合成可能性が高い しかし、現実的なプログラムには副作用がある、いかに制御するか 副作用をいかにコントロールするか Core Shell 純粋関数 イミュータブル 不純関数(副作用を扱う) ミュータブル Call 37 ドメインモデル 副作用を どうするか? (参考)副作用 DBアクセス、ファイル読み書き、 ネットワークリクエスト等々

Slide 38

Slide 38 text

副作用はバグの温床 38 “もっと一般的ないいかたをすれば、関数プログラムには 全く副作用がない。関数の呼出しは、結果を計算する以外 の作用は もたない。このことは、バグの大きな源のひとつ を断つ。 More generally, functional programs contain no side-effects at all. A function call can have no effect other than to compute its result. This eliminates a major source of bugs Hughes, J.. "Why Functional Programming Matters." Computer Journal 32 , no. 2 (1989): 98--107.

Slide 39

Slide 39 text

副作用のコントロール Effect Tracking ● 副作用を型で表現し、副作用の発生を型システムを通じてトラッキングできるように するプラクティス ● 安全で予測可能な設計がしやすくなる Effect Wrapper ● 副作用を含む操作をラップし、それらを純粋関数のコンテキストで扱えるようにするため の抽象化 ● 副作用を含む操作も純粋関数のように、合成や変換ができる 39

Slide 40

Slide 40 text

Clean Architecture と関数型 Entities Controller G atew ay Presenter Use Cases UI W eb Devices D B External Interfaces Martin, R. C. (2017). Clean Architecture: A Craftsman's Guide to Software Structure and Design. Prentice Hall. 40 A → B A → Option[B] A → Either[E, B]

Slide 41

Slide 41 text

Clean Architecture と関数型 Entities Controller G atew ay Presenter Use Cases UI W eb Devices D B External Interfaces Martin, R. C. (2017). Clean Architecture: A Craftsman's Guide to Software Structure and Design. Prentice Hall. 41 副作用が現れる A → F[B] A → F[Option[B]] A → F[Either[E, B]]

Slide 42

Slide 42 text

副作用を箱にいれる 42 a2 a1 b3 b2 b1 A F[B] f: A → F[B] この箱にはなにか不思議な力 があるようです

Slide 43

Slide 43 text

副作用のある関数の合成 43 f: A → F[B] g: B → F[C] g>>=f: A → F[C] A F[B] f g>>=f F[C] g B A F[C]

Slide 44

Slide 44 text

副作用をコントロールする手法 44 Tagless-final Freeモナド ZIO(Scala) 例外 概要 コンビネーターアプローチと 高階多相を利用して、型安 全にDSLを表現する手法。 計算の実行を遅延させるこ とで副作用を持つ操作を 純粋な値(ツリー構造)で 表現。 独自の型を使って、 エラーや環境の依存 注入を表現するライ ブラリ。 (いい感じのReaderT とCakeパターン) 例外 メリット 抽象化力は高い。 タグを使わないので速い。 実はプログラム変換できる。 任意のファンクターを取り、 モナドにできる エコシステムが充 実。エラー処理や並 行処理がしやすく、 実用的。 おなじみ デメリット 異なるエフェクトを持つ計算 同士を合成していくと、型が すごいことになる パフォーマンスのオーバー ヘッドが生じる 初見だと魔術に見える Effect Wrapper? 合成可能性や計 算順序等々 ※まとめきれていないので話半分ぐらいで受け止めてください

Slide 45

Slide 45 text

45 Eric Evans (2015). Domain-Driven Design Reference.

Slide 46

Slide 46 text

46 trait UserRepository[F[_]] { def findOneById(userId: UserId): F[Option[User]] } class UserService[F[_]](userRepository: UserRepository[F]) (implicit ME: MonadError[F, Error]) { def getName(userId: String) = { val userOptF: F[Option[User]] = userRepository.findOneById(userId) Monad[F].map(userOptF) { userOpt => for { user <- userOpt } yield user.name } } } Tagless-finalの例 f: A → F[B]

Slide 47

Slide 47 text

実装都合の副作用が、型としてパラメータ化 47 object UserRepositoryOnJdbc extends UserRepository[IO] { override def findOneById(userId: UserId): IO[Option[User]] = ??? } object UserRepositoryOnApi extends UserRepository[Future] { override def findOneById(userId: UserId): Future[Option[User]] = ??? } object UserRepositoryOnId extends UserRepository[Id] { override def findOneById(userId: UserId): Id[Option[User]] = ??? } 他のサービスを呼び出すか、 DBにアクセスするか 同期で呼び出すか、 非同期で呼び出すか等々

Slide 48

Slide 48 text

さまざまな決定からドメインモデルを守る 48 ● PostgreSQLに問い合わせるか、BigQueryに問い合 わせるか、インメモリをみるか ● 同期にするか、非同期にするか ● 並列にするか、並行にするか ● あのライブラリがいいか、このライブラリがいいか

Slide 49

Slide 49 text

関数型のリスクと展望 49

Slide 50

Slide 50 text

関数型のリスク 50 ● 学習コストはそれなりにある ○ 背景の理屈を理解するのが難しい、かもしれない ● 副作用をコントロールすることで、様々なテクニックや有益なアルゴリズ ム、特性を取り入れることができる ○ 計算ストリームの合成、抽象化を維持しながら高速化等 ○ やり過ぎ注意 ○ 品質属性要件と相談しよう ● チーム開発時のオンボーディング、スケールコスト ○ ポイントさえ押さえれば大層な話ではない ● NULL恐怖症

Slide 51

Slide 51 text

感想 51 ● 副作用が分離されるので、脆弱性に強い ● 副作用が分離されるので、ドメインに専念する人と、外部サービスやDB に専念する人を分けられる ● 型が浸透しているとコンパイルしたときの安心感が違うので、積極的にリ ファクタリング・リアーきできるようになる ○ 技術的負債が溜まりにくい ● オンボーディングのために、あえて型をゆるくする領域をつくる ○ クリティカルなドメインは手堅くつくる(コアサブドメイン、コンテキストマップ上の依存) ● 開発のスケールはクリティカルな問題 ● エンタープライズ基幹システムと相性がいい ○ 日本のレガシー問題に立ち向かう武器になりうる

Slide 52

Slide 52 text

展望 ● 型に導かれるので目的が明瞭な設計にしやすい ○ 型をみればなんとなく分かる ○ String → String はわからないけど、UserId → Tenant はわかる ● モジュラー+安全性、ドメイン全体の代数化 ○ APIやロジックの安全な生成 ○ AIフレンドリー ● 挙動の予測がしやすい ○ サービス運用の自動化 ● 適切なタイミングでの決定 ○ 決定から実装までのリードタイム短縮 52

Slide 53

Slide 53 text

まとめ 53

Slide 54

Slide 54 text

まとめ 54 ● 小さく疎結合にしつつ、適切な型を与えることによって、 モジュラリティと安全性が高まり、決定を遅らせることが できる ○ モジュラリティが高まることで、ドメインコンポーネントの組み換えがし やすくなる(手戻り影響の低減) ○ 副作用をも抽象化することで、方式変更の影響を低減

Slide 55

Slide 55 text

EOF 55