ドメイン駆動設計の適用ステップを考察してみた

ドメイン駆動設計は習得が難しいと思うのですが(少なくとも私には)、「エリック・エヴァンスのドメイン駆動設計」「実践ドメイン駆動設計」などの多くの本ではDDDが何か・どうあるべきかだけ書かれており、どのようにして習得していけばよいかがわかりにくいと思います。

もし徐々にドメイン駆動設計のエッセンスを取り入れていくことができれば、早いスタートを切りつつより良い開発ができるようになると思います。 ここでは、ドメイン駆動設計を習得しつつ現実の開発をどのようにすすめるのがよさそうか、私なりの考えを述べたいと思います。

フェーズ1: 境界づけられたコンテキストに分割する

まずはドメイン(問題空間)の分かれ目を整理して、実装のコンテキスト(解決空間)を分けるところから始めるのが良さそうです。 コンテキストを分けることで、コード理解や変更の影響を境界づけられたコンテキスト内に閉じ込めることができます。

まずはコンテキストをざっくり分けることができていれば、ドメインモデル貧血症(=帳票クラス+トランザクションスクリプトによる帳票の操作)でもいい気がします。ただ、データモデリングリポジトリの実装が引きづられてドメインモデル修正時に手間が発生するリスクはあります。

そしてコンテキストを統合していく必要があるのですが、コンテキストの統合でDDDっぽさを出すのはなかなか難しいように思います。のでDDDっぽくコンテキストを統合するのは後のフェーズでおこないます。

コンテキストを区切ることができていれば、まずは「リポジトリの実体を共有(別のコンテキストの修正の影響が出てしまうのでDDD的にはNGとされる)」する実装でも良い気がします。もしくは「公開サービス+公表された言語」のローカルメソッド呼び出しなど。

実装のコンテキストをわけていればリファクタリングしやすくなっているはずです。

フェーズ2: ドメインモデリング・レイヤー分け

開発のペースが出てきたら、(おそらく帳票ベースの実装になっているのを)リファクタリングしながらドメインモデルを構築することでDDDっぽさをだすことができると思います。ドメインモデルが整理できたら、ドメイン層、アプリケーション層、インフラ層、ポート&アダプターなどに分離できクリーンアーキテクチャを実現できるようになると思います。

フェーズ3: コンテキスト間の統合

