この記事のモチベーション
RDB(Relational DataBase)の歴史は長く、ノウハウも蓄積されている。
関係代数という学問もあるなどアカデミックにも研究されており、
国家資格である「データベーススペシャリスト」も、ほとんどこのRDBのことを扱うなど、
RDBは一定の体系が確立した技術と言える。
一方で、2010年ごろより広まったNoSQL、特にドキュメント指向DBに関しては、
当然ながらRDBに比べると未成熟な段階であろう。
また主観だが、NoSQLは、スケーラビリティといった物理的側面にfocusが当たることが多かったのではと思う。
一方で、論理的側面 = モデリング に関して、および物理的側面と論理的側面のトレードオフの関係について、
議論されつくされているとは言えない。
本稿では、DDD(ドメイン駆動設計)というプログラミングの論理的側面の手法を、
各DBがいかに扱えるかということを主な話題とする。
また、それが物理的側面の制約をどのように受けるか、ということも論じられたらと思う。
RDBの論理的側面(DDDと絡めて)
ER図とDDD
RDBを利用してスキーマを定義するとき、E-R図(E: Entity, R: Relationship)作成は欠かせない。
語弊を恐れず言うと、ここで定義されたEntityと、多対多のRelationshipに対して、テーブルが定義される、という理解だ。
(ER図も粒度があるとのことだが、ここではテーブルを作成するときの粒度のものを思い浮かべてほしい)
さて、ではここでいうEntityと、DDDでいうEntityは、一致しているのだろうか。
これは個々の解釈に依存するので、深入りしたくない領域でもあるが、最低限、定義を確認する。
- DDDで定義されたEntityとは、identityによって区別される必要のあるモデルのことだ。
- ER図のEntityは属性をもったデータの集まりと解される事が多い。
DDDでは「Model」と呼ばれているものが、ER図のEntityに相当すると思っても良い。
つまり、ER図のEntityにはDDDでいうValue Objectも含む(としてER図を書く人もいる)のだ。
特に昨今隆盛を極める、ORマッパーつきのeasyなWeb Frameworkを用いると、
Value ObjectやLocal Entityといった概念スキーマの深淵に深く入らずとも、
永続化するべきデータの集まりであれば、とりあえずActiveRecordなどで管理できてしまう。
概念レベルでは存在しないglobalなidを付与されるが、それはRDBの要求である。
構造化の方向性の違い
語り尽くされているが、ObjectとRelationのインピーダンスミスマッチは、DDDとRDBの垣根をつくる。
集約, 階層化という縦方向の広がりをもったDDDに対し、テーブル、属性という横方向に関心のあるRDB。
RDBの論理モデリングは、DDD内の概念モデリングがそのまま引き継がれないことも多い。
DDDでは、Repositoryという概念で、RDBという「外界」からデータをとってくるという位置づけにしている。
本題: ドキュメント指向DBとDDD
ドキュメント指向DBと集約
ドキュメント指向DBは、ざっくり言ってしまえば、階層化、縦方向に関心のあるDBだ。
テーブルという、データ格納の効率性を犠牲にして、スキーマをなくした。
このドキュメント指向DBに、そっくりそのままDDDでいう「集約ルート」を格納することを考える。
集約ルートは、外部から参照されうる集約の代表で、集約とライフサイクルをともにしたEntityである。
集約ルートの配下に、集約に所属するLocal EntityやValue Objectを含めることで、
- 集約のライフサイクル === 集約ルートのライフサイクル
- 集約ルートのみが外部から参照されうる
- 集約に閉じた処理のトランザクション性 (1ドキュメント内の処理はAtomicになるので)
といった性質が満たされる。
ドキュメント指向DBは、集約を軸にしたDDDモデリングと相性がいいといえる。
ドキュメント指向DBとRepository / Factory
DB to memory
そうはいってもDBはプログラミング言語の扱うメモリ領域からすれば、外界だ。
結局はDB内に構造化された集約のデータを、適切にメモリに載せる必要がある。
JavaScriptなどJSONからモデルへの変換がほぼシームレスに行える言語であれば、その心配はない。
せいぜいconstructorに処理を書く程度だ。あとはそのままDDDの世界のコードを実行すればよい。
静的型付けの言語も、型変換のライブラリを利用するなどがORマッパー的といえるかもしれないが、
RDBよりも直感的なマッピングが可能である。
memory to DB
DBにstate sourcingする場合、集約ルートをJSON化するメソッドさえ定義しておけばよい。
JavaScriptではその必要さえなく、モデルをそのまま送信してもよき解釈をされる。
event sourcingをしたい場合は、差分処理(Update Operators)をそのまま保存することになる。
無限に増えるデータ問題
しかしこれは、論理に閉じた場合の話であり、ドキュメント指向もまた物理的制約を受ける。
集約内のデータが無限に増える時の話である。
例えばヘルスケアアプリ等における無限に測定可能な体重や血圧などのデータは、
ユーザーという集約ルート内のValue Objectだ(Local Entityだ、という人もいるが、ここでは議論しない)。
これを、ドキュメント指向DBのユーザー配下に格納することを考える。
そうすると、1ドキュメントのサイズ制限に達するリスクがある。
Mongoは16MB、DynamoDBだと400KBという制限がある。
この制限のために、集約をそのままモデリングすることが躊躇されるかもしれない。
実際には、日々のデータサイズを見積もることで、格納が可能という結論に達したりもするので、
モデルの数量的見積もりが判断に重要になる。
処理側との通信量
たかだか400KBのデータであれば、1つの集約を通信によって取得/処理するとよい。
JavaScriptなどJSONからモデルへの変換がほぼシームレスに行える言語であれば、そのままDDDの世界のコードを実行すればよい。
問題は、16MBのデータだ。この場合、1つの集約をまるまるクライアントに落とす処理を毎回行うのは効率が良くない。
SELECT FIELD_A FROM TABLE
のように、mongodbもprojectionというしくみで、特定のfieldを取得しないようにできる。
ただこの場合、モデリング側がLocal Entity/Value Objectがある前提でモデリングしているため、Null参照エラーとなってしまう。
もちろんその結果をnullableなtypeとして表現することはできるが、それは物理的制約が論理モデルに漏れ出ている状態である。
ViewModelとか、概念モデルよりもさらに実践的なレイヤが、概念スキーマをすっとばしてDBの好きな値をより好みするということになると、
概念モデリングの本質をそのまま享受できない。
(≒たぶんGraphQLに対して私の抱く違和感はこれだ)
Event Sourcing / 状態同期の可能性
上記の問題の解決策は、個別の問題に限れば、ないわけではない。
例えば、クライアントが関心を持つ集約が限られている場合に、そのクライアントが内部でストレージを持つことを考える。
クライアントは、DBのデータそのものでなく、DBの差分を取得し、それを反映するかたちでデータを管理する。
そうすると通信量は少ないまま、集約をすべて手元に持っておくことができるだろう。ロバスト性やpush型通信などに課題はあるが、
クライアント主権時代、関心のある集約が少ない通信量で同期される世界が実現できれば、
プログラマーの仕事は、概念スキーマに集中することだけになる。
まとめ
ドキュメント指向DBのモデリングは、よりDDDにとって直感的なものであるといえる。
一方で、決して無視できない物理的制約を、導入前に評価することが求められる。
評価はドメイン知識、サービスの運用イメージが必要となる。
その大枠が決まれば、あとはソフトウェア開発者の仕事は、利用側の概念スキーマに集中することとなるだろう。
参考文献
-
Operational Factors and Data Models - MongoDB manual
この記事を書くきっかけは、ほとんどこのMongoDBのデータモデリングの記事に感銘を受けたということだ。ぜひご一読いただけると、より深い理解になると思う。 -
スキーマ(データベース) - Wikipedia
概念スキーマというのがDDDのモデリングに相当するのだ、と理解した。 -
オブジェクト関係マッピング - Wikipedia
テーブルという概念は、本質的にオブジェクトという概念と合わない。それを乗り越えるための対症療法。 -
Rails における値オブジェクトと ActiveRecord の composed_of
Value Objectを扱うためのRailsのeasyな仕組み。改めてRailsの柔軟な表現力に脱帽だ。 -
RDB技術者のためのNoSQLガイド NoSQLの必要性と位置づけ
とにかく名スライド。 -
静的型付けと動的型付け~JavaとJavaScriptのJSON処理を比較~
静的型付けでは、外界を深く理解していることが求められる。