PHP Mentors (Posts tagged domain.modeling)

1.5M ratings
277k ratings

See, that’s what the app is perfect for.

Sounds perfect Wahhhh, I don’t wanna

第40回IT勉強宴会モデリング競演2でDDDのモデリングについて発表しました

2015年4月25日に大阪で開催された関西IT勉強宴会で、DDDのモデリングについて発表しました。

発表で利用したスライドと、実装したコードのリポジトリリンクは以下です。

当日の内容については、togetterのまとめをご参照ください。

今回この勉強会で私の現時点のDDDについて発表する機会ができたのは、とてもよかったです。モデリングという視点と経験の豊富な方々のあつまる勉強会で、いろいろなフィードバックも頂けてとても貴重でした。

また、昨日は私を含めて3つの発表があったわけですが、井田さんの問題設定とアプローチや話の組み立て方などものすごく勉強になりますし、TH法の堀越さん、石井さんの話でも使い込んできたパターンの形などとても納得感がありました。井田さん、堀越さん、石井さん、ありがとうございました。

そして毎度このような勉強会の開催にご尽力されている佐野さん、本当にありがとうございました。

modeling ddd benkyoenkai domain.modeling analysis practical.ddd

「ドメインモデリングにおける関数型パターン―仕様パターン」を翻訳しました

書籍「実践ドメイン駆動設計 (Object Oriented Selection)」が出版されて、ドメイン駆動設計(DDD)の知名度が上がってきているようです。

そのDDDに関連する分野の1つとして、DSL(ドメイン特化言語)を挙げることができると思います。

デバシッシュ・ゴーシュさん(Debasish Ghosh)は、DSLのエキスパートで、「実践プログラミングDSL」(原題 “DSLs in Action")という本を出されています。この本の第1章のタイトルは、「ドメインの言葉を話す方法を学ぶ」となっています。

デバシッシュさんのブログ記事のうちの1つ、"Functional Patterns in Domain Modeling - The Specification Pattern” に惹かれて、翻訳をしました。翻訳記事の公開について、著者ご本人から快諾頂けたため、以下に掲載させて頂きます。


ドメインモデリングにおける関数型パターン―仕様パターン

原文URL:http://debasishg.blogspot.in/2014/03/functional-patterns-in-domain-modeling.html

2014年3月31日月曜

ドメインをモデリングするときは、ドメインのエンティティと振る舞いをモデル化する。エリック・エヴァンスが書籍「ドメイン駆動設計」で述べたように、焦点を合わせるべきはドメインそれ自体だ。設計、実装したモデルは、ユビキタス言語を語っていなければならない。実装にの都合により、偶有的な複雑性が多々生じても、ドメインの本質を捉えることができるようにするためだ。表現力の豊かなモデルにするには、拡張可能であることも必要である。そして、私たちが拡張性について語るとき、関連する性質として合成性がある。

関数は、オブジェクトよりも自然に合成を行う。そこで、この記事では、ドメイン駆動設計のコアを成すパターンのうちの1つを実装するために、関数型プログラミングのイディオムを用いるー 仕様パターンだ。 仕様パターンで最も多いユースケースは、ドメインのバリデーションを実装することである。エリックのDDD本には、仕様パターンについて次のように書かれている:

仕様の用途は複数あるが、最も基本的な概念を伝えているのは、どんなオブジェクトでも評価して、定義された基準を満たしているかどうかを調べるという使い方である。

仕様は述語として定義され、それによって、業務ルール同士をブール論理を使って互いに連鎖させて結合できるようになる。このように合成の概念があるので、このパターンについて語るときは合成仕様Composite Specification)として語ることができるだろう。DDDにおける各種の文献では、これをCompositeデザインパターンを使って実装しており、ごく一般的にはクラス階層とコンポジションが用いられていた。この記事では、その代わりに関数合成を使う。

仕様はどこにあるのか?

モデルを設計する際によくある問題として、集約ルートやエンティティのバリデーションを行うコードをどこに置くのか、という話がある。

  • エンティティの中でバリデーションをするか? これはダメだ。エンティティが肥大化してしまう。同じエンティティのコアでも、コンテキストによってバリデーションを変えたいことがある。
  • インターフェイスの一部としてバリデーションをするか? JSONを使って、その外側でエンティティを構築するかもしれない。たしかに、ある種のバリデーションはインターフェイスに属すると言えるし、そこにバリデーションを置くことに違和感は無い。
  • しかし、もっとも興味深いバリデーションは、ドメインレイヤーに属するものだ。業務のバリデーション(あるいは仕様)であり、エリック・エヴァンスが他のオブジェクトの状態に関する制約をのべるものと定義している。 業務ルールとして、エンティティを次の処理へ渡す前にバリデーションをかけなければならない。

