外部SaaS(決済、CRM、メール配信、認証基盤、チャット等)と自社システムを連携するときの設計・実装・運用の指針をまとめる。
目次
- 基本原則
- アーキテクチャ設計
- 認証・認可とシークレット管理
- APIクライアントの実装
- Webhook受信
- データ同期
- エラーハンドリングと障害対応
- セキュリティとコンプライアンス
- 可観測性(ログ・メトリクス・トレース)
- テスト戦略
- バージョン管理と変更追従
- ベンダー選定とロックイン対策
- 運用
- チェックリスト
1. 基本原則
- 外部は必ず失敗する前提で設計する。 ネットワーク断、タイムアウト、レート制限、仕様変更、障害は日常的に起きる。
- 外部障害を自社サービスの全面障害にしない。 連携機能が止まっても、コア機能は動き続ける(グレースフル・デグラデーション)。
- 外部のデータモデルを自社ドメインに持ち込まない。 SaaS固有の概念は境界で変換する。
- すべての書き込み操作は冪等に。 再試行・重複配信が起きても結果が変わらないようにする。
- 自社が「正」とするデータの所在(Source of Truth)を項目ごとに明確にする。
- 最小権限。 必要なスコープ・権限だけを要求する。
2. アーキテクチャ設計
2.1 腐敗防止層(Anti-Corruption Layer)を置く
SaaS SDK や API を業務ロジックから直接呼ばない。アダプタ層を挟み、自社のインターフェースで抽象化する。
[業務ロジック] → [自社インターフェース (PaymentGateway 等)] → [Stripeアダプタ] → Stripe API
└→ [別ベンダーアダプタ]
- インターフェースは「自社が必要な操作」で定義する(SaaSのAPIをそのまま写さない)。
- SaaS固有のID・ステータス・エラーコードはアダプタ内で自社の型に変換する。
- 差し替え・テスト用モックが容易になる。
2.2 同期呼び出しと非同期処理を使い分ける
| ケース | 方式 |
|---|---|
| ユーザーが結果を即座に必要とする(決済承認、ログイン) | 同期呼び出し+短いタイムアウト |
| 結果が後でよい(CRM登録、通知送信、分析連携) | キュー経由の非同期処理 |
| 大量データの送受信 | バッチ/バルクAPI |
| SaaS側のイベント起点 | Webhook受信 → キュー投入 |
ユーザーのリクエスト処理パスから外部呼び出しをできるだけ外すと、レイテンシと障害影響が小さくなる。
2.3 Outboxパターン
DB更新と外部API呼び出しを一貫させたいときは、同一トランザクションで「送信予定イベント」をOutboxテーブルに書き、別ワーカーが送信する。これによりDBコミット済みなのに外部送信されない/その逆、という不整合を防ぐ。
2.4 IDマッピング
- 自社IDと外部IDの対応表を持つ(例:
user_id ↔ stripe_customer_id)。 - 外部側に自社IDをメタデータとして保存できるなら保存する(突合が楽になる)。
- 外部IDを自社の主キーにしない。
3. 認証・認可とシークレット管理
3.1 認証方式の選択
| 方式 | 用途 | 注意点 |
|---|---|---|
| APIキー | サーバー間、自社アカウント | 漏洩時の影響が大きい。権限を絞ったキーを発行 |
| OAuth 2.0 (Authorization Code + PKCE) | ユーザーの代理でアクセス | リフレッシュトークンの安全な保管と更新処理 |
| OAuth 2.0 (Client Credentials) | サーバー間 | トークンのキャッシュと期限前更新 |
| JWT / 署名付きリクエスト | 高セキュリティ連携 | 時刻同期、鍵ローテーション |
| mTLS | 金融・社内基幹 | 証明書の期限管理 |
3.2 シークレット管理
- シークレットはコード・リポジトリ・ログに含めない。Secrets Manager / Vault 等で管理する。
- 環境(本番/ステージング/開発)ごとに別キーを使う。本番キーを開発環境に置かない。
- 定期ローテーションの手順を用意し、新旧キーを並行稼働できる設計にする。
- 漏洩時の失効手順を Runbook に書いておく。
- 可能なら IP 制限・スコープ制限・読み取り専用キーを活用する。
3.3 OAuthトークン運用
- アクセストークンは期限前に更新する(期限切れエラーを待たない)。
- リフレッシュトークンは暗号化して保存する。
- 複数ワーカーが同時に更新して競合しないよう、ロックをかける(ローテーション型リフレッシュトークンでは特に重要)。
- ユーザーが連携を解除・権限取消した場合(
invalid_grant等)を検知し、再連携を促すUIフローを用意する。 - 要求スコープは最小限にし、追加機能が必要になったら段階的に追加同意を取る。
4. APIクライアントの実装
4.1 タイムアウト
- すべての外部呼び出しにタイムアウトを設定する。 デフォルト無制限のライブラリは多い。
- 接続タイムアウト(短め:1〜3秒)と読み取りタイムアウト(処理に応じて)を分ける。
- 上流のリクエスト全体の締め切りから逆算して設定する。
4.2 リトライ
- 指数バックオフ+ジッターを使う(例:
min(cap, base * 2^n) * random(0.5, 1.0))。 - リトライ対象を限定する:
- リトライする:ネットワークエラー、タイムアウト、
429、502、503、504 - リトライしない:
400、401、403、404、422などクライアント起因のエラー
- リトライする:ネットワークエラー、タイムアウト、
Retry-Afterヘッダーがあれば従う。- 最大試行回数・総時間に上限を設ける。
- 非冪等な操作(POST)は冪等キーなしでリトライしない。
4.3 冪等性
- SaaSが冪等キー(
Idempotency-Keyヘッダー等)をサポートしていれば必ず使う。 - キーは業務上の一意な操作に紐づけて生成する(例:
order-{order_id}-charge)。リトライ毎に新規生成しない。 - サポートがない場合は、送信前に外部側の存在確認をする、または自社側で送信済み記録を持つ。
4.4 レート制限
- SaaSの制限値(リクエスト/秒、日次上限、同時接続数)をドキュメントで確認し、自社側でもスロットリングする。
- レスポンスヘッダー(
X-RateLimit-Remaining等)を監視する。 - 複数プロセスから呼ぶ場合は、共有のレートリミッター(Redis等)で全体を制御する。
- バルクAPIがあれば優先して使う。
4.5 サーキットブレーカー
- 連続失敗が閾値を超えたら一定時間呼び出しを止め、即座にフォールバックする。
- 障害中のSaaSに大量リトライを送り続けて、自社のスレッド・コネクションを枯渇させるのを防ぐ。
- バルクヘッド(連携先ごとにコネクションプール/ワーカーを分離)も併用する。
4.6 ページネーション
- カーソル方式がある場合はオフセット方式より優先する(取得中のデータ変動に強い)。
- 全件取得ループには上限と中断・再開の仕組みを持たせる。
4.7 SDKの扱い
- 公式SDKは便利だが、バージョンを固定し、更新は変更履歴を確認してから行う。
- SDKのデフォルト設定(タイムアウト、リトライ、ログ出力)を確認・上書きする。
- SDKの型をアダプタ層の外に漏らさない。
5. Webhook受信
5.1 受信処理の基本形
受信 → 署名検証 → 生データ保存 → キュー投入 → 即 2xx 返却
↓
ワーカーで非同期処理(冪等)
- すぐに2xxを返す。 重い処理を同期で行うとタイムアウト→再送の連鎖が起きる。
- 処理はキュー経由で非同期に行う。
5.2 セキュリティ
- 署名を必ず検証する(HMAC等)。比較は定数時間比較で行う。
- 署名検証には生のリクエストボディを使う(JSONパース後に再シリアライズすると一致しない)。
- タイムスタンプを検証し、古いリクエストを拒否してリプレイ攻撃を防ぐ。
- 可能なら送信元IPの許可リストも併用する。
- エンドポイントURLは推測されにくくしてもよいが、それだけを防御にしない。
5.3 冪等性と順序
- Webhookは重複配信される前提。イベントIDで処理済み判定をする。
- 順序は保証されない前提。
updatedがcreatedより先に届くこともある。- イベントのタイムスタンプやバージョンで古いイベントを破棄する。
- または、Webhookを「変更通知」として扱い、最新状態をAPIで取得し直す(Thin Event方式)。
- ペイロードの内容を鵜呑みにせず、重要な処理(決済完了など)はAPIで状態を再確認する。
5.4 取りこぼし対策
- Webhookは届かないことがある。定期的な突合バッチ(Reconciliation)で補完する。
- 受信エンドポイントの障害時に備え、SaaS側の再送ポリシー(回数・期間)を把握しておく。
- 管理画面からの手動再送手順を Runbook に記載する。
6. データ同期
6.1 同期方式
| 方式 | 特徴 |
|---|---|
| Webhook(Push) | リアルタイム性が高い。取りこぼし対策が必要 |
| ポーリング(Pull) | 実装が単純。差分取得APIがないと非効率 |
差分同期(updated_since / カーソル) | 効率的。最終同期位置の永続化が必要 |
| 全件同期 | 確実だが重い。定期的な整合性チェック用に |
| ハイブリッド | Webhookで即時反映+定期バッチで突合(推奨) |
6.2 設計ポイント
- 項目ごとにマスター(Source of Truth)を決める。双方向同期は競合解決ルールを必ず定義する。
- 同期のチェックポイント(最終カーソル、最終更新日時)は処理成功後に更新する。
- 差分取得の境界は少しオーバーラップさせ(例:前回時刻の数分前から)、取りこぼしを防ぐ。冪等処理で重複を吸収する。
- 削除の同期を忘れない(論理削除フラグ、削除イベント、全件突合)。
- 時刻はUTCで扱い、タイムゾーンの混在を避ける。
6.3 外部データのキャッシュ
- 頻繁に参照する外部データはキャッシュし、外部障害時もある程度動けるようにする。
- キャッシュの鮮度要件と無効化タイミングを明確にする。
7. エラーハンドリングと障害対応
7.1 エラー分類
| 分類 | 例 | 対応 |
|---|---|---|
| 一時的エラー | タイムアウト、503、429 | リトライ |
| 恒久的エラー | 400、422(バリデーション) | リトライせずエラー記録・通知 |
| 認証エラー | 401、トークン失効 | トークン更新 → ダメなら再連携を促す |
| 権限エラー | 403 | 設定ミスの可能性。アラート |
| 想定外 | 未知のレスポンス形式 | 安全側に倒して失敗扱い・アラート |
7.2 デッドレターキュー(DLQ)
- リトライ上限を超えたジョブはDLQに退避し、失われないようにする。
- DLQの件数を監視し、原因修正後に再処理できる仕組みを用意する。
7.3 フォールバック
- 外部障害時の挙動を機能ごとに決めておく:
- 処理を保留してキューに溜め、復旧後に再送
- キャッシュ値で応答
- 機能を一時的に無効化し、ユーザーに案内表示
- 代替ベンダーへの切り替え(重要機能のみ)
7.4 不整合への備え
- 「外部では成功したが自社側の記録に失敗した」ケースを想定し、突合と補正の手順を用意する。
- 重要な処理(課金など)は、状態遷移を明示的に管理する(
pending→confirmed/failed)。
8. セキュリティとコンプライアンス
- 送信データを最小化する。 SaaSに渡す個人情報は必要な項目だけに絞る。
- 個人情報を送る場合は、プライバシーポリシー・委託先管理・越境移転(データ保管リージョン)を確認する。
- SaaSのセキュリティ認証(SOC 2、ISO 27001、ISMAP 等)を確認する。
- ログにトークン・個人情報・カード情報を出力しない(マスキング)。
- 外部から受け取ったデータは信頼せず、バリデーション・サニタイズする(SSRF、インジェクション対策)。
- ユーザーの退会・削除要求時に、外部SaaS側のデータも削除する手順を用意する。
- SaaS側の管理者アカウントはSSO・MFAを必須にし、担当者の異動時に権限を見直す。
- 依存SDKの脆弱性をスキャン対象に含める。
9. 可観測性(ログ・メトリクス・トレース)
9.1 ログ
- 外部呼び出しごとに記録する:連携先、エンドポイント、メソッド、ステータスコード、レイテンシ、リトライ回数、相関ID。
- SaaSが返すリクエストID(
X-Request-Id等)を保存する(ベンダー問い合わせ時に必須)。 - リクエスト/レスポンス本文は、マスキングした上で必要な範囲に限る。
9.2 メトリクスとアラート
| メトリクス | アラート例 |
|---|---|
| エラー率(連携先別) | 5分間で5%超 |
| レイテンシ(p95/p99) | 通常の2倍超 |
| レート制限残量 | 残り10%未満 |
| キュー滞留数・DLQ件数 | 閾値超過 |
| Webhook受信数 | 通常時間帯に一定時間ゼロ(取りこぼし検知) |
| トークン・証明書の有効期限 | 期限30日前 |
| サーキットブレーカーの状態 | オープン時 |
9.3 トレース
- 分散トレーシングで外部呼び出しをスパンとして記録し、遅延の原因を特定できるようにする。
10. テスト戦略
| レベル | 内容 |
|---|---|
| ユニットテスト | アダプタ層をモックし、業務ロジックを検証 |
| アダプタのテスト | 記録済みレスポンス(VCR等)やスタブサーバーでマッピングを検証 |
| 契約テスト | 外部APIのレスポンス形式が想定通りか定期的に確認 |
| サンドボックス結合テスト | SaaSのテスト環境で実際のフローを通す |
| 障害注入テスト | タイムアウト・5xx・429・不正レスポンス・Webhook重複/順序逆転を再現 |
| 本番スモークテスト | デプロイ後に低リスクな読み取りAPIで疎通確認 |
- サンドボックスと本番の挙動差(レート制限、機能差、データ)を把握しておく。
- テストで本番キーを使わない。CIに本番シークレットを置かない。
11. バージョン管理と変更追従
- APIバージョンを明示的に固定する(ヘッダーやURLで指定)。デフォルト=最新に依存しない。
- SaaSのチェンジログ・廃止予定(Deprecation)通知・ステータスページを購読し、担当者を決める。
- レスポンスの未知フィールドは無視し、必須フィールドの欠落は検知できるようにする(寛容な受信)。
- 列挙値(ステータス等)に未知の値が来ても落ちないようにする。
- バージョンアップは、ステージングで検証 → 段階的に本番切り替え(フィーチャーフラグ活用)。
12. ベンダー選定とロックイン対策
12.1 選定時の確認項目
- API の品質(ドキュメント、SDK、サンドボックス、Webhook、冪等キー、バルクAPI)
- SLA と過去の障害実績、ステータスページの有無
- レート制限と料金体系(従量課金の上限、超過時の挙動)
- データエクスポート手段(解約時に全データを取り出せるか)
- セキュリティ認証、データ保管リージョン、サポート体制
- ベンダーの事業継続性
12.2 ロックイン対策
- 腐敗防止層で抽象化し、差し替えコストを下げる。
- 重要なデータは自社DBにも保持する(外部にしかないデータを作らない)。
- 出口戦略(移行手順、データ移行方法)を事前に検討しておく。
- コスト監視:API呼び出し数・従量課金額をダッシュボード化し、異常な増加を検知する。
13. 運用
- 連携台帳を整備する:連携先、用途、担当者、契約、認証方式、シークレットの保管場所、送受信データ、SLA、サポート窓口。
- Runbook を用意する:
- 連携先障害時の対応(フォールバック切り替え、ユーザー告知)
- シークレット漏洩時の失効・再発行
- DLQ・未処理データの再処理
- Webhookの手動再送・突合
- SaaSのステータスページを監視ツールに取り込む。
- 定期棚卸し:使われていない連携、不要な権限、期限切れ間近の証明書・トークンを確認する。
- SaaS側の管理画面での設定変更も変更管理の対象にする(誰がいつ何を変えたか)。
14. チェックリスト
設計
- アダプタ層で外部APIを抽象化している
- 項目ごとの Source of Truth を定義した
- 同期/非同期の使い分けを決めた
- 外部障害時のフォールバック挙動を決めた
- IDマッピングの方法を決めた
実装
- すべての外部呼び出しにタイムアウトがある
- 指数バックオフ+ジッターでリトライし、対象エラーを限定している
- 書き込み操作に冪等キーを使っている
- 自社側でレート制限を制御している
- サーキットブレーカー/バルクヘッドを導入した
- APIバージョンを固定している
Webhook
- 署名とタイムスタンプを検証している
- 即座に2xxを返し、非同期で処理している
- イベントIDで重複排除している
- 順序逆転に対応している
- 定期突合で取りこぼしを補完している
セキュリティ
- シークレットを専用ストアで管理し、環境ごとに分離している
- ローテーション・失効手順がある
- 要求スコープ・権限が最小限
- ログに機密情報が出ない
- 送信する個人情報を最小化し、法務確認済み
運用
- 連携先別のエラー率・レイテンシ・キュー滞留を監視している
- トークン・証明書の期限を監視している
- DLQと再処理の仕組みがある
- Runbook と連携台帳がある
- チェンジログ・廃止予定の確認担当がいる