テックカリキュラム

エンタープライズシステム統合設計と分散アーキテクチャ

エンタープライズシステム統合設計と分散アーキテクチャ

本章では、VB.NETで構築された業務システムを、単体のアプリケーションとしてではなく、 複数の業務システム、Web API、外部サービス、メッセージング基盤、クラウドサービスと連携する 「企業システム全体」の一部として設計する方法を学習します。

実際の企業システムでは、受注、在庫、請求、顧客管理、会計、認証、物流、決済などが すべて1つのアプリケーションに収まっているとは限りません。 複数のシステムがネットワーク越しに連携し、それぞれ独立したタイミングで処理を行います。

このような分散環境では、「メソッドを呼べば必ず成功する」という前提は成立しません。 通信は失敗し、相手システムは停止し、タイムアウトが発生し、 同じメッセージが複数回届き、処理順序が前後する可能性があります。

そのため、分散システムでは、API設計、非同期メッセージング、冪等性、 Outbox Pattern、Saga Pattern、Circuit Breaker、Retry、Timeout、 Correlation ID、分散トレーシングなど、単体アプリケーションとは異なる設計技術が必要になります。

本章のゴールは、VB.NETを単独の業務アプリケーションとして扱う段階から進み、 複数システムを安全に統合し、障害が発生してもシステム全体へ波及させない エンタープライズレベルの分散アーキテクチャを設計できるようになることです。


1. エンタープライズシステム統合とは

エンタープライズシステム統合とは、企業内外に存在する複数のシステムを接続し、 データや業務処理を連携させる設計です。

企業システムの例

受注管理システム
      ↓
在庫管理システム
      ↓
物流システム
      ↓
請求システム
      ↓
会計システム

      +

顧客管理システム
認証システム
決済サービス
外部API
データ分析基盤

これらのシステムは、必ずしも同じ言語、DB、OS、クラウド環境で動作しているとは限りません。

たとえば、受注管理がVB.NET、顧客管理がJava、WebサービスがC#、 分析基盤がPythonという構成も十分に考えられます。

そのため、システム統合では「VB.NET同士を接続する」という考え方ではなく、 HTTP、JSON、メッセージ、イベントなどの標準的な境界を利用することが重要です。


2. Point-to-Point連携の問題

システム数が少ない場合、それぞれを直接接続するPoint-to-Point方式でも連携できます。

受注 → 在庫

受注 → 請求

受注 → 顧客

在庫 → 物流

請求 → 会計

しかし、システム数が増えるにつれて接続関係が複雑になります。

Point-to-Pointで発生しやすい問題

  • 接続先ごとに異なる実装が必要になる
  • システム変更時の影響範囲が大きくなる
  • 障害発生箇所を追跡しにくい
  • 同じデータ変換処理が複数箇所に存在する
  • 認証方式がバラバラになる
  • システム間依存が強くなる

システム数が増える場合は、API Gateway、Message Broker、Event Busなどを利用して、 システム同士の直接依存を減らすことを検討します。


3. 同期通信と非同期通信

システム間通信には、大きく同期通信と非同期通信があります。

方式特徴代表例
同期通信相手の応答を待つREST API、HTTP
非同期通信送信後に相手の完了を待たないQueue、Message Broker、Event

同期通信

受注システム
    ↓ HTTP
在庫システム
    ↓ Response
受注システム

同期通信は結果をすぐ取得できるため分かりやすい反面、 相手システムが停止すると呼び出し元も影響を受けます。

非同期通信

受注システム
      ↓
Message Queue
      ↓
在庫システム

受注システムはメッセージを登録した時点で処理を進めることができます。 在庫システムが一時停止していても、メッセージをQueueに保持できます。

同期通信が向いている処理

  • 即座に結果が必要
  • 検索
  • マスタ参照
  • ログイン
  • 入力内容のリアルタイム検証

非同期通信が向いている処理

  • メール送信
  • 帳票生成
  • 外部システム通知
  • データ同期
  • 大量処理
  • 即時完了が不要な処理

4. REST API連携

現在のシステム統合では、HTTPを利用したREST APIが広く利用されています。 VB.NETからもHttpClientを利用してAPIを呼び出せます。

GETリクエスト

