ドメインロジックとは
エンタープライズアプリケーションアーキテクチャ(マーチンファウラー著)を読むとドメインロジックという言葉が出てきます。ドメインロジックとはアプリ-ケーションが扱う問題領域の論理であると私なりに理解しています。例えば販売管理で請求額の計算など規模の大きい物や、数量と単価からの金額計算などの小さな物までがこれに含みます。画面遷移・変数の画面表示・データベースの更新等はこれに含みません。ドメインロジックは当方のWindows Formsのアーキテクチャではドメイン層に記述します。
ドメイン層のデータ構造
データベースは表形式でデータを持っています。それに対しドメイン層ではオブジェクトグラフ形式でデータを持っています。テーブルはエンティティ(参照オブジェクト)で表します。エンティティ間の関連は参照で表します。
例えばデータベース上に伝票・得意先マスタ・伝票明細テーブルがあったとします。ドメイン層では伝票エンティティに得意先マスタを参照するプロパティと伝票明細のリストを保持するプロパティを持たせています。
エンティティは、固定繰り返しを持つ場合など、必要に応じて値オブジェクトによって複合的な構成にする場合があります。
ドメインロジックパターンの分類
ドメインロジックの書き方について、トランザクションスクリプト・ドメインモデル・テーブルモジュールがあります。これらの違いは以下の通りです。
分類 | 形態 | 得失(当方の考え) |
---|---|---|
トランザクションスクリプト | ビジネスロジックを一連の手続きで構成する。処理は手続きに集中している。1つのエンティティに関連する処理は各トランザクションスクリプトに分散している。 |
|
ドメインモデル | データベースの行に対応するエンティティに処理を記述。ビジネスロジックはエンティティを渡り歩くように実行する。1つのビジネスロジックに対して記述は分散している。 |
|
テーブルモジュール | データベーステーブルに対応するクラスに処理を記述。ビジネスロジックはテーブルモジュールを渡り歩くように実行する。1つのビジネスロジックに対して記述は分散している。 |
|
トランザクションスクリプト | ドメインモデル | テーブルモジュール | アクティブレコード(参考) | |
---|---|---|---|---|
処理とデータ | 分離 | 一体 | 一体 | 一体 |
インスタンス | エンティティとは独立 | 行毎 | テーブル毎 | 行毎 |
データアクセス | 分離 | 分離 | 分離 | 一体 |
ドメインロジックパターンの適用
当方では従来主にトランザクションスクリプトで記述していました。今はドメインモデルとテーブルモジュールを併用しています。行内の処理およびエンティティが保持する子エンティティの集計処理はドメインモデルで、ルートエンティティの集計処理、例えば複数行の金額合計計算などはテーブルモジュールで記述しています。伝票と伝票明細を編集することを想定した場合には、伝票明細の金額を合計して伝票の合計金額項目を更新する処理は伝票に記述しています。
パターンを変更した理由はトランザクションスクリプトだと処理の重複が発生しやすいからです。ドメインモデルとテーブルモジュールを使った場合1つの処理だけを見れば処理が分散して複雑になるのですが、アプリケーション全体で見ると重複が少なくなりどこに何が書いてあるか見通しが良くなります。
ドメインロジックの構成
当方ではドメインロジックを下記のもので構成しています。
記述内容 | |
---|---|
ドメインモデル | 行内の処理例)数量×単価から金額を求める処理 子エンティティの集計処理例) 伝票と伝票明細があり、伝票明細の金額集計を行う処理を伝票エンティティに記述 |
テーブルモジュール | ルートエンティティの集計処理例)伝票一覧の金額合計処理 |
サービス | バッチ処理その他例) 日次更新・請求処理 |
リポジトリ | クエリーに基づくエンティティのロード抽出条件やキーを指定してデータソースからロード 集計を伴うクエリー、例)請求一覧作成 |
エンティティの選択
エンティティは次の3種類に分けることができます。
- データベーステーブルに対応するエンティティ
- テーブルと異なる結果セットを持つクエリーに対応するエンティティ
- 抽出条件等ユーザー入力に必要なエンティティ
クエリーに対応するエンティティについて補足説明します。ドメイン層はオブジェクトグラフでデータを保持していますので必要なクエリーを削減することができます。例として伝票一覧に得意先マスタの項目である得意先名を表示することを想定します。件数の少ないマスタ類は参照されたときに全件ロードするようにしてあります。そのため得意先名は伝票.得意先マスタ.得意先名
で参照できるのでテーブルを結合する必要はありません。伝票-伝票明細といったトランザクションテーブル同士の結合および集計を伴う場合にクエリーに対応するエンティティを作成します。
リポジトリの役割
リポジトリでは、クエリーによるエンティティのロードと更新処理を行います。
キーを指定して該当するエンティティをリストにロードする場合、呼び出し元コードはリポジトリに対しExtractByKey(Key)
を呼び出します。リポジトリはクエリーを発行し、その結果をIEnumerable
で返します。呼び出し元コードこれを所定のリストに格納します。
抽出条件をユーザが指定できる一覧画面では、抽出条件エンティティのGetFilterExpression
を呼び出して、入力された抽出条件を元にExpression
型の条件式を作成します。条件式はExpression Tree によるDSLで表現しています。この条件式をリポジトリのExtract
メソッドの引数に指定して呼び出します。Extract
メソッドはIEnumerable
の形式でエンティティを取得します。
リポジトリではSQLを利用しながらドメインロジックを実現する場合があります。例えば請求一覧表を作成する処理を大雑把に説明すると、まず各得意先について売上・入金の合計を計算して差額を出します。このときの合計計算はSQLを利用し、その結果はデータベース上のテンポラリテーブルに書き込みます。テンポラリテーブルをロードして請求一覧表とします。これら一連の処理は請求一覧リポジトリに記述しています。SQLを利用する利点はSQLによる簡潔な記述が活かせること、通信量が少ないこと、パフォーマンスがよいことです。欠点はデータベースエンジンによってSQLの記述が異なる場合があること、ドメインロジック内にSQLによる処理とオンメモリテーブル上の処理が混在してしまうことです。