6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「現実的な分散」の第一歩 ― サービスベースアーキテクチャ入門

6
Posted at

はじめに

モノリスの限界は感じている。デプロイが怖い、変更の影響範囲が読めない、チームが大きくなるほど足を引っ張り合う。

「マイクロサービスにすれば解決するのでは?」と調べてみるも、分散トランザクション、サービスメッシュ、運用監視の複雑さに圧倒される――そんな経験はありませんか?

自分もまさにそうでした。正直なところ、「モノリスか、マイクロサービスか」という二択しかないと思っていました。

でも実は、この2つの間に「ちょうどいい選択肢」が存在します。
それがサービスベースアーキテクチャです。
この記事では、その構造と強み、そしてSOAの失敗から学ぶべき教訓まで一通り見ていきます。

TL;DR

  • サービスベースアーキテクチャは、ドメイン単位で粗く分割した4〜12個程度のサービスで構成される分散アーキテクチャ
  • モノリスのACIDトランザクションを維持しつつ、デプロイの独立性とドメイン分離を得られる
  • SOAの失敗が示すように、再利用は結合を通じて実現される。共有よりドメイン分離を優先する方が変更に強い
  • サービスベースから始めて、本当に必要な部分だけマイクロサービスに進化させるのが現実的な戦略

サービスベースアーキテクチャの全体像

では、具体的にどんな構造なのか。まずは全体像を掴みましょう。

基本構造: 粗粒度のドメインサービス

サービスベースアーキテクチャは、以下の3つの要素で構成されます。

┌────────────────────────────────────────────────────┐
│                      UI                            │
└──────┬─────────┬────────────┬──────────┬───────────┘
       │ REST    │ REST       │ REST     │ REST
       ▼         ▼            ▼          ▼
  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
  │ 注文     │ │ 配送    │  │ 顧客    │ │ 在庫     │
  │ サービス  │ │ サービス│  │ サービス │ │ サービス  │
  └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
       │           │           │           │
       ▼           ▼           ▼           ▼
  ┌──────────────────────────────────────────────┐
  │              共有データベース                   │
  └──────────────────────────────────────────────┘

  ドメインサービス: 4〜12個程度

ポイントは、業務領域(ドメイン)ごとにサービスを分割している点です。
「注文処理」「配送」「顧客管理」「在庫管理」など、業務の単位でサービスを切り出します。
これらをドメインサービスと呼びます。

マイクロサービスとの粒度の違いは明確です。
マイクロサービスが「1つの機能 = 1サービス」なのに対し、サービスベースは「1つのドメイン = 1サービス」です。
例えば、注文の作成・決済処理・在庫更新がそれぞれ別サービスになるのがマイクロサービスの粒度です。
注文に関わる処理は全て「注文サービス」の中に収まります。

厳密には、マイクロサービスの分割粒度は「1機能 = 1サービス」と一律ではなく、DDD(ドメイン駆動設計)の境界づけられたコンテキスト(bounded context)を基準とすることが多いです。ここではサービスベースとの対比を分かりやすくするため、典型的なケースとして簡略化しています。

各ドメインサービスの内部構造にも触れておきましょう。
一般的には、APIファサード層(UIからのリクエストを受け付ける窓口)、ビジネスロジック層、永続化層(DBアクセス)という3層構造で設計します。
あるいは、サービス内部をさらにサブドメインに分割する方法もあります。

アクセス方式とデプロイモデル

UIからドメインサービスへのアクセスは、RESTが一般的です。メッセージング、RPC(gRPCなど)、APIゲートウェイ経由なども選択肢に入ります。

デプロイモデルで注目したいのは、導入コストの低さです。
コンテナ化してKubernetesで動かすこともできますが、従来のモノリスと同じデプロイ方式でも問題なく動作します。
「まずは既存のデプロイパイプラインのまま分散化を始められる」というのは、現場にとって大きな安心材料ではないでしょうか。

各ドメインサービスは基本的に単一インスタンスで動かします。
スケーラビリティが必要な部分だけ複数インスタンスにすればよいので、インフラコストも抑えやすい構造です。

なぜサービスベースは「現実的」なのか