Public Class CustomerApiClient

    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    Public Async Function GetCustomerAsync(
        customerId As Integer) As Task(Of String)

        Dim response =
            Await _httpClient.GetAsync(
                "/api/customers/" & customerId)

        response.EnsureSuccessStatusCode()

        Return Await response.Content.ReadAsStringAsync()

    End Function

End Class

POSTリクエスト

Public Async Function CreateOrderAsync(
    request As CreateOrderRequest) As Task

    Dim json As String =
        Text.Json.JsonSerializer.Serialize(request)

    Using content As New StringContent(
        json,
        Text.Encoding.UTF8,
        "application/json")

        Dim response =
            Await _httpClient.PostAsync(
                "/api/orders",
                content)

        response.EnsureSuccessStatusCode()

    End Using

End Function

API連携で考慮すること

  • Timeout
  • Retry
  • 認証
  • HTTPステータスコード
  • APIバージョン
  • JSON互換性
  • レート制限
  • ログ
  • 冪等性

5. API境界とDTO

外部APIのレスポンスを、そのままDomain Entityとして使用するのは避けます。 外部システムの仕様変更が、自システムの業務ロジックへ直接影響するためです。

API DTO

Public Class CustomerApiResponse

    Public Property Id As Integer

    Public Property Name As String

    Public Property Rank As String

End Class

Domainモデル

Public Class Customer

    Public Property CustomerId As Integer

    Public Property CustomerName As String

    Public Property CustomerRank As CustomerRank

End Class

変換処理

Public Function Convert(
    response As CustomerApiResponse) As Customer

    Return New Customer With {
        .CustomerId = response.Id,
        .CustomerName = response.Name,
        .CustomerRank =
            ConvertRank(response.Rank)
    }

End Function

外部モデルと内部モデルの間に変換層を設けることで、 外部APIの変更を内部Domainへ直接持ち込まずに済みます。

このような境界は、Anti-Corruption Layerとして設計することもできます。


6. Message Queue

Message Queueは、送信側と受信側の間にメッセージを保存し、 非同期で処理する仕組みです。

Producer
   ↓
Queue
   ↓
Consumer

代表的な利用用途

  • メール送信
  • 帳票生成
  • 注文処理
  • 外部API連携
  • 画像処理
  • データ同期

キュー方式のメリット

  • システム間を疎結合にできる
  • 一時的な障害に耐えられる
  • 処理量の急増を吸収できる
  • Consumerを増やして処理能力を拡張できる

キュー利用時に考えること

  • メッセージの重複
  • 順序保証
  • 再送
  • 処理失敗
  • Dead Letter Queue
  • 冪等性

7. Publish / Subscribe

Publish / Subscribeでは、1つのイベントを複数のConsumerが受信できます。

OrderCreated
      ↓
  Event Bus
   ↓   ↓   ↓
在庫  請求  分析

受注システムは「注文が作成された」というイベントを発行するだけで、 在庫、請求、分析システムの存在を知る必要がありません。

イベント例

Public Class OrderCreatedEvent

    Public Property EventId As Guid

    Public Property OrderId As Integer

    Public Property CustomerId As Integer

    Public Property OccurredAt As DateTime

End Class

イベント設計のポイント

  • EventIdを持たせる
  • 発生日時を持たせる
  • 業務的な意味のある名前にする
  • Consumer固有の情報を入れすぎない
  • 将来のスキーマ変更を考える

8. At-Most-Once / At-Least-Onceと冪等性

分散システムでは、「メッセージが絶対に1回だけ処理される」と単純に考えてはいけません。

方式特徴
At-Most-Once最大1回。失われる可能性がある
At-Least-Once最低1回。重複する可能性がある

実務では、At-Least-Once配信とConsumer側の冪等性を組み合わせる設計が重要になります。

処理済みイベント管理

ProcessedMessages

MessageId
ProcessedAt

Consumer側

Public Sub Handle(message As OrderCreatedEvent)

    If _processedRepository.Exists(message.EventId) Then
        Return
    End If

    _orderService.Process(message)

    _processedRepository.Save(
        message.EventId,
        DateTime.Now)

End Sub

同じEventIdのメッセージが再送されても、 既に処理済みであれば業務処理を再実行しません。


9. Outbox Pattern

分散システムで非常に重要なのがOutbox Patternです。

