弊社のBtoB顧客管理マイクロサービスの設計をする
はじめに
0UTL1ER株式会社では、法人向けのサービスをいくつか展開しているが、
請求書を発行するたびに億劫だったので、今一度ちゃんとした基幹システムを作ることにした。
いまはせいぜい10枚程度しか発行しないが、こういうものを早めに作っておかないと後々大変になるので、今のうちに作っておいた方がいいと判断した。
そして、私はありとあらゆる開発工程の中で、ドキュメントを作成するという作業が一番嫌いである。
社内の人間しか読まないものに体裁を気にしたくないからである。
ので、どうせならというわけで記事として出版し、備忘録としての意味も持たせている。
じゃあなんで顧客管理システムから作るのか。という話は後述する。
設計
マイクロサービスアーキテクチャを採用
請求書を発行する際に、請求対象の法人の商号と住所を入力する必要があるが、
このようなデータは間違いなく再利用性があるので、データベースを分離して持つことにする。
また、請求書では利用しないものの、後々別の処理で法人番号等も必要になりかねないので、
できるだけ汎用性が高いデータベースを設計することにする。
API仕様
最終的にこういうものが作りたい というのを明示化するためにここに具体的なレスポンス内容を乗っけておく。
一個一個描いてたらいつまで立っても終わらないので、主に利用するAPIを一個紹介する。
詳しくはprotoディレクトリに保存されているprotoファイルを見てもらいたい。
コードに関しては後の記事で紹介する。
{
"company": {
"id": "5a941ee5-beef-48aa-850d-287b35811ccc",
"trademark": "0UTL1ER",
"type": "kabu",
"position": "suffix",
"address": "東京都千代田区神田松永町13番地VORT秋葉原II",
"companyCode": "9010001257448",
"contact": {
"id": "75e5505f-2885-4313-a2fe-d5d7574ba4ae",
"email": "info@0utl1er.tech",
"phone": "03-1111-1112"
},
"staff": [
{
"id": "0e7f9602-eda4-4725-8570-b4a21286bb39",
"name": "黒羽 晟",
"role": "代表取締役",
"contact": {
"id": "29cfb9c6-aa57-4cda-8f71-f8249ccf1c9e",
"email": "joe@0utl1er.tech",
"phone": "090-1234-5678"
}
}
],
"createdAt": "2025-10-11T05:36:44.073679Z"
}
}
DB設計
筆者はドメイン言語が大好きなため、dbmlを使用。
成果物
Companyテーブル
その名通り、会社情報を保存するテーブルだ。
上から順に説明していこう
- id 言わずもがな主キー UUIDを採用している
- trademark 商標 商号ではなく商標としている。理由は後述
- type 法人形態 株式会社とか有限会社とかそういうの enum
- address 住所 これはシンプル
- company_code 法人番号 後々nullの企業とか登録する用件が発生した場合に備えて候補キー
- created_at タイムスタンプ これを作っておくと思わないところで救われる。
updated_atも作ってもいいと思うが、今はとりあえずこれだけにしておく。
なぜ商標と法人形態を分けるの?
例えば弊社は0UTL1ER株式会社 という商号を持っている。
しかしながら、0UTL1ER(株)
という書き方もできてしまうのだ。あと前株後株問題も地味に厄介なのだ。
例えば、先方からエクセルで法人と請求が横に繋がっているエクセルファイルが届いたとしよう
こんな感じだ。
会社 | 請求額 | 備考 |
0UTL1ER(株) | 2000円 | 特になし |
0UTL1ERの子会社だよ株式会社 | 10,000円 | 特になし |
定期的にメールで届くこれを、データベースの情報を元にマッピングしなければいけないといった用件が出た際に、この工夫は輝くのだ。
これであれば、(株)や株式会社を検索と置換で削除してしまって、
関数で特定のキーワードが最初にあるかどうかを判断すればいいのだ。
逆も然り。前と後どっちに来るかという情報と、会社形態さえ分けていれば表現手段が柔軟になるし、抽象化してデータをシンプルに統一することも可能だ。
もちろんエクセルファイルに最初から主キーが入っていればそんな面倒くさいことしなくていいのだが、
世の中こういう作業はザラにあるものである。
ちなみにenumはこんなかんじ
日本にこれ以外に現存の法人形態はそうそうないと思うが、念の為otherで対応できるようにしてある。
なんかの間違いでNGO法人や学校法人が登録されることになった際に備えるためである。
前・後はboolで処理してもいいが、
falseとtrueどっちがどっちだっけ?となりそうなのでenumにしている。
Enum type{
kabu [note: "株式会社"]
yugen [note: "有限会社"]
godo [note: "合同会社"]
goshi [note: "合資会社"]
gomei [note: "合名会社"]
other [note: "その他"]
}
Enum presuf {
prefix [note: "前株"]
suffix [note: "後株"]
}
Staffテーブル
請求書を送信する際に、発行対象の企業はすでに決まっているのに、
その企業の代表取締役の連絡先を調べるのは億劫だ。
請求書以外にも、例えばteamsやgoogle calendarなどから予定表を出力すると、
MTG先のメールアドレスが取得できるが、その情報からどこの法人とのMTGかどうか判断したりなど、
対応できる要件は格段に増える。
というわけで、CEOを含む従業員の基本情報と連絡先をこのテーブルに格納する。
Contactテーブル
電話番号とメアドを保存するシンプルなテーブル。
companyとstaff両方から依存されるように設計されており、
companyには、03-xxxx-xxxxのような代表番号が保存され、
staffでは090-xxxx-xxxxのような個人の電話番号やメアドが保存されることを想定している。
もちろん利用の想定なので、バリデーション等はしていない。
認証認可どうすんのよ
google cloud identity platform上にアカウントを格納して、
Istioを使ったサイドカーパターンや、認証認可を搭載したAPIゲートウェイと一緒にデプロイされることを想定している。シンプルなIP制限も場合によっては有りだろうと思っている。
認証認可はバックグラウンドによって要件が大幅に可変するので、このサービスからは切り離して再利用性を高めている。
おわりに
具体的なコーディングは、connect-goとprotovalidate sqlcによって実装されているが
それなりに盛り込まれていて、説明がまあまあ面倒くさいので、別の記事で紹介することにする。
Discussion