<-- mermaid -->

基幹システムではないtoB SaaSにおいて組織情報のDB設計はどこまで必要なのか

はじめまして、AlphaDriveのWebアプリケーションエンジニアの堀と申します。
本記事は、NewsPicks Advent Calendar 2022 の15日目のブログとして記載させていただきました。

はじめに

最初にこのタイトルで記事を書こうと思った経緯について記載しようと思います。
私は前職で大企業向けのERPパッケージソフトのソフトウェアエンジニアをしておりました。
私の担当は会計ソフトの一領域でしたが、ワークフロー、検索、集計処理、セキュリティといった幅広い機能で会社の組織情報にアクセスする必要がありました。
不具合の修正、機能改修、トラブル対応などを通して、企業の組織情報の複雑さとそれを扱うことの難しさを身をもって知る機会が多々ありました。
そして転職して現在の会社、AlphaDriveにジョインし、開発中の「基幹システムではない」toB SaaSで組織情報全般のDB設計を担当することになりました。
この「基幹システムではない」というのがポイントで、前職で扱っていたゴリゴリの基幹システムであるERPパッケージソフトとは異なり、ユースケースも組織情報を扱う範囲も限定的です。
「基幹システムであろうがなかろうが同じように設計して、一番複雑な構造に対応すればいいんじゃないの?」と思う方もいらっしゃるかもしれません。
確かに基幹システムのように、どんな複雑な組織構造でも扱えるようにDBを緻密に設計しておけば、アプリ側の実装次第でどんなユースケースにも対応できると思います。理論上は。
しかし、DB設計が複雑になれば当然アプリの実装も複雑になり、開発のコストや不具合の発生リスクも膨れ上がります。
複雑なデータ構造をメンテナンスするための顧客やCSの対応コストも膨れ上がります(組織改変の季節が来るとピリつきます・・・)。
にも関わらず、実際のユースケースではそのうちのごく一部しか利用しないとしたら、DB設計としてはToo Muchな設計で失敗と言えるのではないでしょうか。
後になってDB設計をシンプルに作り直すということは簡単ではありません。
ゆえに、弊社で開発中のサービスのように、基幹システムではない、つまり組織情報を扱うことそのものがメインではないtoB SaaSにおいては、自分達のユースケースを満たせる丁度いいラインのDB設計が重要になると考えています。 そこで、自分が設計する中で考えたことや決断したことの経緯についてこの記事にまとめることにしました。
同じようにtoB SaaSで組織情報のDB設計されている方、これから検討される方の目に触れ、参考になったり議論のネタになれば幸いです。

組織情報とは

まず、組織情報とは具体的に何なのかについてですが、組織を構成する要素(マスタ)と、社員の所属情報の2種類に分けて説明したいと思います。

組織を構成する要素(マスタ)

組織を構成する要素には、会社、部門、事業所、役職といったものがあります。
皆さんも目にしたことがあるかもしれませんが、企業の組織図には会社というものがトップにあり、その下に各事業部門、事業所、そしてそれらを統括する役職などが階層構造上に表現されます。
これらの情報はDB上ではマスタデータとして管理され、後述の所属情報やさまざまなトランザクションデータから参照されます。

社員の所属情報

組織を構成する要素と社員をつなげる情報として所属情報というものがあります。
「どの社員がどの部門、事業所に所属しているか」「どの社員がどんな役職についているか」といった情報です。
これらの所属情報を組み合わせることで、社員同士の上司部下関係なども把握することが可能となります。
また、社員によっては複数の部門、役職を「兼務」していることもあり、そういった要素も考慮して設計する必要があります。

組織情報の何が複雑なのか?

ではこれらの組織情報の複雑なポイントについてもう少し具体的に掘り下げてみたいと思います。

階層構造

企業の組織図を見ていただくと分かると思いますが、組織は階層構造になっています。
階層構造をRDBで表現するためのモデルとしては、隣接リストモデル、経路列挙モデル、入れ子集合モデル、閉包モデルなどが有名です。
これらのモデルはどれもデータの参照や更新、削除において一長一短があり、一概にどれが最も優れたモデルと決めることができません。
自分達が採用したデータベースの特性や、サービスのユースケースなどを基準に、設計方針を決める必要があります。

