#はじめに
プロジェクトをドメイン駆動設計で進めることを想定しています。
自分の職場でやってることしか知らないので、他の職場ではこの問題は起きていないのかもしれません。
SQLが単純なデータの読み書きでなく、業務上の処理を担っていることを前提にしています。
#ドメイン駆動設計とは
本題に入る前にそもそもドメイン駆動設計と何なのかを簡単に説明しておきます。Wikipediaには以下のように書いてあります。意味の分からない単語が多くて、内容を理解するのが難しいですね。
ドメイン駆動設計(英: Domain-driven design, DDD)とはソフトウェアの設計手法であり、「複雑なドメインの設計は、モデルベースで行うべき」であり、また「大半のソフトウェアプロジェクトでは、システムを実装するための特定の技術ではなく、ドメインそのものとドメインのロジックに焦点を置くべき」であるとする[1]。この名称は、 Eric Evans が同名の著作で用いた[2]。
https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88
おおざっぱに説明すると、複雑なシステムの設計をビジネスロジック1中心に行う設計手法のことです。
###ビジネスオブジェクトと非ビジネスオブジェクト
ドメイン駆動設計ではビジネスロジックが集約されたドメイン層の設計を重視します。そこで考えなくてはいけないのが、あるオブジェクトがドメイン層に所属するかしないかということです。
ここで、ビジネスオブジェクトを「ビジネスロジックを持つオブジェクト」として話を進めます。
例えばビジネスオブジェクトでないものとして「印刷用の情報を受け取って画像を印刷するオブジェクト」が挙げられます。これは、業務が変わろうと処理を使いまわすことができるので、ビジネスオブジェクトにあたりません。こういった処理はドメイン層から分離して設計します。
反対にビジネスオブジェクトにあたるものとして、「業務情報から印刷用の情報を作成するオブジェクト」が考えられます。ビジネスオブジェクトはドメイン層に所属するように設計します。
このように、ビジネスオブジェクトと非ビジネスオブジェクトを分離することで、開発者は業務固有の振る舞い(どのような画像印刷用の情報を作ればいいか)に専念できます。
#発生している問題
私の周りのプロジェクトではSQLがビジネスロジックを担っているにも関わらず。なぜか、ドメイン層から隔離されています。これにより、ドメインの分断がおきドメイン駆動設計に沿った開発が行えなくなっています。
##影響
その結果以下のような手間が発生しています。
- 手続き型言語の部分とSQLの部分が別々に開発されており、動作確認のためにスタブを用意する必要がある
- 開発、保守の際に、手続き型言語のある部分でどのSQLが発行されているかわからず調査に時間がかかる
- 手続き型言語でのSQL呼び出しAPI部分について、誰が見ても分かるような仕様書を残しておく必要がある。(と言われ仕様書を書かされる)
#なぜこんな設計になっているのか?
なぜ設計者がSQLをドメイン層から分離したがるのか考えたところ2つ思い当たるところがありました。
###1. SQLをハードウェアだと思っているのではないか
みなさんもこの通りの図でなくとも、似たような図を見たことがあるかと思います。そして大抵は、「ハードウェアの層を分離して、Model層が意識ししなくても済むように設計しよう」とまとめられていると思います。
データベース自体は、HDDやマウスと同じようなハードウェアに所属しますが、その上で動くRDBMSはそんなことはありません。なぜなら業務ごとに発行されるSQLは全く異なるものになるからです。(単純な読み込みだけの場合は別です)
このような背景があるため、何が何でも手続き型言語からハードウェアを扱うものであるSQLを排除しないといけないという考えが働いているのではないでしょうか。
###2. 過去のパターンをコピペしているから
私の周りのプロジェクトのDBアクセスの構成ですが下のリンクから見れるような、「業務クラス」と「Facadeクラス」と「DataBaseAccessクラス」が横に並ぶような構成になっています。「DB dao」で調べるとでてきたので、世間で一般的な設計になっている気がします。
これをそのまま持ってきたので、ドメイン層が分離したままの状態になっているのではないでしょうか。
http://www.atmarkit.co.jp/ait/articles/0506/08/news108_2.html
#解決方法
下図のような、構成のプロジェクトでビジネスオブジェクトをまとめることを考えます。
業務クラス:手続き型言語での業務フローが書いてある部分
Facade:データベースアクセス用のインタフェースを提供する。内部でデータベースの接続と切断、トランザクション管理も行っている。
DAO:SQLの構築、発行を行なう箇所。(DataAccessObject)
解決方法は簡単です。業務クラスにDAOクラスを持たせるようにします。Facadeという名前も意味が変わってしまったので他の名前にしましょう。
DataBaseManagerクラスでは、データベースの接続、切断、トランザクション管理を行います。これは業務内容に関係ないので非ビジネスオブジェクトになります。
##業務クラスからの呼び出し例
C#で業務クラスからの呼び出し部分を書いたらどんな感じになるのかを書いておきます。おそらく高階関数を用いた実装になると思いますし、私としてもこちらのほうが理解しやすい気がします。
public void MainWork()
{
// SQL用パラメータ
string name = "Suzuki";
// DB管理用のクラス作成 実際はもう少し生成が複雑になる
var dataBaseManager = new DataBaseManager();
var dao = new WorkDao();
// SQLの実行 実際は処理の内容によってパラメータ、メソッド名を考慮する必要あり
var record = dataBaseManager.Execute(() => dao.GetID(name));
}
public void SubWork()
{
// SQL用パラメータ
string name = "Masuda";
// DB管理用のクラス作成 実際はもう少し生成が複雑になる
var dataBaseManager = new DataBaseManager();
// daoの生成
var workData = new WorkDao();
workData.Name = name;
IWorkDao dao = workData;
// SQLの実行
var record = dataBaseManager.Execute(dao);
}
#まとめ
SQLがビジネスロジックを含む場合は、ドメイン層で構築を行なうようにしましょう。
-
業務の流れや、業務での処理をプログラムで表現したもの ↩