たとえば、注文をDBへ保存した後にMessage Brokerへイベントを送信するとします。

危険な処理

SaveOrder(order)

PublishOrderCreated(order)

SaveOrderが成功した後、PublishOrderCreatedが失敗すると、 DBには注文が存在するのにイベントが送信されていない状態になります。

逆順でも問題がある

PublishOrderCreated(order)

SaveOrder(order)

今度はイベント送信後にDB登録が失敗すると、 存在しない注文のイベントが他システムへ送信される可能性があります。

Outbox Pattern

BEGIN TRANSACTION

Orders INSERT

OutboxMessages INSERT

COMMIT

業務データと送信予定イベントを、同じDBトランザクションで保存します。

Outboxテーブル例

OutboxMessages

MessageId
EventType
Payload
OccurredAt
PublishedAt
RetryCount

登録処理

Using transaction =
    connection.BeginTransaction()

    _orderRepository.Insert(
        order,
        connection,
        transaction)

    _outboxRepository.Insert(
        New OutboxMessage With {
            .MessageId = Guid.NewGuid(),
            .EventType = "OrderCreated",
            .Payload = CreatePayload(order),
            .OccurredAt = DateTime.UtcNow
        },
        connection,
        transaction)

    transaction.Commit()

End Using

Publisher

Dim messages =
    _outboxRepository.FindUnpublished()

For Each message In messages

    Try
        Await _messageBroker.PublishAsync(message)

        _outboxRepository.MarkPublished(
            message.MessageId)

    Catch ex As Exception

        _outboxRepository.IncrementRetry(
            message.MessageId)

        Logger.Error(
            "Outbox送信に失敗しました。",
            ex)

    End Try

Next

これにより、DB更新とイベント送信要求の不整合を防ぎながら、 実際のメッセージ送信を非同期化できます。


10. 分散トランザクションの問題

単一DBではTransactionによって複数更新をまとめてRollbackできます。 しかし、複数サービスをまたぐ場合は同じ方法を簡単には利用できません。

注文DB
 ↓
決済サービス
 ↓
在庫サービス
 ↓
配送サービス

注文DBだけRollbackしても、既に完了した決済や在庫引当が自動的に取り消されるわけではありません。

分散環境で考える必要があること

  • 部分成功
  • タイムアウト
  • 再送
  • 重複処理
  • 補償処理
  • 結果整合性

そのため、分散システムでは「すべてを1つの巨大トランザクションにする」のではなく、 各サービスのローカルトランザクションと補償処理を組み合わせる設計が重要になります。


11. Saga Pattern

Saga Patternは、複数サービスにまたがる業務処理を、 複数のローカルトランザクションとして実行する設計パターンです。

注文処理例

注文作成
 ↓
在庫確保
 ↓
決済
 ↓
配送依頼
 ↓
注文確定

途中で失敗した場合は、それまで成功した処理に対して補償処理を行います。

決済で失敗した場合

注文作成      → 成功
在庫確保      → 成功
決済          → 失敗

補償処理

在庫確保取消
注文キャンセル

VB.NETで概念を表現した例

Public Async Function ExecuteOrderSagaAsync(
    order As Order) As Task

    Dim stockReserved As Boolean = False
    Dim paymentCompleted As Boolean = False

    Try
        Await _orderService.CreateAsync(order)

        Await _stockService.ReserveAsync(order)
        stockReserved = True

        Await _paymentService.PayAsync(order)
        paymentCompleted = True

        Await _shippingService.RequestAsync(order)

        Await _orderService.ConfirmAsync(order.Id)

    Catch ex As Exception

        If paymentCompleted Then
            Await _paymentService.RefundAsync(order)
        End If

        If stockReserved Then
            Await _stockService.ReleaseAsync(order)
        End If

        Await _orderService.CancelAsync(order.Id)

        Throw
    End Try

End Function

実際の分散環境では、補償処理自体も失敗する可能性があります。 そのため、Saga状態を永続化し、途中から再開できる設計が必要になります。


12. Saga OrchestrationとChoreography

Sagaには代表的にOrchestration方式とChoreography方式があります。

Orchestration

Saga Orchestrator
      ↓
Order Service
      ↓
Stock Service
      ↓
Payment Service
      ↓
Shipping Service

