🐈

マイクロサービスでCRMを作ってみる 設計編

に公開
2025/09/14

マイクロサービスに関する記事はいっぱいあるものの実際にサービスを設計・実装まで行なっているサービスはとても珍しいと感じた(こなみ)

というわけで、僕の実務経験を元に、考えられる全ての技術を結集してそれらしいシステムを作ろうと思う。
かっこいいのを作ろう

あと最初に断っておきますが、モジュラーモノリスは導入しません。
この程度の内容であればサービスを分割するメリットがあるのか疑問を持たれるのは当然ですが、
僕ははっきり言っておんなじリポジトリに10kstepsもコードが入っていたら読みたくなくなります。

さらにいうのであれば、トランザクションなんて使わないので当然っちゃ当然です。

なんで作るの?

そもそもSalesforce一強の時代に新しいCRMなんて意味があるのか?
ごもっともだと思われます。
ただ、Kintoneもそうですが、ローコードのツケはアナリティクスを出したいなどの集計関連の要件が発生した時に発生すると思っています。データの正規化がローコードだと難しいからです。

Filemakerのようなローコードツールならリレーションを作成して正規化することもできますが、
今度はUIが柔軟ではありませんし、そもそもそんなDBの知識あるならスクラッチで書けと思います。
保守性もカスですし。

しかしながら、正規化していないということは、エクセルでやってた顧客管理シートをそのまま脳死でDBに突っ込めるので、そういう意味では合理的だったりします。

というわけで、そのメリットを享受しつつ、一般的な基幹システムのようにテーブルが正規化されてたらかっちぇえシステムが作れるんじゃないかなと思いました。

要件

  • CSVやEXCELで営業リストを一括インポートできるようにする。
  • 顧客情報のCRUD
  • 顧客の連絡先の情報のCRUD
  • 顧客の連絡先への連絡履歴のCRUD
  • ベンダーロックインしない 最悪オンプレで展開できるようにする 金ないから

全体図

サービス分割前のDBはこんな感じ。

たった9テーブルやんけw これ分割するのアホやろwww
大規模開発においては100テーブルなんて軽く超えるんやでwwww

おっしゃる通り。だが基本的に100テーブルを超えるというのは、大体数年でのアップデートの過程で、最適化のためのシャーディングや、要件の変更などを通じて肥大化していったからなのだ。
さらにいうなら、たかが9テーブルと思う勿れ。
例えば9テーブルそれぞれのCRUDだけを作ったとしよう。
何、難しいことじゃない。INSERT SELECT UPDATE DELETE
4つのクエリをそれぞれのテーブルで作るだけ。
単純計算で36個のクエリが発行される。つまり最低36個のエンドポイントが作成される。最低だ。
そこにさらにビジネスロジック上必要なクエリが発生したりする。そうなると、少なく見積もっても50はクエリが発生するだろう。100発生しても全然おかしくない。

そしてそして、アップデートの過程でリクエスト・レスポンス形式が変われば、後方互換性を維持するために新しいバージョンを作成するとなると、まあ...大変だ...

作るのは簡単だ。だが保守したくない。少なくともこいつは個人開発のプロジェクトだ。
このサービスに関するコードってどれだっけ?ということすらしたくない。
ので、コードはできるだけシンプルにする。ロジックが複雑でも、それはアーキテクチャで十分にカバーできるものだ。
そうすれば万が一保守のために人を雇うことがあったとしても、保守できる人材の仕入れが簡単になる。
それがミソだ。
余談だが、このER図は認証系は独立していることを前提としており、全体像の把握のためにすぎないため、リフレッシュトークンを管理するためのセッションだのそういう云々は記載していない。
そのため、認証系すら分割しないとなると、さらにロジックは複雑化する。

分け方

分けてもトランザクションが発生しない かつ3テーブル以内に収まるのが好ましい。
サービスはこんな感じだ。重要なのは、役割が明確なことだ。
〜するだけ 逆に言えば、他にやってることがないということが重要なのだ。
実はこのサービスではこういうこともやってるのでは? みたいに保守時に考えないようにする。

  • UI
    NextJSで作られた、BFFで実装されているリクエスト、レスポンスを視覚化するだけのサービス

  • BFF
    独立したマイクロサービスのレスポンスをフロントエンドの好ましい形式に変換するだけのサービス
    NestJSを使ってみる。

  • Keycloak
    認証してJWTを発行するだけのサービス(OSS)

以下は全部Go言語

  • 顧客リストインポート
    CSVとかでインポートして、他のサービスに投入するだけのサービス

  • 顧客サービス
    顧客情報を管理するだけのサービス こいつがマスタ
    あとEnvoyのカスタムチェックを提供する

  • 連絡先管理サービス
    顧客を管理する上で、具体的なスタッフなどとその連絡先を記録するだけのサービス

  • 架電管理サービス
    電話をかけた履歴を保存するだけのサービス

認証方法

広く使われている方法は、API Gatewayパターンや、BFFでサービスごとの認証認可を行うというものだ。
だが、これもプログラムを複雑化させるというのと、BFFとサービスは垂直分散の関係になる。
つまり、可用率がBFFに依存することになるので、BFFはほぼほぼ副作用がないプログラムでないといけない。ただ他のサービスからデータを取ってきて、それを整形するだけのプログラムであるべきだ。
それならば、プログラム自体に関数型プログラミングでいう副作用が発生しないため、
理論上テストコードで実行した時と、全く同じ挙動をするはずだ。
かと言って、各サービスに認証に関するミドルウェアを作るのも、これまた冗長だ。

となると、どうするか。
結論は、サービスメッシュパターンだ。
サイドカーパターンといった方がしっくりくるかもしれない。
つまり、マイクロサービスを展開する上でよく利用されるKubernetesなどで用いられるIstioなどのCNIでは、各サービスにサイドカーをつけることが可能だ。こいつは各サービスに適応されるプロキシだ。

具体的には、全てのマイクロサービスはEnvoyというプロキシを経由してアクセスできるようにされている。
このEnvoyで従来のプログラムではmiddlewareとして行っていたことを丸ごと投げる感じだ。
JWTの検証・ロギングとか諸々である。これでプログラム本体はビジネスロジックに集中できる。

こいつがJWTを検証して、検証結果をリクエストのヘッダーにそのままJSONを突っ込む。
有効なJWTが来ない場合、このリクエストを拒否する。

インフラに認証系が依存していないか?とも思うだろう。そう。これがサービスメッシュパターンのデメリットだ。しかしながら、認証系はセキュリティに関わる部分だ。
セキュアなコードは全てのエンジニアが書けるわけではないので、なるべく認証に関わるコードに触るべきではないのだ。

各サービスの説明記事は別で書く また会おう!

Discussion