2024 年、PDF レポートのカスタマイズ需要と事業範囲が拡大し、利用者データの保護と承認フローへの要求も高まりました。
従来は飛書の多次元テーブル、承認フロー、HTML から PDF への変換で運用していましたが、権限制御、レポート管理、連携が難しくなり、私が新システムの設計を担当しました。
現在の業務と、将来の LIMS・モバイル連携を支える必要がありました。機能以外にも、複数サービスをまたぐ操作の一貫性へ、どの程度の実装・運用コストをかけるべきかを早く決める必要がありました。
1. サービス境界とトランザクション境界は異なる
当時は認可、基礎、フレームワーク、ゲートウェイ、ストレージ、レポートの六種類に分け、Apache Dubbo の RPC で通信しました。
振り返ると、業務と技術の責務が混在しています。ゲートウェイやフレームワークの独立デプロイは、DDD の境界づけられたコンテキストを証明しません。DDD 自体もマイクロサービスを必須にしていません。
将来の LIMS・モバイル対応には明確な API と変化できる業務境界が必要ですが、最初から全モジュールを別プロセスにする必然性はありません。再検討すべき判断の一つです。
また、サービスをまたぐ呼び出しすべてに分散トランザクションが必要なわけではありません。照会や後で送れる通知と、複数の書き込みを同時に成功・失敗させる要件は異なります。
本当に扱うべきなのは、複数サービスが所有するデータを変え、業務がその整合を要求する操作です。
例えば状態更新、ファイル生成、通知は一つのレポートフローに含まれますが、同時完了が必要かは業務ルールで決まります。これは要求を説明する例で、当時すべてを同じトランザクションに入れていたという意味ではありません。
通知は再試行できても、未生成のレポートが承認状態へ進んでよいかは明確な制約が必要です。同じフローというだけで、すべてを強い一貫性に含めるべきではありません。
2. Seata が解決する問題を理解する
調査では Seata を参考にしました。AT、TCC、Saga、XA は、業務への組み込み、DB 対応、一貫性の意味が異なります。
AT では、参加サービスがローカルトランザクション内で業務データと undo log を書き込み、ローカルにコミットします。コーディネーターが全体の結果に応じて確定・ロールバックを調整し、参加側がログで既存の変更を戻します。
グローバル文脈の伝播、ブランチ登録、グローバルロックも必要です。「呼び出し元が例外を出したら相手に戻させる」だけでは説明できず、単一 DB のローカルトランザクションと同じ分離性とも限りません。
サービスは分かれていましたが初期の利用量は小さく、導入には配置資源だけでなく設定、監視、復旧、互換性の負担もありました。
そのため当時はすぐに導入しませんでした。この判断は要求と運用能力に基づくべきで、ベンダーの評判や一度のメモリ使用量を製品固定の性質と見なして決めるものではありません。
導入しなくても問題は残ります。どこをローカルで保証し、どの失敗を補償し、どこに原子的な保証がないかを明確にする必要があります。
3. 当時の三つの処理方法
例外記録、共有 DB、動的データソースを使いました。それぞれ別のコストを下げる方法で、同等の三種類の分散トランザクションではありません。
例外記録:手動復旧の根拠
一時的不整合と事後修復を許容できると判断した操作は、結果と例外をログに残して人が対応しました。
低頻度で照合・復旧できる場合に使えますが、ログだけでは一貫性を保証しません。非中核でも損失がないとは限りません。
信頼できる復旧には、失敗の発見、業務対象との関連、完了済み工程の識別、重複補償の防止が必要です。ログを書いたことと復旧の仕組みが完成したことは別です。
共有 DB:データベースをまたぐ負担の削減
認可とフレームワークのサービスは同じ DB を使い、データソースの管理と横断アクセスを減らしました。
ただし同じ DB は同じトランザクションを意味しません。別接続で個別コミットすれば片方だけ成功します。通常の Dubbo 呼び出しは DB トランザクションを自動伝播しません。
一連の書き込みをローカルで保証するには、同じサービス、トランザクションマネージャー、資源など、一つの境界へ集める必要があります。共有 DB には結合を強める代償もあり、所有とアクセスの境界は引き続き必要です。
動的データソース:実行場所の集約
Baomidou の Dynamic Datasource により、一つのサービスで複数 DB を選択して操作しました。
サービス間通信は減らせますが、ルーティングとトランザクション調整は異なります。一つのプロセスが複数 DB にアクセスできても、原子的なコミットにはなりません。
複数接続のローカルトランザクションを組み合わせる場合も、コミット順と失敗の境界を確認します。先の DB が成功して後の DB が失敗すれば、例外を捕捉しても既存コミットをすべて戻せるとは限りません。
実行場所を集める道具としては有効ですが、分散トランザクションの直接の代替ではありません。部分コミット後の回復か、要件を満たす調整が必要です。
4. 一貫性を業務ルールにする
方式の前に「発生してはいけない状態」を決めます。
| 要求 | 優先して考える方法 | 確認する境界 |
|---|---|---|
| 複数の書き込みが共に成功・失敗 | ローカルへ集約。困難なら分散調整 | 実際の参加資源とロールバック |
| 一時的不整合はよいが最終完了が必要 | 永続的な未処理状態、再試行、補償 | タスクを失わず繰り返しが安全 |
| 低頻度で手動復旧を許容 | 記録、照合、手動補償 | 発見可能性、責任者、手順 |
これは評価の軸であり、当時すべて実装済みという意味ではありません。
オブジェクトストレージなど DB 外の資源もあります。DB を戻しても生成済みファイルは自動削除されず、Seata AT でも通常のストレージ操作まで戻るとは仮定できません。
生成待ち・成功・失敗を明示して再試行・補償する方法が考えられます。メッセージを使うなら、DB コミットと送信の間の信頼性も必要で、トランザクショナル Outbox などを検討します。
これらは業務に合わせて検証すべき方向で、中間ソフトウェアの名前は失敗分析の代わりにはなりません。
5. 将来の余地と導入準備は異なる
登録・設定に Nacos を選び、Seata を後の候補に残しました。ただし Nacos の採用だけで Seata を導入可能とは言えません。
境界整理、データソースプロキシ、文脈伝播、SQL 対応、競合、障害ロールバックを確認し、DB 外の資源を別途設計します。
より有効な準備は、再試行可能な工程、冪等性、補償する状態、失敗を発見・対応する責任を明示することです。
サービス横断の原子性が必要、あるいは手動復旧の頻度・費用・危険が許容できないと分かった時点で、Seata、TCC、Saga の評価に具体的な根拠が生まれます。
6. 設計判断では失敗経路も検証する
李智慧の『大型网站技术架构』は、構成が事業規模とともに変化することを説いています。初期に仮定の規模へ全基盤を備える必要はなくても、今何を保証できるかは理解すべきです。
今回の価値は制約に合わせて複雑さを抑えたことです。一方、共有 DB と動的ルーティングは実装を軽くしても、サービス・DB 横断の原子性を自動で提供しません。
遠隔コミット後の RPC タイムアウト、順次コミット途中の失敗、ファイル生成成功後の状態更新失敗、再試行の副作用を意図的に検証します。
必要な状態を定義して境界を絞り、残る問題を検証可能な補償・調整で扱うこと。過剰な先回りと、未検証の一貫性を既成事実にすることの両方を避ける必要があります。