中央のOrchestratorが処理順序を管理します。

メリット

  • 処理フローが分かりやすい
  • 複雑な業務フローを管理しやすい
  • エラー処理を集中管理できる

Choreography

OrderCreated
     ↓
Stock Service

StockReserved
     ↓
Payment Service

PaymentCompleted
     ↓
Shipping Service

各サービスがイベントを受け取り、次のイベントを発行します。 中央制御者は存在しません。

メリット

  • サービス間を疎結合にできる
  • 中央サービスへの依存を減らせる

注意点

Choreographyを過度に利用すると、 「どのイベントから何が起きるのか」が追跡しにくくなることがあります。 複雑な業務フローではOrchestrationの方が理解しやすい場合があります。


13. Timeout設計

外部システムを呼び出す処理では、必ずTimeoutを設計します。

Timeoutがない場合、相手システムが応答しないことで、 呼び出し元のスレッドや接続リソースが長時間占有される可能性があります。

HttpClientのTimeout

Dim client As New HttpClient()

client.Timeout =
    TimeSpan.FromSeconds(10)

Timeout値を決める観点

  • 通常応答時間
  • SLA / SLO
  • 利用者が待てる時間
  • 後続処理への影響
  • Retry回数

Timeoutを単純に長くすれば安全になるわけではありません。 長すぎるTimeoutは障害の波及を大きくする可能性があります。


14. Retry設計

一時的な通信障害やサービス停止に対してはRetryが有効な場合があります。

ただし、すべてのエラーを無条件でRetryしてはいけません。

Retry候補

  • 一時的なネットワーク障害
  • Timeout
  • 一時的なサービス過負荷
  • 一時的なDB接続失敗

Retryすべきでない例

  • 認証失敗
  • 入力値不正
  • 権限不足
  • 業務ルール違反
  • 存在しないデータ

指数バックオフ

1回目 → 1秒

2回目 → 2秒

3回目 → 4秒

4回目 → 8秒

同じ間隔で連続Retryするより、徐々に待ち時間を増やすことで、 障害中のサービスへ大量アクセスすることを防ぎやすくなります。


15. Circuit Breaker

Circuit Breakerは、障害が継続している外部サービスへの呼び出しを一時的に停止するパターンです。

状態

状態意味
Closed通常通り通信する
Open通信を停止する
Half-Open復旧確認の通信を試す

動作イメージ

正常
 ↓
Closed

障害連続
 ↓
Open

一定時間経過
 ↓
Half-Open

成功
 ↓
Closed

失敗
 ↓
Open

Circuit Breakerを利用すると、停止中の外部サービスへ無駄な通信を送り続けることを防ぎ、 障害が自システムへ波及することを抑えられます。


16. Bulkhead Pattern

Bulkheadは、障害の影響範囲を分離する設計パターンです。 船の隔壁から名前が付けられています。

例えば、1つの外部サービスへの大量リクエストによって、 システム全体のスレッドや接続を使い切ってしまう状態を防ぎます。

分離対象の例

  • 同時実行数
  • Thread Pool
  • DB Connection
  • Queue
  • 外部APIごとの処理枠

SemaphoreSlimによる簡易的な制御

Private ReadOnly _semaphore As New SemaphoreSlim(10)

Public Async Function CallApiAsync() As Task

    Await _semaphore.WaitAsync()

    Try
        Await ExecuteExternalApiAsync()

    Finally
        _semaphore.Release()
    End Try

End Function

この例では、外部APIへの同時アクセスを最大10件に制限しています。


17. Dead Letter Queue

メッセージ処理で何度Retryしても成功しないメッセージを、 通常Queueから分離して保存する仕組みをDead Letter Queueと呼びます。

Queue
 ↓
Consumer
 ↓
失敗
 ↓
Retry
 ↓
失敗
 ↓
Dead Letter Queue

Dead Letter Queueに保存する情報

  • MessageId
  • 元メッセージ
  • エラー内容
  • Retry回数
  • 最終実行日時
  • Correlation ID

重要なのは、Dead Letter Queueを単なる「失敗メッセージ置き場」にしないことです。

運用担当者が確認し、原因を修正した後に再処理できる仕組みまで設計する必要があります。


18. Correlation ID

