SaaSにカスタム項目の機能を実装する際に検討したこと
はじめに
SaaSを作っていると「企業ごとに自由に項目を追加したい」という要望はよく出てくると思います。
この記事では、実際にカスタム項目の機能を実装する際にチームで検討した内容をざっくり整理します。実際の実装では結構難易度が高くかなり詰まったのですが、自分が触ってたSaaS特有の問題で表現が難しい&書けない内容も多く、この記事では表面的な部分だけ記載しておきます。
基本構成
メタデータ(meta)と値(custom_field)に分けて設計しました。
-
meta
- カスタム項目のメタ情報を管理
- 型(文字列 / 数値 / 日付など)、必須かどうか、説明文、表示順 などを保持
-
custom_field
- 実際の値を保存
- 業務レコードに対して「キー: 値」をひもづけて持つ
- 物理テーブルは以下のようなイメージ(domain_idは業務データのidのイメージ)
domain_id | meta_id | value
検討する際には、以下の記事などを参考にしながら、EAV、横持ちの汎用カラム、JSONなどどれがいいかなぁとチームでいろいろ検討しました。
結局、今回はEAVっぽい作りにしました。理由としては以下です。
- データの取得時にはmetaデータもセットで取得したいので、JSONよりもmetaデータへのリレーションを持つ形の方がORMの機能も使えて便利だと思った。
- EAVの課題である検索のしにくさは、別途検索用テーブルに値を保持することで解決できると思った。
- 検索用のテーブルには、JSONカラムで最新の値のみを
metaのキー: 実際の値というフォーマットで値を保持するようにした。 - なので業務データの履歴データはEAV、検索用データはJSONカラムというハイブリットな構造になった。
- 検索用のテーブルには、JSONカラムで最新の値のみを
その他の検討事項
フロントとのインターフェース
フロントとのやりとりは以下のようなスキーマにしました。
{
・・・(業務データの他の値),
"customFields": [
{ "key": "favoriteColor", "value": "blue" },
{ "key": "age", "value": 30 }
]
}
上記の案以外に、以下のようにカスタム項目のキー: 値をJSONに組み込むことも考えましたが、試した感じ、OpenAPIの記載だと難しかったです。以下のようなadditionalPropertiesを使うことで、できるかと思いましたが、(詳細は省きますが)フロント用の型定義を自動生成するときに、キャメルケース、スネークケースの記法が意図通りにならず断念。
{
・・・(業務データの他の値),
"favoriteColor": "blue",
"age": 30
}
バリデーション
バリデーションは、ドメイン層のレイヤーでmetaに定義された情報と実際の値を使って以下の内容などのチェックを行うようにしました。
- 必須項目が埋まっているか
- 型が持つ制約にあっているか
以下、簡単なモデリングを記載します。
親要素であるBaseクラスでは各型に共通のバリデーション(ここでは必須チェック)を行い、子要素である各型のクラスでは、それぞれの型に特有なチェックを実施します。
まとめ
カスタム項目の実装にはいくつかのパターン(EAV、JSONカラムなど)があります。
今回は以下のような折衷案を取りました。
- メタデータテーブル + 値テーブルで柔軟に管理
- 値テーブルはEAV的に持つ
- 検索用に最新値を保持するフラットなテーブルを別途用意してJSONカラムで管理
- フロントとはJSONの配列でやりとり
- バリデーションはmetaと値のセットを使ってドメイン層で実施
これにより、
- スキーマを壊さず拡張できる柔軟性
- 検索・集計のしやすさ
- フロントから見たときに扱いやすいインターフェース
を両立できました。
今回、カスタム項目を実装できたことはとても良い経験になりましたが、実装に手間がかかるし、溜まったデータを分析したりするのにも一工夫必要になるし、メンテナンスも大変だと思うので、本当に必要なときにだけ実装すべき機能かなぁと個人的には思っています。
同様の機能を実装する際の参考になると嬉しいです。
Discussion