DDD特別夏季講習 / 3時限目 : レポジトリ


はじめに

本記事は前回の記事の続きになります。DDDをゼロから学びたい方はまず1時限目からの受講をお勧めします。
これまでの授業:
DDD特別夏季講習 / 1時限目:値オブジェクトとエンティティ
DDD特別夏季講習 / 2時限目:ドメインサービス

今日は、レポジトリについてまとめていきたいと思います。

対象読者

  • 職場でドメイン駆動開発を使って開発を行っている方
  • これからドメイン駆動開発の導入を検討している方
  • エリック・エバンスの本分厚いよー、ぴえん🥺って方
  • その他、ドメイン駆動開発ってなにー??って方

レポジトリの意義・必要性

前回までの記事で、ドメインモデルをドメインオブジェクトの実装に落とし込むための手法について解説してきました。
では前回までの内容を踏まえて、実際にドメインを実装してみましょう。


class User {
 const buy = (item: Item[]) => {
  const connectionString = // DBと接続するための処理...
  {
   DB.hogehoge... // DBのCRUD処理
  }
}
}

まあ実際の処理はめんどくさいので割愛しますが、たいていの場合、ドメインにはそのドメインの振る舞いが定義され(userが何か商品を買うとか、プロフィールを変更するとか)、そしてそのほとんどがDBに値を書き込んだり、DBの値を参照します。
ということはこのまま実装を進めていくと(進めていけばいくほど)、ドメインにDB処理のコードが入り込むことになります。
前回の授業までで、ドメインは現実世界のドメインを実装に落とし込んだもので、いわばコードで表現されたドキュメントのようなものだと解説しました。DB処理が大量に入り込んだコードでは、そのドメインがどう言ったものかよくコードの細部まで読み込まなければわかりにくくなってしまいます。
この問題を解決するのがレポジトリです。

レポジトリの責務

レポジトリの責務はDB処理です。受け取ったデータをDBに永続化したり、DB内のデータを参照しなんらかの値を返したりする処理が記述されます。
DB処理を、このレポジトリ層にうつし、ドメイン層ではそれを呼び出すだけという風にすれば、ドメイン層は大変すっきりし、ドキュメント性も向上します。

// ドメイン層
class User {
 const buy = (item: Item[]) => {
  userRepository.save(item) // ドメイン層にはDB処理のコードは書かず、レポジトリ層で実装したそれを呼ぶ
}
}

// レポジトリ層
class UserRepository {
 const save // DB接続、CRUD処理の記述
}

はい、これで一件落着!簡単じゃーん。と思ったそこのあなた。本当にそうでしょうか??
これはDDD的に(もっというとアーキテクチャ的に)、御法度です。

依存関係

ここで一つ理解しておくべき概念があります。それが依存関係という考え方です。
以下に例をあげます。


class Human extends Animal {
 const think = () => ...


 bark("pien")
}

class Animal {
 const bark = (cry: string) => ...
}

このケースではHumanクラスがAnimalクラスを継承しています。Animalクラスは吠えるみたいなbarkメソッドを持っています。これは基本的にどの動物も泣き声を持っているからですね。これによって、Humanクラスではいちいちbarkメソッドを定義しなくてもbarkメソッドを使えます。
逆に言えばHumanクラスでbarkメソッドを実行するにはAnimalクラスの存在が必須です。これは、HumanクラスがAnimalクラスに依存しているからに他なりません。

DDDにおけるレイヤー間の依存関係

この依存関係はDDDのレイヤー間でも生じます。そしてそのあるべき関係性は決まっています。

これは採用するアーキテクチャにもよりますが、大枠は変わりません。外側の層から内側の層のみへの依存が認められ、その逆は認められません。ですが先ほどあげた例ではドメイン層が一番外側にあるレポジトリ層(インフラ層)に依存しています。

// ドメイン層
class User {
 const buy = (item: Item[]) => {
  userRepository.save(item) // ドメイン層にはDB処理のコードは書かず、レポジトリ層で実装したそれを呼ぶ
}
}

// レポジトリ層
class UserRepository {
 const save // DB接続、CRUD処理の記述
}

これを解決するために、抽象に依存させることによる依存性の逆転を用います。

抽象に依存せよ

では、依存関係が望ましいものになるように先ほどのコードをリファクタしましょう。
まずはレポジトリを実装クラスと型クラスに分けます


// 型クラス
class UserRepository {
 const save(item: Item[]): Item[]
}

// 実装クラス
class UserRepositoryImpl extends UserRepository {
 const save = (item: Item[]) => {
 // DBのCRUD処理
 }
}

そして型クラスであるUserRepositoryクラスはドメイン層(場合によってはユースケース層におくこともあるが)に置きます。そして実装クラスはレポジトリ層に置きます。

これによって依存性は
ドメイン -> レポジトリ
から、
ドメイン <- レポジトリに逆転します。
実装クラスはより抽象度の高い型クラスに依存するためです。

これにより依存性の原則は守られます。

なぜ依存関係が大切か

アプリケーションサービスは変化するものです。ユーザからの要望や事業の方向性の転換によって、改修に次ぐ改修が生じます。開発者は今動くものを作るだけでなく、将来の改修リスクについて常に考える必要があります。
DDD及びそれに伴うアーキテクチャの原則を守ることは、そのリスクへの処方箋です。
ドメインはサービスの核となる概念です。これが頻繁に変更されることはありません。
ところがより外側の層であるレポジトリ層はどうでしょう。新機能の追加や、既存機能の修正に影響を受けやすいと言えます。また、DBの変更など、技術的な要因によって変更があるかもしれません。
依存関係がしっかりと守られていれば、コードの変更箇所を最小限に抑えることができます。

終わりに

いかがだったでしょうか?今日の授業ではレポジトリについて解説しました。レポジトリによってドメインのドキュメント性が高まることに加えて、正しく依存性を守りつつ実装すれば、将来の改修リスクに備えることができます。
次回はコントローラ層について学びます。是非お楽しみに。