分散システムでは、1つの業務処理が複数システムを通過します。 そのため、通常のログだけでは処理全体を追跡することが難しくなります。

受注
 ↓
在庫
 ↓
決済
 ↓
配送

そこで、処理全体で共通のCorrelation IDを使用します。

CorrelationId =
"9d9228e2-8ef4-4e34-baa7-7adfd6dcb234"

ログ

[Order]
CorrelationId=9d9228e2...
OrderCreated

[Stock]
CorrelationId=9d9228e2...
StockReserved

[Payment]
CorrelationId=9d9228e2...
PaymentCompleted

[Shipping]
CorrelationId=9d9228e2...
ShippingRequested

同じCorrelation IDを検索することで、 複数システムにまたがる1つの処理を追跡できます。


19. 分散トレーシング

さらに高度な環境では、分散トレーシングを利用します。

分散トレーシングでは、1つのリクエストをTraceとして管理し、 各システム内の処理をSpanとして記録します。

Trace

Order API
│
├─ Database
│
├─ Stock API
│   └─ Stock Database
│
├─ Payment API
│   └─ External Payment
│
└─ Message Broker

確認できる情報

  • どのサービスを通ったか
  • 各処理に何秒かかったか
  • どこでエラーになったか
  • どのDBアクセスが遅いか
  • どの外部APIがボトルネックか

OpenTelemetryなどの標準的な仕組みを利用することで、 異なる言語やシステム間でもTrace情報を統一して扱いやすくなります。


20. API Versioning

企業システムでは、APIを公開した後も仕様変更が発生します。 しかし、APIを利用しているすべてのシステムを同時に変更できるとは限りません。

Version例

/api/v1/orders

/api/v2/orders

破壊的変更の例

  • プロパティ削除
  • プロパティ名変更
  • 型変更
  • 必須項目追加
  • 意味の変更

互換性を保ちやすい変更

  • 任意プロパティ追加
  • 新しいAPI追加
  • 既存項目を維持した機能拡張

APIはコードだけではなく、システム間の契約です。 変更時には利用者への影響を考える必要があります。


21. API Gateway

複数のAPIを企業内外へ公開する場合、API Gatewayを境界として配置することがあります。

Client
   ↓
API Gateway
   ↓
 ┌─────────────┐
 │ Order API   │
 │ Stock API   │
 │ Customer API│
 │ Payment API │
 └─────────────┘

API Gatewayの代表的な役割

  • 認証
  • 認可
  • ルーティング
  • Rate Limit
  • ログ
  • 監視
  • API Version管理

各サービスが個別に同じ認証処理やアクセス制御を実装するのではなく、 共通処理をGatewayへ集約できる場合があります。


22. キャッシュ設計

外部APIやDBへのアクセスが多い場合、キャッシュによって性能と可用性を改善できる場合があります。

Cache Aside

Application
    ↓
Cache確認
    ↓
存在する → Cacheから返す

存在しない
    ↓
Database
    ↓
Cacheへ保存
    ↓
結果を返す

キャッシュに向いているデータ

  • マスタデータ
  • 頻繁に参照される設定
  • 変更頻度の低いデータ
  • 生成コストの高い検索結果

キャッシュで難しい問題

  • いつ削除するか
  • いつ更新するか
  • 古いデータを何秒許容するか
  • 複数サーバーでどう共有するか
  • Cache Stampedeをどう防ぐか

キャッシュ導入で最も難しいのは保存することではなく、 「いつ無効化するか」です。


23. Eventual Consistency

分散システムでは、すべてのシステムが常に完全に同じ状態になるとは限りません。

イベント連携では、受注システムで注文が登録されてから、 数秒後に分析システムへ反映されることがあります。

10:00:00
Order Created

10:00:01
Event Published

10:00:02
Analytics Consumer Received

10:00:03
Analytics DB Updated

このように、一時的には状態が異なっていても、 最終的に整合する考え方を結果整合性と呼びます。

結果整合性を採用する場合に必要なこと

  • どの程度の遅延を許容するか
  • 処理中状態をどう表示するか
  • 失敗時にどう再送するか
  • 重複イベントをどう処理するか
  • 利用者へどう説明するか

強い整合性が必要な処理と、結果整合性で問題ない処理を業務要件から判断する必要があります。


