Unit of Workパターン - 変更をまとめて一貫して保存する

中級 | 5分 で読める | 2026.06.14

公式ドキュメント

Unit of Workは、複数の永続化変更をまとめ、成功時にcommit、失敗時にrollbackするパターンです。

単一DBの同じtransaction内でOrders、Stocks、PaymentRecordsの3 RepositoryをUnit of Workがまとめ、成功時にcommit、失敗時にrollbackし、外側のPayment APIやメールは戻らないことを示す図

Transactionの単位をそろえる

注文の状態変更と在庫の減算を別々に保存すると、失敗時に片方だけが残ります。同じDBで一体なら、1つのtransactionへまとめます。

begin
  Orderを確定
  Stockを減算
  PaymentRecordを追加
commit

失敗 → rollback

Unit of Workは、ユースケースへtransaction境界を提供します。

interface UnitOfWork {
  run<T>(work: (repos: Repositories) => Promise<T>): Promise<T>;
}

await unitOfWork.run(async ({ orders, stocks }) => {
  const order = await orders.findById(orderId);
  const stock = await stocks.findByProductId(order.productId);
  order.confirm();
  stock.allocate(order.quantity);

  await orders.save(order);
  await stocks.save(stock);
});

runは成功時にcommit、例外時にrollbackし、同じconnectionを各Repositoryへ共有します。

Repositoryとの役割分担

要素主な役割
Repository個別のAggregateを取得・保存する
Unit of Work複数Repositoryの変更を同じ作業単位にまとめる
DB TransactionDB上でcommit・rollbackを実現する

Repositoryごとにtransactionを開始すると、全変更をまとめて戻せません。Unit of Workへ業務ルールも書きません。

ORMに変更追跡やtransaction callbackがあれば、別classを重ねる必要はありません。

外部APIはrollbackできない

DB transaction中に決済APIを呼び、あとでDBをrollbackしても、外部で成立した決済は自動では戻りません。メール送信やmessage publishも同じです。

DB rollback ≠ 決済取消 ≠ 送信済みメールの取消

異なるシステムでは、Outbox、idempotency key、再試行、補償処理を検討します。Unit of Workだけでは解決しません。

境界を決める注意点

  • transaction中に利用者入力や遅い外部APIを待たない
  • deadlock時に安全に再試行できるか確認する
  • 例外を握りつぶして誤ってcommitしない

読み取りや1回の保存だけなら、明示的なUnit of Workは不要な場合があります。

まとめ

  • Unit of Workは複数変更を1つのtransaction単位へまとめる
  • 成功時にcommitし、失敗時にrollbackする
  • Repositoryは個別の保存、Unit of Workは全体の境界を担当する
  • DB外の決済・メール・messageはrollbackできない

個別保存はリポジトリパターン、複数システムは分散トランザクションを参照してください。

参考リソース

← 一覧に戻る
PR
PR
PR
PR