社内でドメイン駆動設計入門の読書会 #5
こんにちは。Nonです。
今回も会社で読書会をしている話をしようと思います。
内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。
より具体的なコードや内容がみたい!という方は購入しましょう!
読んでいる本
読んでいる本はこちらのドメイン駆動設計です。
ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本
以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。
そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。
進行方法
読書会の進行方法は
- 今回読書の対象にする章を決める。
- 10〜20分間その章を読む
- 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう
- その後40分間で、その章に対する疑問や考え方をディスカッションする
- 1〜3を毎週定期的に行う
という進行方向となっています。
社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。
今回読んだ内容
第4章の不自然さを解決する「ドメインサービス」です。
- データにまつわる処理を分離する「リポジトリ」
- リポジトリとは
- リポジトリの責務
- リポジトリのインターフェース
- SQLを利用したリポジトリを作成する
- テストによる確認
- テストに必要な作業を確認する
- 祈り信者のテスト理論
- 祈りを捨てよう
- テスト用のリポジトリを作成する
- オブジェクトリレーショナルマッパーを用いたリポジトリを作成する
- リポジトリに定義されるふるまい
- 永続化に関するふるまい
- 再構築に関するふるまい
- まとめ
私はWEBでの管理プログラムや、WindowsFormを用いてアプリ作成などしかプログラムを作成した経験がないので、MySQLなどしかデータストアとして使ってきませんでした。
この章を読んで色々なものがデータストアとして見ることができることに気づきました。
ディスカッション
リポジトリとは?
経験の長い方が、読書会に入ってくださっているので、簡単に説明してくれました。
DDDの読書会をしているときに申し訳がないが、そもそもドメイン駆動設計の論点とは、設計全体ではなく、例えばMVCのM(モデル)の設計手法について語っているように感じます。
なので、ここでいうリポジトリはこれまでと違って、 「 データ」 をメインに扱うことになるので、コレまでやってきた内容とは一線を画すように感じます。
前回までは、できるだけ「データ」に注目することなく進んできましたが、データストアを扱う章なので、今回ばかりはドメインではなくデータに注目することになりそうです。
第1章でもドメインオブジェクトは知識をプログラムで表現するとありました。
リポジトリとはドメインオブジェクトではなく、ドメインオブジェクトに侵食しそうな プログラムを受け止め防衛するものかもしれません。
COLUMN: リポジトリはドメインオブジェクトを際立たせるでも書かれていることですね。
ユーザーみたいに簡単なエンティティの永続化の例はわかるけど、複雑なリレーションが組まれたものに対してはどのように実装すべき?
これは経験の浅い方からの質問でしたが、私も同じことを思いました。
その日の読書会メンバーの中には答えを持っている人はいませんでしたので、私達なりに一つの結論を出しました。
中間テーブルにはリポジトリを作らず、臨機応変に対応すること。
例:ブログ記事とタグの関係
例えば、記事のみを扱うリポジトリを考えてみましょう。
$articleEntity = new ArticleEntity();
$articleRepository = new ArticleRepository();
$articleRepository->save($articleEntity);
class ArticleRepository
{
public function save(ArticleEntity $article)
{
// 記事の永続化処理
}
}
他にもメソッドを持っているかと思いますが、とりあえず永続化のsave
を持ったArticleRepository
を作成しました。
記事を作成したときに、このリポジトリのsave
を呼んで永続化します。
しかし、記事登録のユースケースはそれだけでしょうか?例えば、記事には1つ以上のタグが紐づくものとしましょう。
$articleEntity = new ArticleEntity();
$articleRepository = new ArticleRepository();
$articleRepository->save($articleEntity);
class ArticleRepository
{
public function save(ArticleEntity $article)
{
// 記事の永続化処理
if ($article->hasTags()) {
// タグの永続化処理
}
}
}
これではなにか不自然に感じてしまいます。ArticleRepository
がタグの永続化処理を持っているのです。
ということは、$articleRepository->save($articleEntity);
を呼んだ後、この処理をすべきでは無いでしょうか?
$articleEntity = new ArticleEntity();
$tagEntity = new TagEntity();
$articleRepository = new ArticleRepository();
$tagRepository = new TagRepository();
$articleRepository->save($articleEntity);
if ($article->hasTags()) {
$tagRepository->save($tagEntity);
}
class ArticleRepository
{
public function save(ArticleEntity $article)
{
// 記事の永続化処理
}
}
class TagRepository
{
public function save(TagEntity $article)
{
// タグの永続化処理
}
}
記事とタグの紐付けは?
これについても出てきましたが、前章エンティティでのディスカッションの通り、記事がタグを持っているかどうかをエンティティで表現すれば可能になるかもしれません。
class ArticleEntity
{
public $tagIds;
public function __construct()
{
// 省略
}
public function hasTags(): bool
{
return count($this->tagIds) > 0;
}
public function getTags(): array
{
return $this->tagIds;
}
}
やっぱりふるまいは持つべきではない。
弊社はPOSレジを扱う会社ですので、色々な集計関数をSQLを用いて取得することでより高速にレジを打てるように努力しています。
しかし、集計関数を利用すると、SQLにロジックを書くことになりそうです。その時リポジトリはこのようになってしまうかもしれません。
class TransactionRepository
{
public function sum(): int
{
// 集計関数などを用いたSQL
}
}
この著書ではExists
が例に挙げられていましたが、このsum
も同様な気がします。
よほど困った理由がなければ上記のようなコードをRepository
書かずにEntity
に書くべきでしょう。
class TransactionEntity
{
public function sum(): int
{
// 集計処理
}
}
最後に
リポジトリについては、データを扱うものからドメインオブジェクトを守るという考えが最初に理解できたので、簡単に理解できたと思います。
データストアに保存する処理や、データストアから取得する処理をできるだけ他のロジックから切り離して考えるというのは、今の知識でも理解できていますので。
リポジトリに何を書くかではなく、リポジトリをどのように扱う(呼び出す)かというのが大事になってくるのかもしれません。
次回は待ちに待ったアプリケーションサービスですね。
その時はよしなに。
.