この記事は スターフェスティバル Advent Calendar 2021 の16日目の記事です。
昨日は @yoshifujiT さんの React Native まわりの話②
でした。
自分も前職ではアプリのリリースに密接に携わっていたので、Webとアプリのリリースサイクルについて非常に頭を悩ませましたね・・!
APIの開発時の後方互換の意識、非常に強く意識してた気がする。。。! わかる・・! という共感の嵐でした。
( 慄く ... よめなかったな・・)
ちなみに、前々回の yoshifujiT さんの投稿はご覧になられましたか?
React Native Japan の Discord でも記事が紹介されたらしいですよっ! React Native に興味の有る方はぜひ参考になってください。
宣伝ですが、我々 スターフェスティバル
は、今とても働く仲間を募集していまして、話だけでも聞いてみたいかな? って思った方は、是非 Findy とかから いいかも
してみてください!!
さて、自分としては、今回のアドベントカレンダーに続いて、3記事目です。
今回はこれまでの記事と違って、少しテック側に振った内容です。 書いてみたいと思います。
さて、本題
今回は主にRDBMSにおけるテーブル設計についての記事です。 自分は普段の業務では主に自分のスキルセットであるバックエンド開発やデータベースの設計を担当しています。 イミュータブルデータモデリング
というモデリング手法があるのですが、それを実際に使って運用していっています。
その中で、こういうのを予め意識しておくと、やりやすくなるかも、と感じた事について書いてみたいと思っています。
イミュータブルデータモデリング
まずは、イミュータブルデータモデル というデータモデリング手法について、ご紹介していきましょう。
実際の出典を見つける事はできなかったのですが、自分がこの言葉を知ったのは kawasima (@kawasima) | Twitter さんの以下のスライドでした。
www.slideshare.net
詳しく知りたい方はスライドをご覧いただくのが良いと思いますが、自分の中で一旦イミュータブルデータモデリングとして目指している所としては以下に集約されていると思います。(引用です)
- テーブルに対してデータのUPDATEを極限まで削る
- それにより責務(ライフサイクル)が明確に分割されたテーブルが得られ、結果としてテーブル構造が拡張に対して開いていて修正に対して閉じている状態になる(致命的な問題も起こりにくくなる)
「UPDATEを極限まで削る」といっても何するの? と思われる方も居るかもしれません。上記に対して、もう少し具体性をもった作業に落としこむとと、以下の記述がそれに該当すると考えています。(これもスライドの一部引用です)
- つまり、更新日時カラムを徹底的に排除する事を目指す
- もし、更新日時カラムが必要になりたいと思ったら、一旦立ち止まって、テーブルを分割して、どうにか作成日時だけあれば良い or そもそも日時が不要 となるような構造にならないかを検討していく
上記にあげたような作業を 対象のドメインに対して、細分化しきるまで、繰り返していく事をやっています。
とはいえ、実際のドメインに対しては 更新が必要なケースも出てくるでしょう。 自分がイミュータブルデータモデリングをやっていく中でも 更新日時カラムは多少なりとも存在しています。 イミュータブルデータモデリングは完全にUPDATEを廃止する事を目指しているのではなくて(それは難しいと捉えてる)、一部ミュータブルとなるテーブルがある事も許容されます。 そのミュータブルになる範囲を極限まで抑えていく事が意識すべきポイントだと思います。
イミュータブルデータモデリングまとめ
- 対象ドメインに注目して、UPDATEを極限まで削ったテーブル構造をモデリングしていく手法がイミュータブルデータモデリング
- UPDATEを廃止する事が目的ではないので、UPDATEがあっても許容される
- その範囲が極限まで削れているかを繰り返し検証していく事で、より変更に強いテーブルを定義する事ができる
自分が普段意識している事
という事で、今回の記事では、この イミュータブルデータモデリングにおいて、自分が実践している事を書いていきたいと思います。
何か例があった方が分かりやすいので、例えば 今回 自分が記事を投稿している はてなブログのようなブログ というドメインについて考えていきましょうかね。
ユースケースを書き出す
データモデリングに限らず、おそらく モデリングを行うときは大概 ユースケースを考えておく事は非常に重要です。
まずは、該当ユースケースを洗い出して、文字に起こしていきます。
ここでは、以下について 意識しながら書いていたりします。
- ユースケースの文言を
5W1H
を軸に言語化する - ドメインの性質上あってはならない事も意識する
という事で仮のブログサービスにおけるブログドメインのユースケースを言語化すると以下のような感じでしょうか。(基本的には はてなブログを軸に書いてますw でも、このエントリが長くなりすぎるな。。? みたいな所をみて、割愛してたりします。)
- ブログ会員は、ブログサイトをサービス内にもっている(今回のスコープ範囲外です)
- ブログ会員はブログサイトを一つ選択して、ブログ記事を書く事ができる
- ブログ記事は、タイトルと本文が必ず必要である
- ブログ会員はブログ記事を記述後にすぐに投稿して公開する事ができる
- ブログ会員は、ブログ記事をすぐに投稿せずに 下書きブログとして保存する事ができる
- ブログ会員は、ブログ記事をすぐに投稿せずに、投稿日時を指定して予約投稿する事ができる
- 下書きブログ記事、予約投稿ブログ記事はそれぞれリンクを生成して他者に共有する事で投稿前であっても投稿ブログ記事と同じように閲覧する事ができるが、それ以外の方法では閲覧出来てはならない
その他、色々あるのですが、この先扱いやすい感じに絞ってみました。(タグ・カテゴリー、アイキャッチ画像とかを省いてますっ)
絵に起こす
ユースケースが大体、記述出来たらざっと登場ドメインを図に起こします。
これは必須でも無いのかな、って思ってるのですが、単純に自分がグラフィカルな方が頭に入りやすいのでそのためにやってます。 主にリレーションの確認の為の作業です。
大きく、ブログ会員 ・ ブログサイト・ブログ記事 という "エンティティ" (エンティティかな・・) とそれぞれに属性を持っている状態をまとめました。
この図は、以前はCacoo とかを使ってたのですが、今は PlantUMLを使っています。
PlantUMLにした経緯としては 配置をこだわりだして、この図を書くのに時間がかかっていたからです。 表現に限界がある為、ある程度の所で諦めざるを得なくなってくる、というのが自分にとってメリットでした。
こんな感じです。
@startuml sample object "ブログ会員" as user { } object "ブログサイト" as site { ブログサイトタイトル ブログURL 開設日時 } object "ブログ記事" as blog { タイトル 本文 公開種別 投稿予約日時 プレビューURL 投稿日時 更新日時 } note right ・ 公開種別 ...下書き・投稿・予約投稿 ・ 投稿予約日時 ...予約投稿の時のみ必須 ・ 投稿日時 ...投稿した時だけ必須 ・ プレビューURL ...下書き・予約投稿の時だけ入る end note user ||-d-|{ site site ||-d-o{ blog @enduml
UPDATE を排除作業
という事で、ここからは 更新されうるデータについて着目をしていきます。
ブログサイトはすでにモデリング済みであると状況下と仮定して、ブログ記事
エンティティに対してだけ、手を入れていきます。
「状態」 を排除する
UPDATEを排除するにあたって、まず一番最初に意識する事としては、 フラグ(種別)を排除する
です。
特に ライフサイクル が種別になっているような場合だったりは ライフサイクルの変遷とともにUpdateが発生する場合がほとんどです。 なので、まずは、ブログとブログの公開種別を分割します。
完成形をひとまず見せるとこんな感じになります。
下部の 下書き・投稿・予約投稿 のそれぞれは、イベントが発生するたびに INSERT されていきます。
単純に、 ブログ記事と 1: n( n>=1) で紐付けてしまうと、どれが一番最新のものかが判別付きづらくなってしまうという問題もあるので、 それを管理するものとして ブログライフサイクル
テーブルが最新のステータス状況を管理する、というような作りです。
ちなみに、自分はこういう設計を取る場合もあります。
これは、ライフサイクルの変遷と共に 下書きブログ記事
から 投稿ブログ記事
へと DELETE => INSERT と移り変わっていくような設計です。
あくまで 現在の状態
が関心事であって、 下書きを更新する
イベントがいつ発生したかは特に保持しなくても良いような時に採用しています。
(このブログの例とかも僕はこっちを採用するかも)
どちらの方法が良いか、とかは、結局、それぞれのサービスのユースケース次第という回答になってしまいますね。
ホテルのチェックイン・チェックアウト とかのドメインを表現する場合はおそらくそれぞれのイベントの発生日時は記録しておく必要があると思います(料金の計算と関わる) ので、前者の全ライフサイクルを記録する方針を取ると思います。
リソースとイベントに分割する
続いては、 ブログ記事
というものに着目してみましょう。 これも タイトル
・ 本文
は常に更新可能なものです。
こういうのを分割するのは、エンティティを リソース
と イベント
に分けることで分割していく手法を用いて分割していきます。
先述のブログ記事にもありますが、 リソースとイベントの境目は 日時を持つかどうか
です。 または、 ~する
と動詞にする事ができるか、という境目とも捉える事が出来ます。
ユースケースに着目して見ましょう。
- ブログ会員は単一のブログサイトを選択して、ブログ記事を書く事ができる
- ブログ記事は、タイトルと本文が必ず必要である
- ブログ会員はブログ記事を記述後にすぐに投稿して公開する事ができる
ユースケースは ブログ記事を作成して、それにタイトルと本文というリソースを紐付ける、その後ブログ記事の状態を公開(etc) に設定する事ができる
と読み替えていきます。
自分もここは結構苦戦するのですが、考え方としては、時間軸をずらすようなイメージでユースケースを読み替えると分割がすんなりいったりします。
今回でいうと、タイトルと本文をもったブログリソースを作成する
という事ではなくて、 ブログ作成イベントを記録して、そのブログにタイトル・本文のリソースが持つ状態にする
みたいに自分は捉えています。
という事で、分割するとこうなります。
ブログ記事
に関しては、タイトル・本文は 更新する事が可能です。 先述した通り、僕はここは更新が発生しても良いと思っているポイントですね。
このサービスの特性として、あくまで 現在のブログ記事の状態を読者に見せる事に重きを起きたい
というサービスであると思ってるからです。
まあ。。諸説はあるかもしれない。
という事で、全てのテーブルに対して 日時属性が 0 or 1 の状態が実現出来ました。 これで モデリングはひとまず完了です。
制約を見直す
イミュータブルデータモデリングを行うと正規化の原則であるところの「One fact in one place」が自然と実現する事が出来ます。
これによって得られる恩恵として、 One fact
についての制約がしっかり貼れる事です。 RDBMSは日々進化を見せていて、自分も大きな恩恵を得ていますが、 Two fact
についての制約を貼るのはやっぱり避けたいよね、と思ってたりしますね。
NOT NULL 制約とか、ふんだんに貼りましょう!! アプリケーションでチェックする事ももちろん大事ですが、最終的に制約はデータをバグから守ってくれると思います。
完成形 & まとめ
という事で 分割しきった結果としては上記のような構成図になりました。
こういう事をやってきました。
- フラグやライフサイクルが移り変わっていくイベントがある場合はテーブル分けると良い
- 作成したユースケースの記述に着目して、テーブルを単一のリソースではなくて、リソースとイベントに分割できるモデリングをやっていくと良い
宣伝
(ガチ勢からすると程遠いかもですが) DDDを取り入れた開発をやってます。
もし、興味が出てきた! とかそういう開発やってみたい、って方は今、絶賛仲間を募集中です。
twitter DM でも良いですし、 ブログの最初に載せた findy へのいいかも、とかでも構いません!
是非とも!!!!何卒!!!!!