主務・兼務

所属情報の説明でも少し触れましたが、社員によっては複数の部門や役職についている人がいます。 メインの所属を「主務」、それ以外を「兼務」として区別しますが、主務は1つなのに対して兼務は複数ありえます。
この主務、兼務をDB設計に組み込む上での方針は大きく2つに分かれると思います。
1つは主務と兼務でそれぞれテーブルを分けるという方法。そしてもう1つは同じテーブルでフラグなどで主務、兼務を管理する方法です。
これはどちらも一長一短だと思いますが、後述の履歴とも密接に関係してくるため、なるべく主務、兼務の前後関係(いつから兼務が始まり、いつ終わったか)が分かりやすい形で保持しておくことが重要です。

履歴

組織を構成する要素、所属情報どちらにも「履歴」という概念があります。
この履歴を表現する方法としては、「開始日」、「終了日」を各レコードに設定し、期間が被らないようにレコードを積み上げていく形が一般的かと思います。
例えば、「部門マスタ」においては、部門の名称変更、部門の統廃合といった組織改変イベントによってマスタの履歴が増えていきます。
社員の所属情報の履歴は、組織改変に加えて人事異動が起きたタイミングでも増えていきます。
この履歴という概念は業務システムにおいてはとても重要で、様々なところで参照されます。
最も一般的なのが、過去のデータの参照時でしょうか。
例えば「営業第1課」という部門が2022年4月1日から「新規開拓営業」という名称に変わったとします。 2022年3月までに同部門で起票された経費申請データを照会する際には「営業第1課」と表示される必要があり、4月以降の申請データでは「新規開拓営業」と表示される必要があります。 また、ワークフローの判定条件などにおいても、特定の日付を基準に組織情報を取得して判定しなければいけないケースがあります。
よくあるトラブル例として、「〇〇さんの申請がXXさんの承認画面に表示されない」といったものがあります。
これだけだとシンプルな問題に見えますが、実は〇〇さんは今月頭に新しい部署に異動したばかりで、XXさんも先月から複数の部署で部長を兼務している、といった複数の事象が重なると、問題も複雑性を増します。

組織情報の何をどこまで扱うべきなのか?

さて、ここからが本題なのですが、これらの組織情報は全て必要なのでしょうか?
特に上に挙げた階層構造や主務・兼務、履歴を扱うとなると、DB設計も複雑性を増し、それを扱うアプリ側の実装も比例して複雑になります。
本家本元のHR基幹システムでは全部必要ですし、大企業で利用されるようなERPパッケージソフトにおいてもほぼほぼ必要になってくると思います。
しかし、冒頭でも少し触れたように、企業に利用されるシステム、サービスだからといって全て必要とは限らないと思います。
特に、基幹システムではない、特定の領域に特化したtoB SaaSにおいては。
極端な話、過去のデータを参照する必要がなければ「履歴」を保持する必要はありません。
特定のレイヤーで部門を束ねて集計するといった機能がなければ「階層構造」もいらないかもしれません。
せっかく頑張って緻密にDB設計をしても、利用するシーンがなければただ悪戯にDBを複雑にするだけですし、かといって割り切りすぎると将来機能追加するときに困るかもしれません。
丁度いいラインを見極めるのが、「基幹システムではないtoB SaaS」における組織情報のDB設計の難しさだと思います。

何を軸に考えるか?

では、何を軸に考えるか?
最も大事なのは、当然かもしれませんが「ユースケース」だと思います。
私の場合は、まず組織情報が利用されるであろう機能をリストアップし、それぞれ自分達のサービスで本当に必要な機能かどうか?必要だとしたらどのレベルの機能が必要になるか?を軸に考えました。
組織情報が利用されるであろう機能については、前職の経験もあったのである程度想像がつきました。

組織情報を利用しうる機能

