マイクロサービスとは、1つのシステムを「独立してデプロイできる小さなサービス」の集まりとして作る、アーキテクチャ(設計の全体像)の考え方です。それぞれのサービスは自分のデータを持ち、APIやメッセージで連携します。「注文」「在庫」「会員」のように業務の単位で分けることで、サービスごとに別々のチームが、別々のタイミングでリリースできるようになります。

一方で、マイクロサービスは「分けるほど良い」ものではありません。分散による複雑さ、データの整合性、運用と監視のコストという“引き換えにするもの”があり、チームの規模や組織の形と合っているときに力を発揮します。この記事では、定義とモノリスとの比較から始め、採用の判断表、モジュラーモノリスという中間の選択肢、モノリスから少しずつ切り出す方法、通信とデータ管理(Saga)、必要な基盤までを、特定の製品に寄らない中立な立場で整理します。Sagaの補償処理と、同期呼び出しの可用性の計算は、実際にコードを動かした結果を載せています。

マイクロサービスの早見表

項目 要点
定義 独立してデプロイできる小さなサービスの集まりとしてシステムを作る設計
分け方の基本 技術の層(画面・DB)ではなく、業務の単位(注文・在庫・会員など)で分ける
データ サービスごとに自分のDBを持つ。他サービスのテーブルを直接読まない
通信 同期(REST・gRPC)と非同期(メッセージ・イベント)を使い分ける
主なメリット 独立したリリース、部分ごとのスケール、障害の影響範囲を小さくできる、チームの自律
採用前に考えたいこと 分散による複雑さ、データの整合性、運用・監視のコスト、チームの規模
向いている状況 複数チームが同じシステムを並行して開発し、リリースの調整が負担になっている
中間の選択肢 モジュラーモノリス(1つのアプリの中で境界をはっきり分ける)
移行の定番 ストラングラーフィグ(新機能・一部機能から少しずつ切り出す)
必要な基盤 コンテナ、CI/CD、ログ・メトリクス・分散トレーシングによる監視

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「マイクロサービスとは|モノリスとの違い・採用判断・移行の進め方」

マイクロサービスの定義をもう少し詳しく

よく引用される定義は、ジェームズ・ルイス氏とマーティン・ファウラー氏が2014年に発表した記事「Microservices」です。そこでは、マイクロサービスを「1つのアプリケーションを小さなサービスの集まりとして作る方法で、各サービスは自分のプロセスで動き、軽い仕組み(多くはHTTPのAPI)で通信する。サービスは業務の能力を中心に作られ、自動化された仕組みで独立してデプロイできる」という趣旨で説明しています。

この定義から、マイクロサービスの特徴は次の5つにまとめられます。

特徴 意味 具体例
独立してデプロイできる 1つのサービスだけを更新・リリースできる 在庫サービスだけを今日リリースする
業務の能力で分ける 「注文」「在庫」など業務のまとまりで分ける 画面層・ロジック層・DB層で分けるのではない
データを自分で持つ 各サービスが自分のDBを管理する 注文サービスは在庫テーブルを直接読まない
軽い通信 APIやメッセージでやり取りする REST、gRPC、メッセージキュー
自動化が前提 テスト・デプロイ・監視を自動化する CI/CD、コンテナ、監視基盤

「マイクロ」は行数やサイズの小ささではなく、「1つのチームが責任を持って独立に変更できる大きさ」と考えるとわかりやすくなります。サービスの数が多いこと自体は目的ではありません。

分け方の考え方:境界づけられたコンテキスト

サービスの境界を決めるときによく使われるのが、ドメイン駆動設計(DDD)の「境界づけられたコンテキスト」という考え方です。同じ「商品」という言葉でも、在庫を管理する側では「数量と保管場所」、カタログを見せる側では「名前と説明と画像」を指します。言葉の意味が変わるところが、サービスを分ける候補になります。

