本章では、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
注文登録
- WinFormsから注文登録を要求する
- Order Systemが入力値を検証する
- Ordersへ注文を登録する
- 同じTransactionでOutboxMessagesへイベントを登録する
- Commitする
- Outbox PublisherがイベントをMessage Brokerへ送信する
- 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、メッセージング、イベント、分散トランザクション、障害耐性、可観測性を組み合わせながら、 企業全体のシステムを安全に連携できるアーキテクチャを設計できるようになることです。