ドメインモデリングの機能パターン-貧弱なモデルと動作のレイアウト

こんにちは、Habr! それほど前ではないが、著者が困難であるが待望され、苦しんでいた主題分野の機能モデリングに関するがマニング出版社で出版された。







Scala主題指向のデザインパターンの両方に関する本を準備しているので、彼の本のアイデアに関するSahib Ghoshの記事の1つを公開し、この本があなたにとってどれほど面白いかを尋ねます。



私はかつてDean Wampler による主題指向設計、貧血主題モデル、およびこれらの問題のいくつかを滑らかにすることを可能にする関数型プログラミングに関するプレゼンテーションを研究しました。 OOPプログラマーはWomplerの論文のいくつかをぞっとするかもしれません。 これらは、主にOOPを使用してサブジェクト指向の設計を実行する必要があるという一般に受け入れられている信念と矛盾しています。



私は強く同意するアイデアを表明します-「 DDDは主題分野を理解することを奨励しますが、モデルの実装には役立ちません 。」 DDDは、開発者が対処しなければならないサブジェクト領域を習得し、アプリケーションの設計と実装全体で使用される一般的な用語を開発するのに役立ちます。 同様の役割が設計パターンによって果たされます-それらは、実装の詳細に入ることなく開発者にタスクを本質的に説明できる用語装置を提供します。



一方、状態が動作に関連付けられている標準のOOPテクニックを使用してDDDの概念を実装しようとすると、混乱した可変モデルが得られます。 モデルは、ドメインから取得した特定の抽象化のすべての側面をシミュレートされたクラスに埋め込むことができるという意味で飽和状態になります。 しかし、この場合、抽象化は非常にローカルであるため、クラスは壊れやすくなり、再利用と構成可能性の観点からグローバルな機能が不足しています。 その結果、サブジェクト領域のサービスレベルで多くの抽象化を構成しようとすると、このレベルはガベージグルーコードでオーバーフローします。このコードは、クラス境界間の負荷の不一致に対処するために必要です。



したがって、Deanが「 モデルは貧血であるべきだ 」と言うとき、彼は、対象領域のオブジェクトの状態と動作のこのような混乱を避けることを求めます。 彼は、このようなドメインオブジェクトを記述することをお勧めします。自律型機能を使用して動作をモデル化する場合にのみ、オブジェクトを状態にする必要があります。



美しい実装は単なる機能である場合があります。 メソッドではありません。 クラスではありません。 ワイヤーフレームではありません。 ただの機能。

ジョン・カーマック


私によく出くわす不器用な議論がもう1つあります。クラス内のメソッドのカプセル化が大きくなるにつれて、状態は後者をモデル化するプロセスの動作と混同されます。 まだそのような哲学を固守しているなら、2000年に書かれたスコット・マイヤーによる素晴らしい記事を参照してください。 彼はクラスがモジュール化に必要なレベルであると考えることを拒否し、モジュール内のサブジェクト領域の動作を保存する方が便利なため、より強力なモジュールシステムを記述することを推奨します。



これは、 Order



抽象化の貧弱な主題モデルです...



 case class Order(orderNo: String, orderDate: Date, customer: Customer, lineItems: Vector[LineItem], shipTo: ShipTo, netOrderValue: Option[BigDecimal] = None, status: OrderStatus = Placed)
      
      





先ほど、関数型プログラミングの原理を使用してDDD仕様と集計パターンを実装する方法を書きました 。 さらに、Lensなどのデータ構造を使用して機能的な集計更新を行う方法についても説明しました。 この記事では、それらを構築要素として使用し、より機能的なパターンを適用し、ドメイン言語をモデル化するより大きな動作を実装します。 最終的に、DDDの基本原則の1つは、ドメインディクショナリを実装レベルに引き上げることです。これにより、モデルのサポートに関与している開発者に機能が明らかになります。



主なアイデアは、自律機能の形でドメインの振る舞いを作成するときに、DDDの原則に従って実際に効果的なドメインモデルを提供するかどうかを確認することです。 モデルの基本クラスには、機能的な手段で変更できるこのような動作のみが含まれます。 すべてのドメインの動作は、集計を表すモジュールにある関数を使用してモデル化されます。



関数がコンパイルされ、これがサブジェクト領域の動作をリンクし、小さなものから大きな抽象化を構築する方法です。 次に、Orderを評価する小さな関数を示します。 注意: Kleisli



返します。これは、実際にモナド関数の合成を提供します。 つまり、a- a -> b



およびb -> c



を配置a -> b



代わりに、通常の関数のレイアウトで行っていたとおりに、 a -> mb



およびb -> mc



を使用してm



をモナドとします。 効果のある構図、いわば。



 def valueOrder = Kleisli[ProcessingStatus, Order, Order] {order => val o = orderLineItems.set( order, setLineItemValues(order.lineItems) ) o.lineItems.map(_.value).sequenceU match { case Some(_) => right(o) case _ => left("Missing value for items") } }
      
      





しかし、これは私たちに何を与えますか? 機能パターンを通して正確に何が得られますか? 同様の抽象概念のファミリー、たとえば、アプリカティブとモナドを区別する機会があります。 このアプローチを実証するためには、おそらく抽象的に聞こえますが、別の記事が必要です。 簡単に言えば、コンピューティングの効果と副作用をカプセル化するため、モデルの実装に集中できます。 以下のプロセス関数を見てください-実際のモナド関数の構成は次のとおりです。 ただし、効果と副作用の処理を提供する全体の充填はKleisli



で抽象化されているため、ユーザーレベルでの実装は単純で簡潔です。



Kleisli



は、モナド関数をリンクする可能性を十分に示しています。 ドメイン内の動作はすべて失敗する可能性があり、失敗はEither



モナドを使用してモデル化されます-ここで、 ProcessingStatus



..type ProcessingStatus[S] = \/[String, S]



型エイリアスです。 Kleisli



を使用する場合、障害を処理するためのコードを記述する必要はありません。 以下では、構成が通常の機能と完全に類似していることを確認できます。パターンのレベルでは、実行の代替スレッドが考慮されます。



Order



評価されたら、その製品に割引を適用する必要があります。 これは、 valueOrder



と同じパターンに従って実装される別の動作valueOrder







 def applyDiscounts = Kleisli[ProcessingStatus, Order, Order] {order => val o = orderLineItems.set( order, setLineItemValues(order.lineItems) ) o.lineItems.map(_.discount).sequenceU match { case Some(_) => right(o) case _ => left("Missing discount for items") } }
      
      





最後に、注文Order



コストを計算します...



 def checkOut = Kleisli[ProcessingStatus, Order, Order] {order => val netOrderValue = order.lineItems.foldLeft(BigDecimal(0).some) {(s, i) => s |+| (i.value |+| i.discount.map(d => Tags.Multiplication(BigDecimal(-1)) |+| Tags.Multiplication(d))) } right(orderNetValue.set(order, netOrderValue)) }
      
      





そして、サブジェクト領域の上記のすべての動作を1つの大きな抽象にコンパイルするユーティリティメソッドがあります。 単一のオブジェクトをインスタンス化する必要はありません。 関数を構成するだけなので、イベントのストリーム全体を表現できます。 抽象化自体が明確に定義されているため、コードは非常に読みやすく簡潔であることが判明しました。



 def process(order: Order) = { (valueOrder andThen applyDiscounts andThen checkOut) =<< right(orderStatus.set(order, Validated)) }
      
      





この例の完全なソースコードに興味がある場合は、github リポジトリに送信します



All Articles