また、「システムの構造は、それを作る組織のコミュニケーション構造に似る」というコンウェイの法則もよく参照されます。マイクロサービスは、チームの分け方とサービスの分け方をそろえると、力を発揮しやすくなります。

モノリスとマイクロサービスの比較

モノリスとは、システム全体を1つのアプリケーションとしてまとめて作り、まとめてデプロイする形です。多くのシステムはモノリスとして始まり、それで十分に成果を出しています。どちらが優れているかではなく、状況に合うかどうかで選びます。

観点 モノリス マイクロサービス
デプロイ 全体をまとめて1回 サービスごとに独立
開発の始めやすさ 始めやすい(1つのリポジトリ・1つのDB) 基盤づくりが先に必要
機能をまたぐ変更 1回のコミットで完結しやすい 複数サービス・複数チームの調整が必要
データの整合性 DBのトランザクションで守れる サービスをまたぐと結果整合性(Saga等)になる
スケール 全体をまとめて増やす 負荷の高いサービスだけ増やせる
障害の影響 1か所の不具合が全体に及ぶことがある 設計しだいで影響を一部に閉じ込められる
技術の選択 基本は1つの言語・フレームワーク サービスごとに選べる(ただし増やしすぎると保守が大変)
テスト 結合テストが1つのプロセスで済む サービス間の契約テストや結合環境が必要
監視・調査 1つのログを追えばよい 分散トレーシングで複数サービスをまたいで追う
チームの規模 少人数〜中規模で進めやすい 複数チームが並行開発するときに効果が出やすい

モノリスは「まとまっていることの強さ」、マイクロサービスは「分かれていることの強さ」を持っています。同じ問題でも、どちらの強さが今のチームに必要かで答えが変わります。

マイクロサービスのメリット

1. サービスごとに独立してリリースできる

最大のメリットは、他のチームのリリースを待たずに、自分のサービスだけを出せることです。全体の結合テストやリリース日の調整が減り、小さな変更をこまめに届けられるようになります。

2. 必要な部分だけスケールできる

アクセスが集中する「検索」だけを10台に増やし、利用の少ない「管理画面」は1台のまま、といった部分ごとのスケールができます。

3. 障害の影響範囲を小さくできる

タイムアウトやサーキットブレーカー(失敗が続く相手への呼び出しを一時的に止める仕組み)を組み合わせると、1つのサービスの不調が全体の停止につながりにくい設計にできます。

4. チームが自律して動ける

サービスとチームの担当が一致していると、設計・実装・運用を1つのチームで完結でき、判断が速くなります。

5. 技術を少しずつ新しくできる

サービス単位で言語やライブラリを更新できるため、システム全体を一度に作り直さずに、段階的に技術を新しくしていけます。

採用する前に考えたいこと

メリットを得るには、次の4つの点を引き受ける準備が必要です。事前に知っておけば、どれも設計と基盤で対処できます。

分散による複雑さ

サービス間の呼び出しはネットワークを通るため、遅延・タイムアウト・一時的な失敗が日常的に起こる前提で設計します。同期呼び出しを直列につなぐと、全体の可用性は掛け算で下がります。可用性99.9%のサービスを直列に呼んだ場合を計算した結果です。

for n in (1, 3, 5, 10):
    print(f'{n:>2}サービスを同期で直列に呼ぶ: {0.999**n*100:.2f}%  (月間停止目安 {(1-0.999**n)*30*24*60:.0f}分)')
 1サービスを同期で直列に呼ぶ: 99.90%  (月間停止目安 43分)
 3サービスを同期で直列に呼ぶ: 99.70%  (月間停止目安 129分)
 5サービスを同期で直列に呼ぶ: 99.50%  (月間停止目安 216分)
10サービスを同期で直列に呼ぶ: 99.00%  (月間停止目安 430分)

同期の呼び出しの連鎖を短く保つこと、非同期に置き換えられる箇所は置き換えることが、この点への基本的な対処です。

データの整合性

