関数型プログラミングの文脈で貧血モデルについて話すことはまだ有効ですか?


40

DDDの戦術的なデザインパターンのほとんどはオブジェクト指向のパラダイムに属し、貧弱なモデルは、すべてのビジネスロジックがオブジェクトではなくサービスに置かれ、一種のDTOになっている状況を表します。言い換えると、貧血モデルは手続き型の同義語であり、複雑なモデルにはお勧めできません。

純粋な関数型プログラミングの経験はあまりありませんが、DDDがFPパラダイムにどのように適合し、その場合に「貧血モデル」という用語がまだ存在するかどうかを知りたいです。

更新:Recentlry は、このテーマに関するビデオを公開しました。


1
私があなたがここで言っていると思うことを言っているなら、DTOは貧血ですが、DDDのファーストクラスのオブジェクトであり、DTOとそれらを処理するサービスの間には自然な分離があります。原則的に同意します。このブログ投稿もそうです。
ロバートハーヴェイ


1
「その場合、「貧血モデル」という用語がまだ存在するかどうか」簡単に答えると、貧血モデルの用語はオブジェクト指向の文脈で作られました。FPのコンテキストで貧血モデルと言えば、まったく意味がありません。慣用的なFPとは何かを説明する意味で同等のものがあるかもしれませんが、貧血モデルとは何の関係もありません。
plalx

5
エリック・エヴァンスはかつて彼が彼の本で説明するのは良いオブジェクト指向のデザインであると彼を非難する人々に言うことを尋ねられ、彼はそれが非難ではなく、真実であり、DDDはちょうど良いOODであると答えましたいくつかのレシピとパターンを書き留めて名前を付けたので、それらをフォローして話しやすくなりました。したがって、DDDがOODにリンクされていることは驚くことではありません。より広範な質問は、OODとFPDの共通部分と相違点となるでしょうが、最初に「関数型プログラミング」の意味を定義する必要があります。
ヨルグWミットタグ

2
@JörgWMittag:通常の定義以外の意味ですか?Haskellが最も明白なプラットフォームである多くの実例となるプラットフォームがあります。
ロバートハーヴェイ

回答:


24

「貧血モデル」問題の記述方法は、そのままではFPにうまく変換されません。まず、適切に一般化する必要があります。本質的に、貧血モデルとは、モデル自体によってカプセル化されていない、適切な使用方法に関する知識を含むモデルです。代わりに、その知識は関連するサービスの山に広がっています。これらのサービスはモデルのクライアントのみである必要がありますが、貧血のために彼らはそれに対して責任負います。たとえば、Accountクラスを介して処理されない限り、アカウントのアクティブ化または非アクティブ化、またはアカウントに関する情報の検索にも使用できないクラスを考えますAccountManager。アカウントは、外部マネージャークラスではなく、その基本操作を担当する必要があります。

関数型プログラミングでは、データ型がモデル化すべきものを正確に表していない場合に同様の問題が存在します。ユーザーIDを表す型を定義する必要があるとします。「貧弱な」定義は、ユーザーIDが文字列であることを示します。技術的には実現可能ですが、ユーザーID 任意の文字列のように使用されないため、大きな問題に直面します。それらを連結したり、それらの部分文字列を切り取ったりすることは意味がありません。Unicodeは実際には重要ではなく、文字や形式の制限が厳しいURLやその他のコンテキストに簡単に埋め込むことができます。

この問題の解決は通常、数段階で行われます。簡単な最初のカットは、「まあ、a UserIDは文字列と同等に表されますが、それらは異なるタイプであり、一方を期待する場所で使用することはできません」と言うことです。Haskell(およびその他の型付き関数型言語)は、この機能をnewtype次の方法で提供します。

newtype UserID = UserID String

これは、型システムによってaのように扱われるが、まだ実行時のままである値を作成するUserID関数を定義します。これで、関数は文字列の代わりにが必要であることを宣言できます。以前に文字列を使用していた場所でs を使用すると、2つのsを連結するコードから保護されます。型システムは、テストが必要ないことを保証します。StringUserIDStringUserIDUserIDUserID

ここでの弱点は、コードはまだ任意のを取ることができるということですStringように"hello"して構築しUserID、それから。さらなるステップには、「スマートコンストラクター」関数の作成が含まれます。この関数は、文字列が与えられると、いくつかの不変式をチェックしUserID、それらが満たされた場合のみaを返します。そして、「ダム」UserIDクライアントが望む場合は、コンストラクタは、プライベートそうに作られてUserID、彼らがしなければならない、それによって存在に入ってくるから不正なユーザIDを防ぐ、スマートコンストラクタを使用します。

さらに別の手順では、単に定義によって、不正な形式または「不適切な」形式を作成できUserIDないようにデータ型を定義します。例えば、a を数字のリストとして定義する:UserID

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

UserID数字のリストを作成するには、提供する必要があります。この定義を考えるとUserID、URLで表現できないものが存在することは不可能であることを示すのは簡単です。Haskellでこのようなデータモデルを定義することが多いような高度な型システムの機能によって支援されたデータの種類一般化代数データ型(GADTs)型システムは、コードの詳細不変条件を定義し、証明することができ、。データが動作から切り離されている場合、動作を強制する必要がある唯一の手段はデータ定義です。


2
そして、不変量を保護する集約と集約ルートについてはどうでしょうか?私にとってDDDの最も重要な部分は、ビジネスモデルとコードの直接マッピングです。そして、あなたの答えはまさにそれについてです。
パベルボロニン

