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

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

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

Kenichi SUZUKI

March 24, 2024
Tweet

More Decks by Kenichi SUZUKI

Other Decks in Technology

Transcript

  1. リスクと問題 潜在 顕在 未 対 応 対 応 リスク 問題

    課題 対応を決めた リスク インシデント 課題設定 リスク判定 応急措置 消⽕のマネジメント 防⽕のマネジメント 消火活動に追われると、健全な開発が困難になる 8
  2. 適切なタイミングで決定したい 手遅れになると悪循環に陥りやすくなる 時間 コスト 最終責任点 (Lost Responsible Moment; LRM) Tom

    Poppendieck, Mary Poppendieck. Lean Software Development: An Agile Toolkit. Addison-Wesley Professional. 2003. を参考に作成 誤ったものを 作るリスク 機会損失の リスク ⼿戻りコスト 決定コスト 9
  3. 関数型の良いところ(個人主観) • 予測可能な設計にしやすい • ハイレベルなモジュラリティ 17 その他の利点: • 再利用性と高いテスタビリティ •

    保守性が高い • より良い抽象化と柔軟性 • 型安全性(実行時エラーを減らす) • 並行性・並列性 • 合成可能性
  4. 関数 18 集合 A の各々の要素に対して、集合 B の要素に対応させる仕方 A B 入力として取りうる値の集まり

    出力しうる値の集まり f: A → B と書く f fun f(a: A): B = … val f: (A) -> B = … domain 定義域、始域 codomain 余域、終域
  5. 関数合成 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
  6. 全域関数と部分関数 21 a3 (1)全域関数の例 a2 a1 b3 b2 b1 再利用性の高いドメインモジュー

    ルは全域関数化しましょう A B a3 (2)部分関数の例 a2 a1 b3 b2 b1 A B プログラム的には例外になる。 (クラッシュしたり、処理が終わ らなかったりする場合も含まれ る) ?
  7. 例外を出す関数は部分関数 関数を全域化する 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 (例外が出る)
  8. 代数的データ型=直積・直和の性質を持つデータ型 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
  9. 代数的データ型=直積・直和の性質を持つデータ型 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通り
  10. 型=値の集合 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も指定され ないケースが発生しない ことを保証できる
  11. (再掲)適切なタイミングで決定したい 悪循環に陥る 時間 コスト 最終責任点 (Lost Responsible Moment; LRM) Tom

    Poppendieck, Mary Poppendieck. Lean Software Development: An Agile Toolkit. Addison-Wesley Professional. 2003. を参考に作成 誤ったものを 作るリスク 機会損失の リスク ⼿戻りコスト 決定コスト 34
  12. 継続的アーキテクチャ 36 以下の6原則に従ったアーキテクチャアプローチ 参考:Murat Erder (2015). Continuous Architecture: Sustainable Architecture

    in an Agile and Cloud-Centric World. Morgan Kaufmann. 1. プロジェクトではなく、プロダクトを設計 2. 機能要件ではなく、品質属性にフォーカス 3. 必要になるまで、設計の決定を遅らせる 4. 変化のために設計する — “小さな力”を活用する 5. ビルド、テスト、デプロイのための設計 6. システムの設計後に組織をモデル化 顧客に集中する 品質属性の要件がアーキテクチャ を推進 使われない無駄を避ける 小規模で疎結合に 継続的デリバリーも考慮 チームの編成方法がアーキテク チャと設計を駆動する
  13. 副作用はバグの温床 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.
  14. 副作用のコントロール Effect Tracking • 副作用を型で表現し、副作用の発生を型システムを通じてトラッキングできるように するプラクティス • 安全で予測可能な設計がしやすくなる Effect Wrapper

    • 副作用を含む操作をラップし、それらを純粋関数のコンテキストで扱えるようにするため の抽象化 • 副作用を含む操作も純粋関数のように、合成や変換ができる 39
  15. 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]
  16. 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]]
  17. 副作用を箱にいれる 42 a2 a1 b3 b2 b1 A F[B] f:

    A → F[B] この箱にはなにか不思議な力 があるようです
  18. 副作用のある関数の合成 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]
  19. 副作用をコントロールする手法 44 Tagless-final Freeモナド ZIO(Scala) 例外 概要 コンビネーターアプローチと 高階多相を利用して、型安 全にDSLを表現する手法。

    計算の実行を遅延させるこ とで副作用を持つ操作を 純粋な値(ツリー構造)で 表現。 独自の型を使って、 エラーや環境の依存 注入を表現するライ ブラリ。 (いい感じのReaderT とCakeパターン) 例外 メリット 抽象化力は高い。 タグを使わないので速い。 実はプログラム変換できる。 任意のファンクターを取り、 モナドにできる エコシステムが充 実。エラー処理や並 行処理がしやすく、 実用的。 おなじみ デメリット 異なるエフェクトを持つ計算 同士を合成していくと、型が すごいことになる パフォーマンスのオーバー ヘッドが生じる 初見だと魔術に見える Effect Wrapper? 合成可能性や計 算順序等々 ※まとめきれていないので話半分ぐらいで受け止めてください
  20. 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]
  21. 実装都合の副作用が、型としてパラメータ化 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にアクセスするか 同期で呼び出すか、 非同期で呼び出すか等々
  22. 関数型のリスク 50 • 学習コストはそれなりにある ◦ 背景の理屈を理解するのが難しい、かもしれない • 副作用をコントロールすることで、様々なテクニックや有益なアルゴリズ ム、特性を取り入れることができる ◦

    計算ストリームの合成、抽象化を維持しながら高速化等 ◦ やり過ぎ注意 ◦ 品質属性要件と相談しよう • チーム開発時のオンボーディング、スケールコスト ◦ ポイントさえ押さえれば大層な話ではない • NULL恐怖症
  23. 感想 51 • 副作用が分離されるので、脆弱性に強い • 副作用が分離されるので、ドメインに専念する人と、外部サービスやDB に専念する人を分けられる • 型が浸透しているとコンパイルしたときの安心感が違うので、積極的にリ ファクタリング・リアーきできるようになる

    ◦ 技術的負債が溜まりにくい • オンボーディングのために、あえて型をゆるくする領域をつくる ◦ クリティカルなドメインは手堅くつくる(コアサブドメイン、コンテキストマップ上の依存) • 開発のスケールはクリティカルな問題 • エンタープライズ基幹システムと相性がいい ◦ 日本のレガシー問題に立ち向かう武器になりうる
  24. 展望 • 型に導かれるので目的が明瞭な設計にしやすい ◦ 型をみればなんとなく分かる ◦ String → String はわからないけど、UserId

    → Tenant はわかる • モジュラー+安全性、ドメイン全体の代数化 ◦ APIやロジックの安全な生成 ◦ AIフレンドリー • 挙動の予測がしやすい ◦ サービス運用の自動化 • 適切なタイミングでの決定 ◦ 決定から実装までのリードタイム短縮 52