サービスごとにDBを分けると、複数サービスにまたがる処理を1つのDBトランザクションで守れなくなります。そのため「最終的に整合する(結果整合性)」設計と、失敗したときに取り消す仕組み(後述のSaga)が必要になります。

運用・監視のコスト

サービスの数だけ、デプロイの仕組み、ログ、メトリクス、アラート、ライブラリの更新が増えます。1つのリクエストが複数サービスをまたぐため、分散トレーシングがないと原因の調査に時間がかかります。基盤の整備と、それを担う人の時間を計画に含めておくと安心です。

チームの規模

サービスの数に対してチームが少ないと、1人が多くのサービスを見ることになり、独立性のメリットが出にくくなります。目安として「1つのチームが無理なく持てるサービスの数」から逆算して、分ける粒度を決めるとバランスが取れます。

採用の判断表

次の表で、今の状況に近い列を確認してみてください。「マイクロサービス寄り」が多く当てはまるほど、効果を得やすい状況です。

確認すること モノリス(またはモジュラーモノリス)寄り マイクロサービス寄り
開発チームの数 1〜2チーム 3チーム以上が同じシステムを並行開発
リリースの悩み 特にない 他チームとのリリース調整が大きな負担
業務の境界 まだ固まっていない(新規事業・検証段階) 業務の境界が安定していて、はっきりしている
負荷の偏り 全体がほぼ均一 一部の機能に負荷が大きく偏る
CI/CD・自動テスト これから整える すでに自動化されている
監視・運用の体制 担当者が少ない ログ・メトリクス・トレースを扱える体制がある
整合性の要件 強い整合性が必要な処理が多い 結果整合性で成り立つ処理が多い

迷ったときは、まずモジュラーモノリスで境界を整えておき、必要になった部分から切り出すのが、後戻りの少ない進め方です。ファウラー氏も「モノリスファースト(まずモノリスから始める)」という考え方を紹介しています。

モジュラーモノリスという選択肢

モジュラーモノリスとは、デプロイは1つのアプリケーションのまま、内部を業務ごとのモジュールにはっきり分けて作る形です。マイクロサービスの「境界を分ける」良さを取り入れつつ、分散の複雑さを後回しにできます。

観点 モノリス(境界なし) モジュラーモノリス マイクロサービス
デプロイ単位 1つ 1つ サービスごと
内部の境界 あいまいになりやすい モジュールごとに公開APIを決める ネットワークで分離
データ 共有 モジュールごとにスキーマやテーブルを分ける サービスごとのDB
整合性 DBトランザクション DBトランザクションが使える 結果整合性・Saga
将来の切り出し 手間が大きい 境界どおりに切り出しやすい —

モジュラーモノリスで意識したいルールは次の3つです。

  • 他のモジュールの内部クラスを直接呼ばず、公開したインターフェースだけを使う
  • 他のモジュールのテーブルを直接読まない(必要ならそのモジュールのAPIを通す)
  • 依存の向きを一方向に保つ(静的解析やアーキテクチャテストで自動チェックする)

このルールが守られていれば、あとで1つのモジュールをサービスとして切り出すとき、呼び出しをネットワーク越しに置き換えるだけに近づけられます。

モノリスから段階的に分ける方法(ストラングラーフィグ)

ストラングラーフィグ(Strangler Fig)とは、既存システムの前に振り分け役を置き、機能を1つずつ新しいサービスへ移していく移行の方法です。木に絡みついて少しずつ育つ植物になぞらえて、ファウラー氏が名付けました。一度に全部を作り直すのではなく、動いているシステムを止めずに少しずつ置き換えられるのが特長です。

利用者
  └─ 振り分け役(APIゲートウェイ / リバースプロキシ)
       ├─ /points/ で始まるリクエスト → 新しいポイントサービス(切り出し済み)
       └─ それ以外のリクエスト       → 既存のモノリス

進め方の5ステップ