24. 分散システムにおけるセキュリティ

システム数が増えると、通信経路や認証情報も増えます。 そのため、セキュリティをシステム単位ではなく全体として設計する必要があります。

主な対策

  • TLSによる通信暗号化
  • OAuth 2.0などを利用した認可
  • アクセストークン管理
  • Secretの安全な保管
  • APIアクセス制御
  • 最小権限
  • 監査ログ

やってはいけない例

Const ApiKey As String =
    "production-secret-api-key"

API Key、DBパスワード、Client Secretなどをソースコードへ直接書いてはいけません。

環境変数、Secret Store、クラウドのSecret管理サービスなどを利用し、 コードと認証情報を分離します。


25. Graceful Degradation

一部の外部サービスが停止しただけで、システム全体を停止させる必要があるとは限りません。

重要でない機能を一時的に制限しながら、 主要業務を継続する設計をGraceful Degradationと呼びます。

レコメンドAPI停止

→ 商品検索は利用可能
→ レコメンドだけ非表示
分析システム停止

→ 受注登録は継続
→ 分析イベントはQueueへ保存

設計時の考え方

  • 絶対に必要な機能は何か
  • 停止しても業務継続できる機能は何か
  • 後から再処理できる処理は何か
  • 利用者へどの状態を表示するか

すべてを「成功するか全部停止するか」の二択にしないことが、 大規模システムの可用性向上につながります。


26. 実務でありがちな分散システム設計の失敗

すべて同期APIで接続する

すべてのシステムを同期通信で接続すると、 1つのサービス障害が連鎖してシステム全体へ波及しやすくなります。

Timeoutが設定されていない

応答しない外部サービスを待ち続けることで、 スレッドや接続などのリソースを消費します。

Retryを無制限に行う

障害中のサービスへ大量のRetryを行うと、 さらに負荷を高め、復旧を妨げる可能性があります。

冪等性を考えていない

メッセージ再送や通信Retryによって、注文、決済、ポイント付与などが二重実行される危険があります。

DB更新後に直接イベント送信する

DB更新とイベント送信の間で障害が発生すると不整合になります。 Outbox Patternなどを検討します。

Correlation IDがない

障害発生時に、複数システムをまたぐ1つのリクエストを追跡できなくなります。

Queueを入れただけで安心する

Queueを導入しても、Retry、Dead Letter Queue、監視、冪等性、再処理設計がなければ、 障害時にメッセージが大量滞留する可能性があります。

マイクロサービス化を目的にする

サービスを細かく分割すると、ネットワーク通信、監視、認証、デプロイ、データ整合性などの複雑性が増えます。

「新しいから」という理由ではなく、 独立した変更・運用・スケーリングが本当に必要なのかを判断することが重要です。


27. エンタープライズ統合設計の実践例

受注システムを中心に、以下のシステムが存在するとします。

                    ┌──────────────┐
                    │ Customer API │
                    └──────┬───────┘
                           │
                           ↓
┌──────────┐       ┌──────────────┐
│ WinForms │ ───→  │ Order System │
└──────────┘       └──────┬───────┘
                           │
                     Local Transaction
                           │
                  ┌────────┴────────┐
                  │ Orders          │
                  │ OutboxMessages  │
                  └────────┬────────┘
                           │
                           ↓
                     Message Broker
                    ↙       ↓       ↘
              Stock      Billing    Analytics
                │            │
                ↓            ↓
             Warehouse    Accounting

注文登録

  1. WinFormsから注文登録を要求する
  2. Order Systemが入力値を検証する
  3. Ordersへ注文を登録する
  4. 同じTransactionでOutboxMessagesへイベントを登録する
  5. Commitする
  6. Outbox PublisherがイベントをMessage Brokerへ送信する
  7. Stock、Billing、Analyticsがそれぞれ処理する

障害発生時

  • Stock停止 → Queueにメッセージを保持
  • 一時エラー → Retry
  • 継続失敗 → Dead Letter Queue
  • 重複配信 → EventIdで冪等制御
  • 障害調査 → Correlation IDで全システムを追跡

このように、単一の技術だけではなく、 トランザクション、Outbox、Queue、冪等性、Retry、監視を組み合わせてシステム全体を設計します。


28. 演習課題

演習1:同期・非同期通信を分類する

