『関数型ドメインモデリング』に関するメモ part2
2024-11-30 21:30:24
この記事は、Scott Wlaschinの著書『関数型ドメインモデリング』に関する個人的なメモです。 原著者の主張だけではなく、筆者(猪股)の解釈と考察を含んでいます。
関数型アプローチと「Repositoryパターン」
『エンタープライズアプリケーションアーキテクチャパターン』の「リポジトリパターン」
martinfowler.com より引用 日本語翻訳はGoogle Geminiによる。
リポジトリは、ドメイン層とデータマッピング層の間を仲介し、インメモリ・ドメインオブジェクトのコレクションのように機能します。 クライアントオブジェクトは、クエリ仕様を宣言的に構築し、それをリポジトリに提出し、満たしてもらいます。 オブジェクトは、単純なオブジェクトのコレクションのように、リポジトリに追加したり削除したりすることができ、 リポジトリによってカプセル化されたマッピングコードが、バックグラウンドで適切な操作を実行します。
概念的には、リポジトリは、データストアに永続化されたオブジェクトの集合とそのオブジェクトに対して実行される操作をカプセル化し、 永続化層のよりオブジェクト指向的なビューを提供します。 また、リポジトリは、ドメイン層とデータマッピング層の間の明確な分離と一方向の依存関係を実現するという目標もサポートします。
『関数型ドメインモデリング』第 12 章 「永続化」 より引用
「ドメイン駆動設計」の原書には、データベースにアクセスするためのパターンとして、リポジトリ パターンがあります。その本をご存知の方は、このパターンが関数型のアプローチとどのように結びつ くのか疑問に思うかもしれません。 その答えは、「結びつけなくてもよい」です。リポジトリパターンは、可変性を前提としたオブジェ クト指向設計において、永続化を隠すための便利な方法です。しかし、すべてを関数としてモデル化 し、永続化を端に追いやると、リポジトリパターンは必要なくなります。 この方法は保守性の面でも利点があります。なぜなら、1 つの I/O インターフェースに何十ものメ ソッドがあっても、特定のワークフローではそのほとんどを使うことはないからです。そこで、各 I/O アクセスごとに個別の関数を定義し、必要なときだけ使用するようにしているのです。
考察
原著者の主張は以下の2点だと理解しました。
- 永続化データへのアクセスはドメイン(境界づけられたコンテキスト)の端に置き、ドメインロジックは純粋に保つため、データアクセスをドメインレベルで抽象化する必要がない
- 永続化データへのアクセスパターンは複数ありえるが、それらを一つにまとめて定義・実装する意味がない
1. データアクセスをドメインレベルで抽象化する必要がない
概念的には、ビジネスワークフロー(または、ワークフローを処理ステップに分解したもの)を以下のように、純粋なビジネスロジックとI/Oに分けるべきだと本書で主張しています。
コード例を見ると、ワークフローを実装する「コマンドハンドラ」は、以下のような構造になっています。
ℹ️ Note
この話題の本筋ではありませんが、アプリケーションレイヤー(コマンドハンドラ)がデータベースレイヤーを直接呼び出しているのが気になる人もいると思います。 本書では、「関数を単独でテストしたい場合は…(略)…関数パラメーターを追加する」、 つまりデータベースレイヤーの関数を依存物として受け取るように設計してもよい、と書かれています。 関数を依存物として受け取るのは、つまりDIによる依存関係の逆転ですから、 クリーンアーキテクチャやオニオンアーキテクチャが想定する依存階層になるでしょう。
ビジネスロジックがある程度小さいなど、「頭に収まり、人間が理解できる」条件が整っていれば、筆者が言うように、純粋なビジネスロジックとI/Oに整理できるかもしれません。しかし、現実のアプリケーション開発で、はたしてそういう理想的な設計ができるのか?という疑いも、また正当なものではないかと思います。
たとえば、大規模で複雑な条件分岐を内包するロジックなど、入力となるデータを事前にすべてロードするのは非効率なケースもありそうです。そのようなケースでは、ビジネスロジック関数が依存物としてデータアクセス関数を受け取ることも合理的ではないでしょうか。それはつまり、ドメイン層と永続化層を明確に分離し、依存関係を一方向にするものですから、関数型プログラミングにおけるリポジトリパターンとみなせるでしょう。
2. 多様なデータアクセスを一つにまとめて定義・実装する意味がない
この主張には同意できます。たとえば、ある集約に対するCreate/Read/Update/Delete関数をすべてまとめた型、みたいなものは関数型プログラミングでは不要になるでしょうし、そうすべきとも思いません。
とはいっても、それぞれの関数の実装はどこに置くのか?という疑問は残ります。
もし、ドメイン(境界づけられたコンテキスト)レベルで共有するモジュールに置くのなら、それは、インターフェースという型はなくても、役割としてはリポジトリモジュールとみなせるように思います。 インターフェース型で抽象化はされてなくても、実装上はデータアクセスモジュールですね。
2024-11-30追記
原著者のScottに、そな太さんが質問したところ回答があったようです。
そな太 @sonatard
In section 12.1.2, it says the Repository pattern is unnecessary. Is this because pushing persistence to the edges keeps the domain pure and testable without mocking?
午前10:03 · 2024年11月30日
Scott Wlaschin @ScottWlaschin
Thank you for the kind words about my book! Regarding the Repository Pattern, yes, you are exactly right. If you keep IO at the edges you don’t normally need it. It might be useful sometimes but I think it is overused.
午後7:37 · 2024年11月30日