ステップ やること ポイント
1. 境界を選ぶ 切り出す機能を1つ選ぶ 依存が少なく、変更が多い機能から始めると効果が見えやすい
2. 振り分け役を置く ゲートウェイやプロキシを既存システムの前に置く 最初は全部を既存システムへ流すだけにする
3. 新サービスを作る 選んだ機能を新サービスとして実装する データの持ち方もこの時点で決める
4. 少しずつ切り替える 一部のリクエストから新サービスへ流す 割合を段階的に増やし、すぐ戻せるようにしておく
5. 古い実装を片付ける 既存システム側の該当コードとデータを削除する 片付けまで終えて1サイクル完了

データの移行は、コードの移行より時間がかかることが多いので、「しばらくは両方に書く」「変更データを同期する」といった移行期間の設計も計画に入れておきます。

サービス間通信:同期と非同期

観点 同期(REST・gRPC) 非同期(メッセージ・イベント)
動き 呼び出して、返事を待つ メッセージを置いて、返事を待たずに進む
わかりやすさ 処理の流れが追いやすい 流れが分散するため、追跡の仕組みが必要
相手が止まっているとき 呼び出し側も失敗する メッセージがたまり、復旧後に処理される
向いている処理 すぐに結果が必要な参照・問い合わせ 注文の後の在庫引当・通知・集計など
注意点 タイムアウト、リトライ、サーキットブレーカー 重複して届く前提で冪等(何回処理しても同じ結果)に作る、順序の扱い

非同期のメッセージは「少なくとも1回届く」設計が一般的なので、受け取る側は同じメッセージを2回処理しても問題が起きないように作ります。たとえば、処理済みのメッセージIDを記録しておき、同じIDが来たら何もしない、という方法があります。

また、DBの更新とメッセージの送信を確実にそろえる方法として、トランザクショナルアウトボックス(同じDBトランザクションで「送るべきメッセージ」を表に書き、別の処理がそれを送信する)という定番のパターンがあります。

データ管理:サービスごとのDBとSaga

サービスごとのDB(Database per Service)

各サービスが自分のデータを持ち、他のサービスは必ずAPIやイベントを通してデータを得るのが基本です。これにより、あるサービスのテーブル変更が他のサービスを壊すことを防げます。物理的にDBサーバーを分けなくても、スキーマやアクセス権で分ける方法もあります。

他のサービスのデータを画面に並べたいときは、APIをまとめて呼ぶ(API合成)か、イベントを受け取って読み取り専用のコピーを自分の側に持つ、といった方法で対応します。

Sagaの概要

Sagaとは、複数のサービスにまたがる処理を「各サービスのローカルなトランザクションの連続」として進め、途中で失敗したら、それまでに成功したステップを「補償トランザクション(取り消しの処理)」で打ち消すパターンです。

方式 進め方 向いている場面
オーケストレーション 司令役(オーケストレーター)が各サービスに順番に指示する 手順が多く、流れを1か所で見たいとき
コレオグラフィー 各サービスがイベントを受けて、次のイベントを出す 参加するサービスが少なく、疎結合にしたいとき

オーケストレーション型のSagaを、1つのプロセスの中で3つのサービスを模擬して動かしてみます(考え方を示す最小のモデルで、実際にはサービス間はネットワークやメッセージでつながります)。

# オーケストレーション型Sagaの最小モデル(1プロセス内で3つの「サービス」を模擬)
class OrderService:
    def __init__(self): self.db = {}
    def create(self, oid): self.db[oid] = "PENDING"; print(f"[order]     {oid} を PENDING で作成")
    def approve(self, oid): self.db[oid] = "APPROVED"; print(f"[order]     {oid} を APPROVED に更新")
    def reject(self, oid): self.db[oid] = "REJECTED"; print(f"[order]     {oid} を REJECTED に更新(補償)")