受注、在庫、決済、メール、分析、帳票生成の各処理について、 同期通信と非同期通信のどちらを利用するか判断し、その理由を説明してください。

演習2:Outbox Patternを実装する

Ordersテーブルへの注文登録とOrderCreatedイベントを同一Transactionで保存し、 別Publisherから未送信イベントを送信する処理を設計してください。

演習3:冪等Consumerを実装する

ProcessedMessagesテーブルを利用し、 同じEventIdが複数回届いても業務処理が1度だけ実行されるConsumerを作成してください。

演習4:Sagaを設計する

注文作成、在庫確保、決済、配送依頼の4処理について、 各処理が失敗した場合の補償処理を設計してください。

演習5:障害耐性を設計する

外部APIに対してTimeout、Retry、Circuit Breakerをどの順番で適用するか検討し、 障害時の処理フローを設計してください。

演習6:Dead Letter Queue運用を設計する

5回Retryしても処理できなかったメッセージをDead Letter Queueへ移動し、 運用担当者が原因確認後に再処理できる仕組みを設計してください。

演習7:Correlation IDを導入する

WinForms → Order API → Stock API → Payment APIという処理経路について、 同じCorrelation IDをログへ記録し、処理全体を追跡できる仕組みを設計してください。

演習8:Graceful Degradationを設計する

顧客ランク取得API、分析システム、帳票システムのそれぞれが停止した場合に、 受注システムを継続可能か判断し、代替動作を設計してください。

演習9:既存VBシステムをイベント駆動化する

注文登録後に在庫更新、メール送信、CSV出力を同期実行している既存VB.NETシステムを、 Outbox Patternと非同期メッセージングを利用した構成へ段階的に変更してください。


29. まとめ

本章では、VB.NET業務システムを企業全体のシステム群へ統合するための、 エンタープライズシステム統合設計と分散アーキテクチャについて学習しました。

単体アプリケーションでは、メソッド呼び出しやDBトランザクションによって比較的簡単に処理を制御できます。 しかし、複数システムをネットワーク越しに連携すると、 通信失敗、Timeout、重複配信、部分成功、処理順序の違いなどを考慮する必要があります。

システム間連携では、即座に結果が必要な処理には同期API、 後から処理できるものにはQueueやイベントを利用するなど、 業務要件に応じて同期・非同期通信を使い分けます。

非同期メッセージングでは、メッセージが重複する可能性を前提とし、 EventIdや処理履歴を利用してConsumerを冪等に設計することが重要です。

DB更新とイベント発行の不整合にはOutbox Patternが有効です。 業務データと送信予定イベントを同一トランザクションで保存し、 後続PublisherがMessage Brokerへ送信することで、更新とイベント発行要求の整合性を保ちます。

複数サービスにまたがる業務処理では、単一DBのようなRollbackができない場合があります。 Saga Patternと補償処理を利用し、各サービスのローカルトランザクションを組み合わせて業務全体の整合性を管理します。

障害耐性では、Timeout、Retry、Circuit Breaker、Bulkheadを組み合わせます。 外部サービスが停止しても無制限に待機・再試行せず、 障害を検知して影響範囲を限定することが重要です。

また、分散システムでは障害を完全に防ぐことだけでなく、 障害発生後に原因を追跡できることが重要です。 Correlation ID、構造化ログ、Metrics、Distributed Tracingを利用し、 複数サービスをまたぐ処理を可観測にします。

さらに、すべてのシステムを常に完全同期させるのではなく、 業務要件によってはEventual Consistencyを採用します。 強い整合性が必要な処理と、数秒程度の反映遅延を許容できる処理を区別することが、 大規模システム設計では重要です。

そして、分散化やマイクロサービス化そのものを目的にしてはいけません。 システムを分割すると、デプロイ、通信、認証、監視、データ整合性、障害対応など新しい複雑性が生まれます。 必要な部分だけを適切に分離し、既存VB.NET資産と新しいサービスを段階的に統合することが現実的なアプローチです。

本章のゴールは、VB.NETという1つの言語やアプリケーションの範囲を超え、 API、メッセージング、イベント、分散トランザクション、障害耐性、可観測性を組み合わせながら、 企業全体のシステムを安全に連携できるアーキテクチャを設計できるようになることです。