はじめに
個人開発しているプロジェクトで TypeORM を採用しているのですが、DB(TypeORM)におけるエンティティがそのままドメインモデル*1 になっていて、とにかく違和感がありました。
ベースはレイヤードアーキテクチャに近いものです。
*1 ここでのドメインモデルは DDD のエンティティと値オブジェクトをひとまとめにして呼称してます。DB におけるエンティティとは別だと認識しています。
ちゃんと設計しろよという話なのですが、その違和感がどうしても頭から離れなかったため、今回はその違和感を言語化(問題として認識する)、ドメインモデルから TypeORM を取り除く方法を自分なりに考えてみました。
あくまでも筆者はこう考えたのね程度に思っていただければです。
対象読者
- DDD の初学者(私は超初学者)
- Node.js/TypeScript/RDB でバックエンドを書いている方
- TypeORM を利用している方
tl; dr
当たり前と言われればそこまでですが、ドメインモデルは一切のライブラリに依存せず他レイヤとも依存しないようにして、TypeORM はインフラストラクチャレイヤに閉じ込めてしまう、が現時点の解法です。
サンプルコードを書いています。 当記事では具体的なコードの説明はしていないため、サンプルからディレクトリ構造や具体的な処理を見て、何をしたいかが多少伝わればと思います 🙏
想定している問題点
違和感を言語化すると以下 3 点の問題が起きていると考えています。
- ドメインモデルと言っているが、実体は DB におけるエンティティを指している
- ドメインモデルが特定の ORM に依存している、微レ存ですが ORM にモデル自体の寿命が握られていると捉えられる
- ドメインモデルを設計しているようで、実はテーブルを設計していて、本来のビジネスロジックのオブジェクト化からずれている
また、これから以下の 2 点の問題が起こると考えています。
- インピーダンスミスマッチを回避できず、エンティティが肥大・複雑化してテーブル設計が難しくなる
- 別の ORM に移行した際に間違いなく大幅な改修が入り、微レ存で同じビジネスロジックを表現できなくなる可能性がある
どうあるべきと考えているのか?
- ドメインモデルと DB のエンティティを明確に区別する
- 適切にインターフェースを利用して、使う側は ORM 自体を知らなくても良い状態にする
どうするのか?
オニオンアーキテクチャを参考にしつつ、ドメインモデルは一切のライブラリに依存せず他レイヤとも依存しないようにして、問題の TypeORM はインフラストラクチャレイヤに閉じ込めてしまいます。
- エンティティとドメインモデルが剥離するのはどうしようもないため、ドメインモデル側は集約を使って両者間のデータの受け渡しをできるようにする
- TypeORM のリポジトリ機能も便利だが、ライブラリへの依存を排除するために、リポジトリのインターフェースを用意する
急にオニオンを引き合いに出しましたが、ドメインをロジックのみに集中できるように依存関係を制御できるアーキテクチャであれば問題ないと考えています。オニオンのレイヤ構造と実際のディレクトリ構造がしっくりきたためです。
おわりに
インピーダンスミスマッチを解決するための ORM ですが、それすらも抽象化したいと考えると、今回のような問題に直面して、結局二重実装(ドメインモデルと entity を別々で記述、リポジトリのインターフェース化)のようなことに陥ってスピード感が失われそうだなと思いました 😅