class InventoryService:
    def __init__(self, stock): self.db = {"item-1": stock}
    def reserve(self, oid, qty):
        if self.db["item-1"] < qty: raise RuntimeError("在庫不足")
        self.db["item-1"] -= qty; print(f"[inventory] {oid} 用に {qty} 個を引当(残り {self.db['item-1']})")
    def release(self, oid, qty):
        self.db["item-1"] += qty; print(f"[inventory] {oid} の引当 {qty} 個を戻す(補償、残り {self.db['item-1']})")

class PointService:
    def __init__(self, balance): self.balance = balance; self.db = {}
    def use(self, oid, points):
        if points > self.balance: raise RuntimeError("ポイント残高不足")
        self.balance -= points; self.db[oid] = points
        print(f"[point]     {oid} で {points} ポイントを使用(残り {self.balance})")

def place_order(oid, qty, points, order, inv, pt):
    done = []  # 成功したステップの補償処理を積む
    try:
        order.create(oid);          done.append(lambda: order.reject(oid))
        inv.reserve(oid, qty);      done.append(lambda: inv.release(oid, qty))
        pt.use(oid, points)
        order.approve(oid)
        print(f"=> {oid}: 成功")
    except RuntimeError as e:
        print(f"!! {oid}: 失敗({e})→ 逆順に補償トランザクションを実行")
        for compensate in reversed(done): compensate()

order, inv, pt = OrderService(), InventoryService(stock=10), PointService(balance=5000)
place_order("A-001", 3, 3000, order, inv, pt)
print()
place_order("A-002", 2, 8000, order, inv, pt)
print()
print("最終状態:", order.db, inv.db, pt.db)

Python 3.11で実行した結果です。

[order]     A-001 を PENDING で作成
[inventory] A-001 用に 3 個を引当(残り 7)
[point]     A-001 で 3000 ポイントを使用(残り 2000)
[order]     A-001 を APPROVED に更新
=> A-001: 成功

[order]     A-002 を PENDING で作成
[inventory] A-002 用に 2 個を引当(残り 5)
!! A-002: 失敗(ポイント残高不足)→ 逆順に補償トランザクションを実行
[inventory] A-002 の引当 2 個を戻す(補償、残り 7)
[order]     A-002 を REJECTED に更新(補償)

最終状態: {'A-001': 'APPROVED', 'A-002': 'REJECTED'} {'item-1': 7} {'A-001': 3000}

2件目の注文では、ポイントの使用で失敗したため、在庫の引当を戻し、注文を「REJECTED」にする補償処理が逆順に実行され、在庫は7個に戻りました。ここからわかるポイントは次のとおりです。

  • Sagaは「ロールバック」ではなく「取り消しの処理を実行する」仕組みです。途中の状態(PENDINGや引当中)は、一時的に他から見えることがあります
  • 補償処理も失敗しうるので、実際のシステムではリトライと、状態の永続化(どこまで進んだかをDBに記録)が必要です
  • 画面には「受付中」「確定」のように途中の状態を前提にした表示を用意すると、利用者にもわかりやすくなります

必要な基盤

領域 役割 代表的な技術・考え方
コンテナ サービスを同じ形で動かし、配置する Dockerなどのコンテナ、Kubernetesなどのオーケストレーション
CI/CD サービスごとにテスト・ビルド・デプロイを自動化する パイプライン、段階的リリース(カナリア等)、すぐ戻せる仕組み
設定・秘密情報の管理 環境ごとの設定や認証情報を安全に配る 環境変数、シークレット管理
ログ 各サービスのログを1か所に集めて検索する 構造化ログ、ログ集約
メトリクス リクエスト数・エラー率・応答時間を見る ダッシュボード、アラート、SLO
分散トレーシング 1つのリクエストが通ったサービスを順に追う トレースIDの伝搬、OpenTelemetry
サービス間の通信制御 リトライ・タイムアウト・認証をそろえる APIゲートウェイ、サービスメッシュ
契約テスト サービス同士のAPIの約束が壊れていないか確かめる コンシューマー駆動の契約テスト

