web-dev-qa-db-ja.com

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

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

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

更新:話題について最近公開された book および video

そしてもう1つ video スコットから。

44
Pavel Voronin

「貧血モデル」問題の記述方法は、FPのように適切に変換されません。最初に適切に一般化する必要があります。本質的に、貧血モデルは知識を含むモデルですモデル自体によってカプセル化されていないそれを適切に使用する方法についてです。代わりに、その知識は関連するサービスの山に分散しています。これらのサービスはモデルのclientsのみである必要がありますが、保持されている貧血responsibleそのため。たとえば、アカウントのアクティブ化または非アクティブ化に使用できないAccountクラスを検討します。 AccountManagerクラス。アカウントは、一部の外部マネージャークラスではなく、アカウントの基本的な操作を担当する必要があります。

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

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

newtype UserID = UserID String

これはUserID関数を定義します。この関数は、Stringを指定すると、型システムによって扱われたように a UserIDの値を構築しますが、実行時は単なるStringです。関数は、文字列ではなくUserIDが必要であることを宣言できるようになりました。以前に文字列を使用していたUserIDsを使用すると、2つのUserIDsを連結するコードから保護されます。型システムguarantees起こり得ない、テストは必要ありません。

ここでの弱点は、コードが"hello"のような任意のStringを取り、そこからUserIDを構築できることです。さらに、「スマートコンストラクター」関数の作成が含まれます。この関数は、文字列が与えられると、いくつかの不変条件をチェックし、それらが満たされた場合にのみUserIDを返します。次に、「ダム」のUserIDコンストラクターを非公開にします。そのため、クライアントがUserIDを必要とする場合、それらはmustスマートコンストラクターを使用して、不正な形式のUserIDの存在を防ぎます。

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

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

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

27
Jack

不変性により、OOPの擁護者のように、関数とデータを密接に結合する必要がなくなります。コードからはるかに離れたコードで、派生データ構造を作成することもできます。元のコードから、元のデータ構造があなたの下から予期せず変化することを恐れずに。

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

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

10
Karl Bielefeldt

ODDでDDDを使用する場合、ビジネスロジックをドメインオブジェクト自体に配置する主な理由の1つは、ビジネスロジックは通常、オブジェクトの状態を変更することによって適用されることです。これはカプセル化に関連しています:Employee.RaiseSalaryは、salaryインスタンスのEmployeeフィールドを変更する可能性がありますが、パブリックに設定することはできません。

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

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

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

したがって、いつものようにFPアプローチは、より優れたモジュール性/構成可能性を提供します。

8
la-yumba

問題の本質は、モデルで動作するサービス内のすべてのドメインロジックを持つ貧血モデルは基本的に手続き型プログラミングであることだと思います-「実際の」OO 「スマート」であり、データだけでなく、データに最も密接に関連付けられているロジックも含まれています。

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

0