ここまでで構造の全体像を見てきました。では、なぜこのアーキテクチャが多くの現場で「ちょうどいい」と言われるのか、その理由を掘り下げていきます。

ACIDトランザクションを維持できる

自分としては、これがサービスベースの最大の強みだと思っています。

ACIDトランザクションとは、「全部成功するか、全部取り消すかを保証するデータベース操作」のことです。
モノリスでは当たり前のように使えるこの仕組みが、マイクロサービスでは途端に難しくなります。

ECサイトの注文処理を例に考えてみましょう。

サービスベースの場合、注文の作成・決済・在庫更新が全て1つのサービス内にあるため、通常のDBロールバックで整合性を保てます。

一方マイクロサービスの場合、サービスが分かれているため、途中で失敗するとデータ不整合が発生します。
これを解消するには、補償トランザクション(後から取り消し処理を別途実行する仕組み)が必要になります。

具体的な処理の流れを図で比較してみましょう。まずはサービスベースの場合です。

次に、マイクロサービスの場合を見てみましょう。

この差は開発・運用コストに直結します。
補償トランザクションの設計・実装・テストは想像以上に大変で、「決済は失敗したけど取り消し処理も失敗した」というケースまで考慮する必要があります。

上の図では説明を簡略化するため、決済処理をDB操作として描いていますが、実際の決済処理は外部の決済ゲートウェイへの呼び出しを含むことが一般的です。外部サービス呼び出しはACIDトランザクションの範囲外になるため、サービスベースであっても外部連携部分には別途エラーハンドリングの設計が必要です。

デプロイの独立性と変更容易性

ドメインサービスは独立してデプロイできます。「注文サービス」に変更を加えても、「配送サービス」や「顧客サービス」に影響を与えません。

これによって得られる恩恵は4つあります。

  • アジリティ(変更の速さ):ドメイン内で閉じた変更なら、他チームとの調整なしにリリースできる
  • テスタビリティ(テストのしやすさ):テスト範囲がドメイン内に限定される
  • デプロイ容易性:影響範囲が明確なので、デプロイのリスクが低い
  • 耐障害性:ドメインサービス同士は独立して動作するため、1つのサービスが落ちても他のサービスには影響しにくい

この4つは、ビジネスのスピード感に直結する特性です。「新機能を素早く届けたい」「バグを早く直したい」という現場の要求にしっかり応えてくれます。

粗粒度のトレードオフ

とはいえ、良いことばかりではありません。

粗粒度のドメインサービスにはデメリットもあります。
例えば、「注文サービス」の決済ロジックだけを修正しても、注文サービス全体のテストとデプロイが発生します。
マイクロサービスなら、PaymentServiceだけをテスト・デプロイすれば済みます。

しかし、多くの現場ではこの粗さが「ちょうどいい」ことが多いのも事実です。
マイクロサービスの細かさが本当に必要かどうかは、変更の頻度やチームの規模を見ながら慎重に見極めたいところです。

トポロジの柔軟性

サービスベースの「基本形」は共有DBに複数のドメインサービスという構成でした。
しかし、スケーラビリティや耐障害性の要件が高まると、基本形だけでは対応しきれない場面も出てきます。
サービスベースはこうした要件に応じてトポロジを柔軟に変形できるのも大きな特徴です。

UIの分離バリエーション

基本形では単一のモノリスUIですが、ドメインごとにUIを分離することもできます。

【基本形】                      【UI分離パターン】

┌──────────┐              ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 単一 UI   │              │ 顧客向け  │ │ 倉庫作業  │ │ サポート   │
│          │              │   UI     │ │ 者向けUI  │ │ 向けUI    │
└──────────┘              └────┬─────┘ └────┬─────┘ └────┬─────┘
      │                        │            │            │
      ▼                        ▼            ▼            ▼
┌──────────┐              ┌─────────┐ ┌─────────┐ ┌─────────┐
│全サービス  │              │注文/在庫 │ │ 梱包     │ │ 顧客    │
│          │              │サービス  │ │サービス   │ │サービス  │
└──────────┘              └─────────┘ └─────────┘ └─────────┘

UIを分離すると、スケーラビリティ・耐障害性・アジリティがそれぞれ向上します。顧客向けUIに障害が発生しても、倉庫作業者向けUIは影響を受けません。