ここまでのフェーズでは、コンテキスト間の統合に改善の余地がある状態でした。DDD っぽさを出していくには、DDD のマナーでコンテキスト間を統合する必要がでてきます。

  • リポジトリの分割(永続化先の分割)
  • 公開サービス+公表された言語(REST API
  • ドメインイベント、イベントソーシングやメッセージング

戦術的設計のエッセンスだけ取り入れるのはNG?

下記の『わかる!ドメイン駆動設計 〜もちこちゃんの大冒険〜【C91新刊】』には戦術的設計のエッセンスだけ取り入れるのではなく戦略的設計から導入するのが良いと書かれています。

booth.pm

DDD の最低限の習得でスタートして、上記のようにフェーズ分けして徐々に習熟度をあげていけば、戦略的設計から開始できてよりよいドメイン駆動設計ができるようになると思います。

DDD の何が大変か考えてみた

  • ドメインモデルの作成

    • → それっぽいものはできる。適宜改善の必要はあり「これでいいのか?」はつきまとうが、難しさの本質ではなさそう
  • コンテキストの分割

    • → それっぽいものはできる。適宜改善の必要はあり「これでいいのか?」はつきまとうが、コンテキストの分割はなんとなくできるので難しさの本質ではなさそう。
  • コンテキストの統合

    • 時間がかかる。DDDのマナーにのっとりつつ、データの整合性をたもって永続化するにはどうすればいいのか?など詳細がわからず『実践ドメイン駆動設計』などを熟読する必要が出てくる。
      • 例:ドメインイベント+イベントソーシングとは?
    • リポジトリをわけろ」と言われているが、一貫性を保ちつつ統合するにはどうすればいいのかわからないので共有リポジトリを作ってつっこみたくなる。

『実践ドメイン駆動設計』を読んでWiki風のアプリケーションを作ってみた

最近『実践ドメイン駆動設計』を読んだので、DDD のより深い理解のために小さめのアプリケーション(Wikiのような)を作ってみました。

github.com

特徴

  • Wiki風のWebアプリケーション(Python + Flask + インメモリのモック)
  • DDD + CQRS + イベントソーシングで実装
    • 記事の作成コンテキストと参照コンテキストを分離
    • Markdown記事の作成でイベントが発行され、それにもとづき参照用HTMLページが生成

インストール

下記のようなコマンドをターミナルなどで実行します。

$ git clone https://github.com/signdoubt/ddd-wiki-example.git
$ cd ddd-wiki-example
$ pip install -e .

起動

下記コマンドで起動します。

$ mywiki

起動後 http://localhost:5000/ にアクセスしてWiki風アプリケーションを利用できます。

ユーザ認証・認可

ユーザ認証・認可のモジュールはモック実装となっています。ポリシーは下記です。

  • admin でログインするとMarkdownで記事の作成・参照が可能
    • モック実装のため、ユーザ名さえあっていればログイン可能
  • Bob でログインすると記事を参照可能
  • 未ログイン状態では作成・参照でエラー

処理の流れ

処理の流れは下記のようになっています。

https://raw.githubusercontent.com/signdoubt/ddd-wiki-example/images/interactions.svg?sanitize=true

  1. EditorMarkdownで記事を作成すると Article.Issued イベントが発行される
  2. Article.Issued イベントにもとづき ArticleConverter が HTMLの記事を作成する
  3. Viewer はHTMLの記事を参照できるようになる

構成

パッケージ構成は下記です。

mywiki.application     # アプリケーション層
mywiki.domain.model    # ドメインモデル
mywiki.infrastructure  # インフラ層
mywiki.presentation    # プレゼンテーション層

application パッケージ

  • edit.py

    • Markdown記事の編集まわりのアプリケーションサービスを配置
    • ドメイン層のモジュールを連結(アクセス制御の有効化など含む)
  • view.py

    • HTML記事の参照をおこなうためのアプリケーションサービスを配置
    • ドメイン層のモジュールを連結(アクセス制御の有効化など含む)

domain.model パッケージ

下記のように edit / view / userのコンテキストに分かれています。

edit

記事(Markdown)の編集まわりのコンテキストです。 記事の編集をあらわすのにステートソーシングではなくイベントソーシングを使っています (Article オブジェクトの値を直接変更せずに、 Article に対する変更をイベントとして保持)。

view

記事(HTML)の参照まわりのコンテキストです。 editコンテキストで発行されたドメインイベント( Article.Issued / Article.Discarded など)をサブスクライブして参照用HTMLページを作成します。

user

ユーザ認証、認可まわりのコンテキストです。

  • 認証
    • 認証サービスに応じた認証用データを入力する
      • パスワード認証ならアカウント名+パスワード
  • 認可
    • URIの形式でパーミッションを指定
    • たとえば view://localhost/customers/resellers で表されるパーミッションの意味は:
      • 「viewコンテキストで customers/resellers カテゴリ配下の記事へアクセスできる」ことを意味する

各ユーザがパーミッション(ポリシーの定義)にもとづいて実際にどのようにアクセス制御するか(ポリシーの施行)はそれぞれのコンテキストで実装します。

  • 例えば、viewコンテキストでは mywiki/domain/model/view/security.py でどの権限に対して何ができるかを制御

infrastructure パッケージ

各種モック実装や、Flaskセッションに依存した実装をここに配置しています。

その他

mywiki コマンドを実行すると mywiki/__main__.pymain() メソッドが実行されます。 __main__.py ではDI(各種モックとの連携)をおこない、Flaskアプリケーションとして起動しています。