2
スピーチは良いが、OPの質問には答えない
SerG

10

大部分は、不変性により、OOPの支持者として関数をデータと密接に結合する必要がなくなります。元のデータ構造が予期せずに変更されることを恐れずに、元のコードとはかけ離れたコードで、好きなだけコピーを作成し、派生データ構造を作成することもできます。

ただし、この比較を行うためのより良い方法は、おそらくモデルとサービス層に割り当てている関数を調べることです。OOPの場合と同じようには見えませんが、複数レベルの抽象化を1つの関数に詰め込もうとすることは、FPでよくある間違いです。

私の知る限り、それを貧血モデルと呼ぶ人はいません。これはOOPの用語ですが、効果は同じです。必要に応じて汎用関数を再利用できますが、より複雑またはアプリケーション固有の操作の場合は、モデルを操作するためだけに豊富な関数セットも提供する必要があります。適切な抽象化レイヤーを作成することは、どのようなパラダイムでも優れた設計です。


2
「不変性により、OOPの支持者として関数をデータと密接に結合する必要が大幅になくなります。」データとプロシージャを結合するもう1つの理由は、動的ディスパッチによるポリモーフィズムの実装です。
ジョルジオ

2
DDDのコンテキストで動作とデータを結合する主な利点は、意味のあるビジネス関連のインターフェイスを提供することです。常に手元にあります。私たちには、コードを自己文書化する自然な方法があります(少なくとも私は慣れています)。それがビジネスの専門家とのコミュニケーションを成功させる鍵です。FPではどのように達成されますか?おそらくパイピングが役立ちますが、他には何がありますか?FPの一般的な性質は、ビジネス要件をコードからリバースエンジニアリングするのを難しくしませんか?
パベルボロニン

7

OOPでDDDを使用する場合、ドメインオブジェクト自体にビジネスロジックを配置する主な理由の1つは、通常、オブジェクトの状態を変更することによってビジネスロジックが適用されることです。これはカプセル化に関連しています。Employee.RaiseSalaryおそらくsalaryEmployeeインスタンスのフィールドを変更します。

FPでは、突然変異は回避されるためRaiseSalary、既存のEmployeeインスタンスを取得し、新しい給与で新しい Employeeインスタンスを返す関数を作成することにより、この動作を実装します。したがって、突然変異は含まれません。元のオブジェクトから読み取り、新しいオブジェクトを作成するだけです。このため、このようなRaiseSalary関数はEmployeeクラスのメソッドとして定義する必要はありませんが、どこにでも存在できます。

この場合、データを振る舞いから分離するのが自然になります。1つの構造はEmployeeasデータ(完全に貧弱)を表し、1つ(または複数)のモジュールにはそのデータを操作する関数が含まれます(不変性を保持します)。

DDDのようにデータと動作を組み合わせると、一般に単一責任原則(SRP)に違反することに注意してEmployeeください。給与の規則が変更された場合、変更する必要があります。ただし、EOYボーナスの計算ルールが変更された場合は、変更が必要になる場合があります。分離アプローチでは、これは当てはまりません。複数のモジュールがあり、それぞれに1つの責任があるためです。

そのため、通常のFPアプローチは、より優れたモジュール性/構成性を提供します。


-1

問題の本質は、モデル上で動作するサービス内のすべてのドメインロジックを含む貧血モデルが基本的に手続き型プログラミングであると思うことです。しかし、データと最も密接に結びついているロジックもあります。

また、関数型プログラミングにも同じコントラストがあります。「実際の」FPは、関数をファーストクラスのエンティティとして使用することを意味し、パラメーターとして渡され、その場で構築され、戻り値として返されます。しかし、そのすべてのパワーを使用できず、それらの間でやり取りされるデータ構造を操作する関数しか持っていない場合、同じ場所にいることになります。つまり、基本的に手続き型プログラミングを行っています。


5
はい、それは基本的にOPが彼の質問で言っていることです。どちらもあなたがまだ機能的な構成を
ロバートハーヴェイ

-3

DDDがFPパラダイムにどのように適合するかを知りたい

私はそれを行うと思いますが、主に不変の値オブジェクト間を移行するための戦術的なアプローチとして、またはエンティティでメソッドをトリガーする方法としてです。(ロジックのほとんどがまだエンティティに存在する場合。)

その場合、「貧血モデル」という用語がまだ存在するかどうか。

「従来のOOPに似た方法で」という意味であれば、通常の実装の詳細を無視して基本に戻ると役立ちます。ドメイン専門家はどの言語を使用していますか?ユーザーからどのような意図でキャプチャしますか?

彼らがプロセスと機能を一緒に連鎖することについて話しているとすると、機能(または少なくとも「do-er」オブジェクト)は基本的あなたのドメインオブジェクトのようです!

したがって、そのシナリオでは、「関数」が実際に実行可能ではなく、代わりに実際の作業を行うサービスによって解釈されるメタデータの単なる星座であるときに、「貧弱なモデル」がおそらく発生します。


1
タプル、レコード、リストなどの抽象データ型をさまざまな処理関数に渡すと、貧弱なモデルが発生します。「実行しない機能」ほどエキゾチックなものは必要ありません(それが何であれ)。
ロバートハーヴェイ

したがって、「関数」を囲む引用符は、貧血のときにラベルがいかに不適切になるかを強調します。
ダリエン

皮肉な場合、それは少し微妙です。
ロバートハーベイ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.