2019/02/25

ドメイン駆動設計の用語と解説(DDD入門ガイド)

中〜大規模アプリケーションを開発するノウハウを持っておらず、どのようにシステム設計するのが良いのかわからなかった。そのため、1週間ほどドメイン駆動設計(Domain-Driven Design)について勉強した。

当記事では、勉強中に得たドメイン駆動設計をわかった気になれるのに必要な用語のまとめや、実装でどのように使われるかをまとめる。

筆者は「実践ドメイン駆動設計」を読んだわけでも、完全に理解したわけでもない。しかし、雰囲気を掴むための情報はわかっている状況なので、間違っている箇所があったら指摘していただきたい。


ドメイン駆動設計とは?


ドメイン駆動設計(DDD、Domain-Driven Design)を一言で説明すると「現実世界の業務をドメインモデルに詰め込んでソフトウェアに深く反映させる設計手法」だ。

詳しい説明は後述するが、ドメイン駆動設計の全体図は下図のような感じだ。


ドメイン駆動設計に登場する用語集


この章では、ドメイン駆動設計で使われる用語を一言、二言でシンプルに説明する。

ドメイン駆動設計は大きく2つに分けられる。「戦略的設計」と「戦術的設計」だ。

戦略的設計(何をどうやって設計するか)

  • Domain: ドメイン
    • 取り扱う現実世界の業務
    • 事業的に重要なものを「Core Domain: コアドメイン」
    • 補助的なものを「Sub Domain: サブドメイン」
  • Model: モデル
    • Domainの抽象的な概念
    • ソフトウェアの核
  • Domain Model: ドメインモデル
    • 知識を取捨選択しながら作成する抽象モデル
    • Domain関係者に向けて表現したもの
  • Bounded Context: 境界づけられたコンテキスト
    • Domainの課題を解決する部分
    • 異なる概念を混ぜないように適切な範囲で分割する
    • Core Domain : Bounded Context = 1 : 1 が最適
    • Bounded Context : Team = 1 : 1 が最適
  • Context Map: コンテキストマップ
    • Bounded Contextの関係を表す鳥瞰図
    • チーム間の関係を示しているので他チームとコミュニケーションをとるときに使う
  • Ubiquitous Language: ユビキタス言語
    • チーム内での共通言語
    • チームにはSEやプログラマだけでなく、ドメインエキスパート(業務担当者)なども含まれる

戦術的設計(どのように実装するか)

  • Entities: エンティティ
    • ユニークになるものを表現する
  • ValueObjects: 値オブジェクト
    • 一意に識別する必要のないものを表現する
  • Services: サービス
    • Domain Service
      • Domain Layer: ドメイン層に存在するサービス
      • EntityやValueObjectの責務でないビジネスロジックを書く
      • 複数のドメインオブジェクトを使って計算する処理やファサード
    • Application Service
      • Application Layer: アプリケーション層に存在するサービス
      • Domainの外部の関心事を実装する軽薄なサービス
  • Domain Events: ドメインイベント
    • Domainの出来事をモデリングする
  • Factories: ファクトリ
    • ユビキタス言語を用いて複雑なObjectやAggregatesをシンプルに生成する
  • Application: アプリケーション
    • ユースケースごとにドメインオブジェクトのコーディネートを行う
  • Aggregates: 集約
    • EntityやValueObjectをまとめて、トランザクション整合性を保ちながらデータを更新する
  • Repositories: リポジトリ
    • データの永続化を行う
  • Modules: モジュール
    • クラスを責任と役割ごとにまとめて管理する



どのように実装するか


アーキテクチャ

原典である「Implementing Domain-Driven Design(実践ドメイン駆動設計)」には、Layered Architecture(レイヤアーキテクチャ)が紹介されている。昨今では、ヘキサゴナルアーキテクチャやイベント駆動アーキテクチャ、オニオンアーキテクチャ、クリーンアーキテクチャなどの派生系が多く存在する。

当ブログでは、原典で紹介されていたLayered Architectureについて紹介する。

  • UI (Presentation)
    • 描画に関する処理を取り扱う層(いわゆるView層)
    • UIの状態や固有ロジックがある場合はプレゼンテーションモデルを作る
    • Domain操作はApplication層を介して行う
  • Application
    • ユースケースごとにドメインオブジェクトのコーディネートを行う(いわゆるController層)
    • ドメインロジックは持たず、薄くて軽い層にする
  • Domain
    • ビジネスルールやDomainを扱う(いわゆるModel層)
    • ビジネスロジックやユースケース、ユーザストーリーはDomain層に閉じ込める
  • Infrastructure
    • データの永続化を行う層
    • DB操作やAPIリクエストなどを行う

Domain層の実装指針