単純な例で考えよう。注文Order)エンティティとそのモデルがあるとする。新しい注文は、処理工程に入る前に、下記のドメインの「仕様」を満たすものとする。

  1. 妥当valid)な注文でなければならず、ドメインが必要とする正当な日付、正当な商品品目、といった制約に従わなければならない。
  2. 正しい権限で承認approved)されていなければならない。処理工程の次段階に進めるのはその場合だけだ。
  3. 顧客がブラックリストに載っていないことを保証するため、状態を審査しなければならない。
  4. 注文可能かどうかを調べるため、商品品目の在庫を確認しなければならない。

個々の手順に分かれていて、注文の処理工程に沿って順次完了していく。そのようにして、注文の前に、注文実行の用意が整っているどうかを検証する。どこかでエラーがあると、注文は処理工程から外れて、そこで処理が終わる。そして、私たちが設計するモデルは、この順序を知っている必要があるし、手順の一部として経るべき制約はすべて課す必要がある。

注文を変化させるのは手順だけである―ここは重要なポイントだ。すべての仕様は、最初の注文のコピーを入力として受け取り、ドメインルールを検証後、処理工程の次の手順へ進ませて良いかどうか判定する。

実装へ……

私たちがここまでで学んできたことをふまえて、実装に落としこんでみよう。

  • 注文は、少なくとも今回の操作の流れにおいては、不変のエンティティにできる。
  • すべての仕様は1つの注文を必要とする。これがトリックになっていて、仕様に注文インスタンスを順次渡すことで、APIをシンプルに保つことができる。
  • 関数型プログラミングの原則により、上記の処理手順をとしてどのようにモデル化できるか。結果を最後まで合成可能に保ち、注文完了後の次工程へ渡すにはどうするか(次工程については、いずれまた別の記事で議論しよう)。
  • すべての関数は似たシグネチャを持っているようだ―私たちは関数をたがいに合成する必要がある。

あれこれと説明や理論を持ち出すよりも、まずは基本的なビルディングブロックを使ってドメインエキスパートととりまとめた内容を実装していこう。

type ValidationStatus[S] = \/[String, S]

type ReaderTStatus[A, S] = ReaderT[ValidationStatus, A, S]

object ReaderTStatus extends KleisliInstances with KleisliFunctions {
  def apply[A, S](f: A => ValidationStatus[S]): ReaderTStatus[A, S] = kleisli(f)
}

ValidationStatusは、どの関数からも結果として返す型を定義している。それは状態S、または何らかの異常を報告するエラー文字列となる。正確にはscalazで実装されているEither型(右側が正)である。

この実装が優れているのは、すべてのメソッドで注文パラメータが繰り返されることなく処理手順が呼び出されるからだ。そのようにするイディオムの1つとして、Readerモナドが使われる。そして、私たちはすでにモナド―\/はモナドだ―を持っている。そこで、処理結果をモナド変換子を使って積み上げていく。ReaderTがこの作業を受け持つ。結果同士を結びつけてくれるありがたい型としてReaderTStatusを定義する。

次のステップはReaderTStatusの実装で、クライスリと呼ばれる別の抽象を使う。scalazライブラリを使って、クライスリの言葉でReaderTを実装する。実装の詳細に立ち入る話はしないでおく―もし興味があるのであれば、ユージーンによる優れた論文を参照してほしい。

さて、サンプルの仕様はどのようになるのか?

話に入る前に、基本的な抽象を用意する(わかりやすさのため、ごく単純にしてある)。

// 基底の抽象
sealed trait Item {
  def itemCode: String
}

// サンプル実装
case class ItemA(itemCode: String, desc: Option[String],
  minPurchaseUnit: Int) extends Item
case class ItemB(itemCode: String, desc: Option[String],
  nutritionInfo: String) extends Item

case class LineItem(item: Item, quantity: Int)

case class Customer(custId: String, name: String, category: Int)

// 注文のスケルトン
case class Order(orderNo: String, orderDate: Date, customer: Customer,
  lineItems: List[LineItem])

そして、下記が注文オブジェクトの制約を検査する仕様である。

// 基本的なバリデーション
private def validate = ReaderTStatus[Order, Boolean] {order =>
  if (order.lineItems isEmpty) left(s"Validation failed for order $order")
  else right(true)
}

これは説明用の例に過ぎないので、ドメインルールが多く含まれているわけではない。 重要なのは、関数を実装するために定義した型をどう使っているかだ。 注文は関数に対する暗黙の引数ではない―それはカリー化される。関数はReaderTStatusを返す。ReaderTStatus自体はモナドである。したがって、別の仕様と併せて処理工程を手順化していくことができる。つまり、決められた順序の要求を、式指向プログラミングの枠組みの中で解決できるのだ。