APIゲートウェイの活用

UIとドメインサービスの間にAPIゲートウェイ(リクエストを受け付けて適切なサービスに振り分ける中間レイヤー)を配置するパターンもあります。

外部へのAPI公開、横断的な関心事(セキュリティ、メトリクス収集、監査ログ)の一元管理、複数インスタンスへのロードバランシングが必要な場合に有効です。

データベーストポロジの選択肢

データベースの構成も段階的に選べます。

【段階1: 共有モノリスDB】     【段階2: 論理分割】      【段階3: 物理分割】

┌──────────────────┐     ┌──────────────────┐      ┌───────┐ ┌────────┐
│ 全テーブルが       │     │ 注文 │ 顧客       │      │ 注文DB │ │ 顧客DB  │
│ 1つのDBに同居      │     │-----│------     │      └────────┘ └────────┘
│                  │     │ 配送 │ 共通       │      ┌────────┐ ┌────────┐
└──────────────────┘     └──────────────────┘     │ 配送DB  │ │ 在庫DB │
                                                  └────────┘ └────────┘

基本形の共有モノリスDBは構成がシンプルですが、スキーマ変更(テーブル定義の変更)が全サービスに波及するリスクがあります。
特に共有モノリスDBを採用する場合は、DB構成だけでなく、アプリケーション側でエンティティオブジェクトをどう管理するかも重要になります。

共有DBを使う場合、エンティティオブジェクト(DBテーブルに対応するプログラム上のクラスやオブジェクト)を1つの共有ライブラリにまとめるのはアンチパターンです。テーブル変更のたびに全サービスの再デプロイが必要になります。

対策として、共有ライブラリをドメインごとに論理分割する方法があります。
「注文」「顧客」「配送」「在庫」「共通」とライブラリを分け、各サービスは必要なライブラリだけを参照します。
こうすれば、「注文」テーブルの変更は「注文」ライブラリを使うサービスにしか影響しません。

ただし「共通」テーブル(複数サービスが参照するマスターデータなど)の変更は全サービスに影響する可能性があります。
対策として、共通テーブルのエンティティオブジェクトをバージョン管理システムでロックし、DB専任チームだけが変更できるようにする運用が推奨されています。

サービスベースの運用上の注意点

ここまでサービスベースの強みを見てきました。しかし、実際に運用する上で気をつけたいポイントがいくつかあります。

サービス間通信は最小限にする

マイクロサービスではサービス間通信が日常的ですが、サービスベースでは逆です。ドメインサービス間の直接通信はできるだけ避ける方が望ましいとされています。

なぜでしょうか。サービス間通信が増えると、サービス同士が密結合になり、独立してデプロイできるというメリットが薄れてしまうからです。
もしサービス間通信が多くなっているなら、それはドメイン分割が適切でないサインか、そもそもこのアーキテクチャスタイルが合っていない可能性があります。

では、複数のサービスが絡む処理はどうするのか。
オーケストレーション(処理の順序制御)はUI層またはAPIゲートウェイ層で行います。
データの共有はサービス間通信ではなく、共有データベースを通じて行う方が望ましいとされています。

サービス数の上限を意識する

ドメインサービスの実用上の上限は約12個です。

それ以上に増やすと、テスト範囲の管理、デプロイの調整、監視の複雑化、DBコネクション数の管理などで問題が発生しやすくなります。
「もっと細かく分割したい」と感じたら、それはマイクロサービスへの移行を検討するタイミングかもしれません。

チーム編成はドメインに合わせる

サービスベースはドメインで分割されたアーキテクチャです。
そのため、チーム編成もドメインごとのクロスファンクショナルチーム(フロントエンド・バックエンド・DBの担当者が1つのチームに集まる形態)が最適です。

技術レイヤーごとのチーム編成(フロントエンドチーム、バックエンドチームなど)は合いません。
ドメインの変更が複数チームにまたがり、チーム間の調整コストが増大するためです。

サービスベースアーキテクチャは、ストリーム型チーム(特定のビジネス領域の価値提供を一気通貫で担うチーム)との相性も良いとされています。ただし、ストリームがドメイン境界をまたぐ場合は境界の再設計が必要です。