Entiries
  • Mutable(可変)なオブジェクト
  • 設計方法
    1. ドメインエキスパートからユースケースやユーザーストーリーを聞く
    2. 1.からユビキタス言語を構築する
    3. シナリオに「変更」というキーワードがある場合、その主語がEntiryになる可能性が高い
  • 一意な識別子を生成する
    • 同一性の判定のためequalsやhasCodeメソッドなどを実装する
    • コンストラクタで識別子を生成する
    • 生成が複雑な場合はFactoryを使う
  • バリデーションを実装する
    • 属性に条件があればバリデーションを実装する
    • オブジェクト全体のチェックではValidatorクラスを作成し、適宜呼び出す
    • 複数オブジェクトの状態をバリデーションしたい場合は、Domain Serviceに必要な数だけValidatorを用意する

Value Objects
  • Immutable(不変)なオブジェクト
  • プリミティブ型ではなくクラスを作成することで変数を説明する(例: phoneNomber: string → class PhoneNumber)
  • Immutableなため、値を更新したい場合は新しいオブジェクトを生成して交換する
    • コピーコンストラクタで自分自身を引数として受け取りコピーする
  • 可能な限り自分自身の情報だけを使い処理を行う(副作用をなくすため)
  • タイプコード(数字に意味をもたせる)よりもenum(列挙体)を使う
  • 一意な識別子を持たないので、Value Object同士を比較するためのequalsメソッドでは属性値で判別する

Domain Services
  • ユビキタス言語で表現される
  • Entiries/Value Objects/Aggregatesを利用して計算するビジネスルールを実装する
  • シンプルにするためにビジネスロジックをまとめるときにも使う
  • 必要以上にDomain Serviceを実装するとドメインモデル貧血症になる
  • Domain Serviceは必須ではないので、不要なら作らなくても良い

Domain Events
  • シナリオの「〜するとき」「〜した場合」がある場合、Domain Eventsになりえる
  • 他システムとの連携や他のAggregatesへのデータ反映を疎結合にする効果がある
  • イベント名やプロパティはユビキタス言語で表現する
  • 軽量なオブザーバーパターンでイベント制御を行う(関連: GoFデザインパターン)
  • Immutableなクラスで設計し、コンストラクタで値を設定するのみでプロパティは読み取り専用にする

Modules
  • ユビキタス言語に従い、高凝縮・疎結合なモジュール(ディレクトリ構成)をつくる
  • 参照は単一方向にとどめる(双方向や循環参照はやめる)
  • 命名規約は、組織名>コンテキスト名>重要なモジュール名.コンセプト名
    • 例: SaaSOvation.AgilePM.Domain.Model.Products
    • Entities, Value Objects, Domain Service, Domain Events, Aggregatesm Repositoriesを含む
  • Bounded Contextとの違い
    • Bunded Context: ユビキタス言語の境界線(ビジネス的な境界線)
    • Modules: 役割ごとにクラスをまとめる仕組み(実装的な境界線)

Aggregates
  • オブジェクト群の生成/読込/変更/保存/削除といったライフサイクルを管理する
  • 外部からの操作のためにEntityを1つだけ持ち公開する
  • 設計ルール
    • 小さく設計する
    • Value Objectsから構成する
    • Root Entity以外はValue Objectsであることが望ましい
    • 他のAggregatesへの参照は一意なIDを用いて行う
    • Bounded Contextの外部ではトランザクション整合性ではなく結果整合性を使う
  • 設計時に知っておくと便利な法則
    • デメテルの法則(呼び出して良いもの)
      • オブジェクト自身のメソッド
      • 自身にパラメータとして渡されたオブジェクトのメソッド
      • 自身の内部でインスタンス化されたオブジェクトのメソッド
      • 自身が保持しており、直接アクセスできるオブジェクトのメソッド
    • Tell - Don't Ask(尋ねるな、命令しろ)
      • オブジェクトの内部状態は一切把握することなく(尋ねることなく)、呼び出し(命令)だけを行う

Factories
  • GoF デザインパターンのFactory Pattern
  • 生成処理がシンプルな場合はコンストラクタで十分
  • 一貫した状態のオブジェクトだけを返す
  • オブジェクトの生成ロジックを隠蔽する

Repositories
  • データベースにアクセスし、データの永続化をする
  • データベースがメモリ上にあると錯覚させる
  • データベースからデータを取り出すときは、Factoriesを使ってモデルを生成する
  • インターフェースはDomain層、実装はInfrastructures層に置く


プロジェクトのディレクトリ構成例


  • 組織名
    • プロジェクト名
      • コンテキスト名
        • Application
          • Application Services
        • Domain
          • Model
            • コンセプト名
              • Entiry
              • Value Object
              • Domain Service
              • Domain Event
              • Aggregate
              • Repository



参考サイト




以上

written by @bc_rikko

0 件のコメント :

コメントを投稿