特に「分散トレーシング」は、マイクロサービスの調査のしやすさを大きく左右します。リクエストの入口でトレースIDを発行し、すべてのサービス呼び出しとログに引き継ぐことで、「どのサービスで時間がかかったか」を1本の線として見られるようになります。OpenTelemetryは、トレース・メトリクス・ログを収集するためのベンダー中立の標準仕様とツール群です。

基盤は最初から全部そろえる必要はありません。サービスが2〜3個の段階では、CI/CD、ログの集約、トレースIDの伝搬の3つから始め、数が増えるのに合わせて広げていくと無理がありません。

よくある失敗と、うまくいく進め方

よくある状況 起きること うまくいく進め方
技術の層(画面・API・DB)で分ける 1つの機能追加で全サービスの変更が必要になる 業務の単位で分ける
複数サービスで1つのDBを共有する テーブル変更の影響が全体に広がり、独立してリリースできない サービスごとにデータを持ち、APIやイベントで連携する
細かく分けすぎる 呼び出しが増え、1つの処理に多数のサービスが必要になる 一緒に変わるものは一緒に置く。迷ったら大きめに分ける
同期呼び出しを長くつなぐ 1つの遅延・停止が全体に伝わる 連鎖を短くし、非同期化・タイムアウト・サーキットブレーカーを使う
監視を後回しにする 障害の原因がどのサービスか特定しにくい 最初のサービスからログ集約とトレースIDを入れる
一度に全部を作り直す 移行期間が長くなり、既存機能の改善が止まる ストラングラーフィグで1機能ずつ移す

よくある質問

Q. マイクロサービスとSOA(サービス指向アーキテクチャ)は何が違いますか? A. どちらも「サービスに分ける」考え方ですが、SOAは企業内の複数システムを共通の連携基盤(ESBなど)でつなぐ文脈で語られることが多く、マイクロサービスは1つのシステムを、独立してデプロイできる小さなサービスに分け、軽い通信と自動化を前提にする点が特徴です。

Q. 小さなチームでもマイクロサービスにしたほうがよいですか? A. 1〜2チームで開発している場合は、モジュラーモノリスで境界を整えるほうが、少ない手間で多くのメリットを得られることが多いです。チームが増えてリリースの調整が負担になってきたら、境界に沿って切り出しを検討します。

Q. サービスの大きさはどれくらいが適切ですか? A. 行数や機能数の決まりはありません。「1つのチームが責任を持って、他チームと調整せずに変更・リリースできる大きさ」が目安です。業務の言葉の意味が変わるところ(境界づけられたコンテキスト)を分ける候補にします。

Q. サービスをまたぐトランザクションはどうすればよいですか? A. 2フェーズコミットのような分散トランザクションは可用性と性能の面で扱いが難しいため、Sagaで各サービスのローカルトランザクションをつなぎ、失敗時は補償処理で取り消すのが一般的です。強い整合性が必要な処理は、同じサービスの中にまとめる設計も検討します。

まとめ

  • マイクロサービスとは、独立してデプロイできる小さなサービスの集まりとしてシステムを作る設計です
  • 業務の単位で分け、サービスごとにデータを持つのが基本です
  • メリットは独立したリリース、部分ごとのスケール、障害の影響範囲の縮小、チームの自律です
  • 採用前に、分散の複雑さ・データの整合性・運用と監視のコスト・チームの規模を確認しましょう
  • 迷ったらモジュラーモノリスで境界を整え、ストラングラーフィグで必要な部分から切り出します
  • サービスをまたぐ処理はSaga(補償トランザクション)で、非同期の受信は冪等に作ります
  • 基盤はCI/CD・ログ集約・分散トレーシングから始め、規模に合わせて広げます

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「マイクロサービスとは|モノリスとの違い・採用判断・移行の進め方」

株式会社RJCでは、システムの規模やチームの形に合わせたアーキテクチャの選び方を、プロジェクトの中で仲間と議論しながら学んでいます。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?