SOA(サービス指向アーキテクチャ)という「やりすぎた分散」の教訓

ここまで、サービスベースが「ちょうどいい分散」であることを見てきました。
では逆に「やりすぎた分散」はどうなるのか。
2000年代にSOAが経験した失敗は、分散アーキテクチャで踏みがちな落とし穴そのものです。その教訓を振り返りましょう。

SOA(サービス指向アーキテクチャ)が生まれた背景

1990年代後半から2000年代初頭にかけて、企業の急成長や合併が相次ぎ、ITシステムの複雑化が加速していました。
当時はOSもDBも高額なライセンス制だったため、リソースを「再利用」して効率化することが至上命題でした。

「重複を排除し、再利用を最大化する」という哲学から生まれたのが、オーケストレーション駆動SOAです。
これは中央のエンジンが全てのサービス呼び出しを制御する分散アーキテクチャです。

サービスタクソノミーという階層構造

SOAの大きな特徴は、サービスを厳格に分類するタクソノミー(階層的な分類体系)を持つ点です。

┌───────────────────────────────────────────────────────┐
│  ビジネスサービス                                       │
│  (業務プロセスの入口。コードなし、入出力定義のみ)            │
│   例: ExecuteTrade, PlaceOrder                        │
├───────────────────────────────────────────────────────┤
│  エンタープライズサービス                                 │
│  (共有される細粒度の実装。再利用のビルディングブロック)        │
│   例: CreateCustomer, CalculateQuote                  │
├───────────────────────────────────────────────────────┤
│  アプリケーションサービス                                 │
│  (再利用不要な一回限りのサービス)                          │
│   例: 特定アプリ向けジオロケーション機能                    │
├───────────────────────────────────────────────────────┤
│  インフラストラクチャサービス                              │
│  (監視・ログ・認証などの運用関連)                          │
└───────────────────────────────────────────────────────┘

この設計の理想は、細粒度のエンタープライズサービスを「完璧な粒度」で作り、再利用可能なビルディングブロックとして蓄積していくことでした。

しかし現実には、「完璧な粒度」は幻想でした。ビジネス要件は常に変化し、ソフトウェアのコンポーネントは建築資材のように何十年も安定しているわけではありません。

オーケストレーションエンジンという単一障害点

SOAでは全てのリクエストがESB(Enterprise Service Bus、サービス間の通信を仲介する中央のハブ)を経由します。

ESBはトランザクション制御、メッセージ変換、ワークフロー定義を一手に担います。
全ての処理がこの1点を通るため、ESBに障害が起きればシステム全体が止まります。

さらに厄介なのは組織面での影響です。
ESBチームがシステム全体の「関所」になるため、組織内で政治的な力を持ち、官僚的なボトルネックになっていきました。
何か変更を加えたければ、まずESBチームのチケットキューに並ぶ必要がある、という状況です。
これはコンウェイの法則(システムの構造は組織のコミュニケーション構造を反映する)の典型的な実例です。

再利用を追求しすぎた代償

SOAの「再利用」の考え方がどう裏目に出たか、具体例で見てみましょう。

ある保険会社に自動車保険、障害保険、生命保険など6つの部門があり、それぞれが独自に「顧客」を管理していました。
SOAの発想では、これを1つのCustomerサービスに統合して再利用するのが正しい判断です。

一見合理的ですが、致命的な問題が発生しました。

  • Customerサービスへのどんな変更も、6部門全てに波及する(巨大な結合の発生)
  • 自動車保険に必要な「運転免許情報」が、障害保険など無関係な部門にまで押し付けられる(不要な複雑性の増加)

ここから見える本質的なトレードオフは、「再利用は結合を通じて実現される」ということです。再利用すればするほど、サービス同士は密に結合していきます。

DDDが「包括的な再利用を避けよ」と主張する背景には、まさにこのSOAでの苦い経験があります。各ドメインが独自の「顧客」概念を持つ方が、変更の影響を局所化できるという教訓です。

技術分割の限界