収集したドメイン知識に基づく仕様はほかにもあった。

private def approve = ReaderTStatus[Order, Boolean] {order =>
  right(true)
}

private def checkCustomerStatus(customer: Customer) = ReaderTStatus[Order, Boolean] {order =>
  right(true)
}

private def checkInventory = ReaderTStatus[Order, Boolean] {order =>
  right(true)
}

これらを互いにつなぐこと

しかし、ドメインが与える操作順序を表すには、これらのピースをどうつなげば良いのだろう。モデルで合成性による利点を享受するにはどうするのか? これは非常に簡単だ。合成に適した型定義という難しい仕事はすでに終わっている。

以下のisReadyForFulfilmentメソッドでは「合成仕様」を定義していて、for内包表記(for-comprehension)でくるまれた個々の仕様すべてを順番に呼び出している。

def isReadyForFulfilment(order: Order) = {
  val s = for {
    _ <- validate
    _ <- approve
    _ <- checkCustomerStatus(order.customer)
    c <- checkInventory
  } yield c
  s(order)
}

このように、モナディックな結合により、順序立てられた一連の処理を、抽象の合成性を保ちつつ実装することができる。次回は、注文の後工程を合成可能にする方法、エンティティの情報を読むだけでなく可変とするやり方についても見ていこう。もちろん、関数型のアプローチで。


翻訳後記

この記事は、初学者の私が、背伸びをして勉強しながら訳したものです。推敲を重ねましたが、誤訳等が含まれている可能性はあります。もし何かありましたら、ご指摘頂けると幸いです。

関連記事

参考

ddd dsl practical.ddd functional.programing domain.modeling modeling analysis

Practical Symfony #2: Dependency Injectionコンテナの導入によるドメインモデルの改善 : Symfony Advent Calender 2011 JP - 11日目

PHPメンターズの久保です。本記事はSymfony Advent Calendar JP 2011の11日目の記事となります。

私は現在Piece Frameworkの新プロダクトとして継続的テスティングプラットフォームMakeGood Universeの開発を進めていますが、並行してMakeGood Universe(およびMakeGood)のテストランナーフレームワーク部分を担当するStagehand_TestRunnerのバージョン3の開発も進めています。

Stagehand_TestRunner 3の開発においては各機能の実装にSymfonyのコンポーネントを全面採用することにしており、DependencyInjectionコンポーネントの導入もその一環です。DependencyInjectionコンポーネントを導入する最大の動機は、Configコンポーネントとの合わせ技によるYAMLベースの設定ファイルの実現にあります。

image

YAMLベースの設定ファイルは問題空間の言語で書かれており、変換器を通して解決空間の言語で書かれたDIコンテナの定義に変換されます。コマンドラインも問題空間の言語であり、同じ仕組みの中に統合されます。

さて、大規模な構造改善に着手したのもつかの間、問題があることがわかりました。これまでのStagehand_TestRunnerでは、事実上グローバルなConfigオブジェクトを各コンポーネントが参照することによってアプリケーションの設定にアクセスしていました。

image

この設計にはドメインモデルの欠落(あるいはコードへの未反映), 関連性のない情報への参照の発生といった問題があります。そこで最初のステップとして、グローバルなConfigオブジェクトに設定していたパラメータを各コンポーネントに直接設定する形への移行に取り組みました。

image

しかし、この設計には同じパラメータが異なるサービスの引数に繰り返し設定されるという問題があります。グローバルなConfigオブジェクトとは異なり、サービスに設定されるパラメータはリテラルなので、同一パラメータの値が設定されるタイミングによって異なるといった問題も発生するでしょう。

Configオブジェクトの廃止と、DIコンテナのパラメータとサービスの引数への設定に移行したことにより、関連性のない情報への参照は発生しなくなりましたが、ドメインモデルの不在はより顕在化しました。そこで次のステップとして、各パラメータの適切な配置先となるオブジェクトを用意し、パラメータからオブジェクトへの変換は1度のみ行うことにしました。もちろん、適切な配置先となるオブジェクトは単に用意するという性格のものではなく、ドメインモデルが反映されているものでなければなりません。

image

Dependency Injectionコンテナの導入により必ずしも設計やドメインモデルの改善ができるとは限りませんが、ドメインモデルの欠落やコードへの未反映を発見する契機とすることはできるでしょう。

Symfony Advent Calendar JP 2011 12日目となる明日の担当は、Silex使いの@77webさんです。

参考

configuration design symfony dependency.injection stagehand.testrunner domain.modeling modeling practical.symfony