一般的な業務システムにおいて、組織情報を扱う機能としては以下のようなものが考えられます。

  • 検索・照会
  • セキュリティ・権限制御
  • ワークフロー
  • 業務トランザクション(経費申請データ etc)
  • 集計処理

弊社で開発中のサービスでは、検索・照会、セキュリティ・権限制御、集計処理といった機能で組織情報を活用する予定のため、それらをメインで考えました。

検索・照会

特定の組織情報に紐づいたトランザクションデータや、特定の所属の社員データを検索したり照会するといったケースです。
前述の営業第1課の経費申請データを検索したり照会するケースがまさにこれに当たります。
多くのサービスでは、最新の組織情報をもとに検索、照会できれば十分であることがほとんどだと思います。
弊社で開発中のサービスにおいても、それで十分と判断しました。
しかし、サービスによっては、過去のデータを照会したり、ある特定の時点の組織情報をもとにデータを検索するケースもあるかもしれません。
そういったケースを捨てきれない場合は、マスタで履歴を保持できるように設計する必要があります。

セキュリティ・権限制御

データの閲覧や更新、削除に制限をかける際に組織情報を利用されることがあります。
例えば社内のプロジェクトを管理する機能で、特定の部門に所属しているユーザーにのみ閲覧権限を付与したいといったケースや、特定の役職以上の人にのみデータの更新を許可したいといったケースなどが考えられます。
このケースにおいて厄介なのは、単に組織情報を利用できるようにしただけでは全ての要件を満たせるとは限らないということです。
例えば、部長職の社員に何らかの権限を付与していたとします。
要件がこれだけであればそこまで難しくなさそうですが、「〇〇部に関してはこの社員も部長と同等の権限を付与したい」といったケースが大企業においては必ずといっていいほど出てきます。
こういったイレギュラーな要件も、組織情報をかなり緻密に保持していれば満たせるかもしれません。
しかし、作り込みのシステムではなく広くtoB向けにサービスを展開するのであれば、全てを網羅することは相当な難易度となります。
したがって、組織情報と権限制御を密結合させる場合はかなり注意が必要です。
弊社で開発中のサービスでは、組織情報と権限制御を直接的に連携させない方向で考えています。
権限制御用のグループは組織情報とは切り離して作成できるようにし、必要であれば組織情報をベースにグループを作成できるようにする、といった形で顧客の要件に柔軟に対応できるようにする予定です。

集計処理

特定の組織情報(部門、事業所、役職など)の単位で何らかの集計結果を出したいといったケースがあると思います。
主に経営層向けのレポート機能などではほぼ必須の要件となるのではないでしょうか。
この集計処理を考える上では、とりわけ階層構造と向き合う必要があります。
階層構造を持たないということは、仮に組織情報を持っていたとしても最小単位の組織でしか集計できないということです。
例えば「営業本部 > 東日本営業 > 営業第1課」という3階層の組織があった場合を考えます。
システム上で階層構造を保持していない場合、「営業第1課」という単位でしか集計できません。
「東日本営業」や「営業本部」という単位で集計するにはどうしても階層構造を保持しておく必要があります。
弊社で開発中のサービスでは、経営層向けのレポート機能が重要なため、組織単位での集計処理の重要度も必然的に高くなります。
そのため、階層構造から逃げずに向き合うことにしました。
階層構造をRDBで表現するためのモデルとしてはいくつかモデルがあると前述しましたが、私は「隣接リストモデル」を採用する方向で考えています。
採用した理由を簡単にまとめると以下のようになります。

  • アンチパターンとされているが、DBとして利用しているPostgreSQLでは再帰構文をクエリで利用できるため、問題をクリアできる。
  • 基幹システムから組織情報を連携することを考えると、連携用のCSVファイルを作成しやすく、連携処理もシンプルになる点が良い。
  • データ構造がシンプルゆえデータの紐づきがわかりやすく、組織情報絡みのトラブルが起きた時に比較的原因を追いやすい。