SOAは極端な技術分割アーキテクチャでもありました。
「住所行を1つ追加する」というシンプルなドメイン変更でも、ビジネスサービス層・エンタープライズサービス層・アプリケーションサービス層と、複数層にまたがる変更が必要でした。

その結果、デプロイ容易性やテスタビリティは著しく低いものでした。
結局、多くの企業はSOAをインテグレーション(異なるシステム間の連携)用途に縮小するか、マイクロサービスなどのドメイン分割型アーキテクチャに移行しました。

SOAから学ぶべきこと

SOAの歴史から得られる教訓を整理します。

教訓 内容
再利用は無条件に善ではない 再利用 = 結合。共有するほど変更の影響範囲が広がる
中央集権は変更のボトルネック 全てを1点で制御すると、そこが組織的にも技術的にもボトルネックになる
極端な技術分割はドメイン変更コストを爆発させる ドメインの変更が多数の技術レイヤーに波及する

ただし、ESBの統合ハブとしての機能自体が悪いわけではありません。レガシーシステムとの連携やシステム間統合など、限定的な用途では現在も有効に活用されています。

サービスベースから始める段階的戦略

サービスベースの強みとSOAの失敗の両方を踏まえた上で、実際のプロジェクトでどう活用するかを考えます。

サービスベースを出発点にする

サービスベースは、他の分散アーキテクチャへの ステッピングストーン(踏み台) として位置づけられています。

この戦略が有効な理由は、DDDとの親和性の高さにもあります。
粗粒度のドメインサービスはDDDの「境界づけられたコンテキスト」(ある概念が一貫した意味を持つ範囲)に自然に対応します。
ドメインの変更がサービス内に閉じるため、変更容易性が高く保たれます。

「本当に必要な部分だけ」マイクロサービスに進化させる

Going Green(電子機器リサイクル会社)の例が分かりやすいので紹介します。

┌─────────────────────────────────────────────────────────────┐
│                     Going Greenの戦略                        │
│                                                             │
│  ┌─────────────┐  ┌─────────────┐                           │
│  │ Recycling   │  │ Accounting  │   変更頻度: 低              │
│  │ サービス     │  │ サービス      │   → ドメインサービスのまま   │
│  └─────────────┘  └─────────────┘                           │
│                                                             │
│  ┌──────────────────────────────────────────┐               │
│  │  Assessment(査定)                       │  変更頻度: 高   │
│  │  ┌────────┐ ┌────────┐ ┌────────┐        │               │
│  │  │ iPhone │ │ Galaxy │ │ iPad   │  ...   │               │
│  │  │ 査定    │ │ 査定   │ │ 査定    │        │               │
│  │  └────────┘ └────────┘ └────────┘        │               │
│  │  → デバイス種類ごとにマイクロサービス化        │               │
│  └──────────────────────────────────────────┘               │
└─────────────────────────────────────────────────────────────┘

RecyclingサービスやAccountingサービスは変更頻度が低く、粗粒度で十分です。ドメインサービスのまま据え置きます。

一方、Assessmentサービスは新製品が出るたびに変更が発生し、高いアジリティが求められます。
この部分だけ、デバイスの種類ごとにマイクロサービスとして分解するのです。

もしGoing Greenがサービスベースを経由せず、いきなり全てをマイクロサービスにしていたらどうなるでしょうか。
RecyclingもAccountingも不必要に細かく分割され、複雑性とコストが無駄に膨らんでいたはずです。

まとめ

この記事で見てきた内容を振り返ります。

サービスベースアーキテクチャは、モノリスの安心感と分散の柔軟性を両立する、多くの現場にとって最初の一歩になるアーキテクチャです。

SOAの歴史は、分散アーキテクチャを設計する上で今なお有効な教訓を与えてくれます。
「再利用は結合を通じて実現される」「中央集権は変更のボトルネックになる」という事実は、どんなアーキテクチャを選ぶ場合にも忘れてはならないポイントでしょう。

そして何より大切なのは、「全てをマイクロサービスにする必要はない」ということです。

サービスベースから始めて、Going Greenの例のように運用しながら「本当に細かい粒度が必要な部分」を見極め、その部分だけをマイクロサービスに進化させる。
この段階的なアプローチこそが、最も現実的な分散化の戦略ではないでしょうか。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?