各モデルの長所短所については様々なブログ記事などでも説明されているので、気になる方は調べてみてください。
弊社で開発中のサービスでは階層構造が必要と判断してDB設計に組み込むことにしましたが、組織単位の集計処理機能や、組織図そのものを表示する機能などを必要としない場合は不要になると思います。

集計処理においては過去の集計結果をどう扱うかについても考える必要があります。
過去の集計結果をその時点の組織情報に紐づけた状態で照会できるようにするためには、マスタの履歴を保持しておく必要があります。
しかし、マスタのメンテナンスコストとそれに伴うアプリ側の実装コストなどを考えると、履歴は設計から切りたいというのが私の考えの中にはありました。
最終的には履歴は持たせずとも過去のデータを参照できる形を取ることにしましたが、その選択については組織情報のメンテナンスコストとトランザクションデータとの整合性が関わってくるので、それらに触れながら説明したいと思います。

組織情報のメンテナンスとトランザクションデータとの整合性

基幹システムではないSaaSで組織情報を扱う場合、マスタデータのメンテナンス機能をSaaS内に持つのは現実的ではありません。
多くの企業では、基幹システムに入っているマスタを正としてメンテナンスしているため、顧客にマスタのダブルメンテを強いることになるためです。
したがって、基幹システムではないSaaSの組織情報は、基幹システムから連携する前提で考えるべきだと思います。
弊社で開発中のサービスでは、CSVファイルによる取り込み処理を考えていますが、連携方法として2つの選択肢がありました。
1つはデータの全洗い替え(完全なDELETE & INSERT)、もう1つが基幹システムでの変更に合わせてデータの登録、更新、削除を適宜行うという正攻法です。
前者には連携処理がシンプルになるというメリットがありますが、集計結果などのトランザクションデータにマスタのIDを持たせた場合に整合性が取れなくなるというデメリットがあります。
逆に後者には、連携処理が複雑になるというデメリットがありますが、トランザクションデータとの整合性は取れるというメリットがあります。
弊社のサービスでは前者を取ることにしました。
大企業の組織構造はときに組織改変によってガラリと変わることがあります。
また、それに伴ってマスタのコード体系が一新されることもあります。
そういった組織情報の変更をこちらのサービスに連携する際に、マスタとトランザクションデータの整合性をとりながら綺麗に同期させるのは簡単ではありません。
仮に連携処理のプログラムに全く問題がなかったとしても、基幹システムから出力されたCSVファイルが間違っていたため連携処理が失敗してしまうというケースは起き得ます。
そういった将来起こりそうなトラブル対応のコストを無くすためにも、連携処理は可能な限りシンプルにしたいと考えました。
では、トランザクションデータとの整合性や過去データの維持はどうするのか。
これは割り切って非正規化することにしました。
集計結果テーブルにはマスタのIDを持たせるのではなく、「コード」と「名称」を持たせることで、仮にマスタデータが無くなったとしても過去の結果を照会できるようにします。
こうすることによって仮にマスタの全洗い替えが失敗したとしても、既存の集計結果テーブルには影響が出ないため、何度でも気にせずリトライできます。
将来的には顧客の手で連携処理を回してもらうことも可能だと考えています。 ここが組織情報を扱う上で一番割り切ったところで、基幹システムと違って必要な情報量が少ないため取れる選択肢だと思います。
前職で携わっていた大企業向けのERPパッケージソフトでは絶対に取れない選択肢でした。
弊社のサービスにおいても組織情報は重要ですが、それがメインではありません。
組織情報のメンテナンスにコストをかけた結果、メインとなる機能の安定性を欠くとなってしまっては本末転倒です。
不要なものを削ぎ落としつつ、自分達のユースケースを満たせる丁度いいラインを考えた結果、この選択に至りました。

最後に

長々と綴ってしまいましたが、ここには記載していない部分でも悩んだ部分は色々ありました。
ただ、多くのtoB SaaSでも直面する悩みどころについてはある程度網羅しているかなと思います。
toB SaaSの組織情報の設計に携わっていらっしゃる方の目に触れ、少しでも参考になれば幸いです。

Page top