Unit of Workパターンとは、複数の変更を1つの作業単位としてまとめ、最後に一貫して保存するための設計パターンです。
一言でいうと
Unit of Workは「この処理で行った変更をまとめてコミットする」ための管理役です。
注文作成では、注文を保存し、在庫を減らし、支払い記録を作るかもしれません。途中で失敗したら、全部なかったことにしたい場面があります。
登場人物
| 名前 | 役割 |
|---|---|
| Repository | 個別の集約を保存/取得する |
| Unit of Work | 変更のまとまりを管理する |
| Transaction | DB上の一貫性を守る仕組み |
| Use Case | 作業単位を開始し、最後に確定する |
Repositoryが「何を保存するか」を扱い、Unit of Workが「いつまとめて確定するか」を扱います。
処理の流れ
注文作成を例にします。
| 順番 | 処理 |
|---|---|
| 1 | Unit of Workを開始する |
| 2 | 注文を作成する |
| 3 | 在庫を減らす |
| 4 | 支払い記録を作る |
| 5 | すべて成功したらcommitする |
| 6 | 失敗したらrollbackする |
このまとまりがないと、注文だけ保存されて在庫が減っていない、といった不整合が起きやすくなります。
Repositoryとの関係
interface UnitOfWork {
orders: OrderRepository;
stocks: StockRepository;
payments: PaymentRepository;
commit(): Promise<void>;
rollback(): Promise<void>;
}
ユースケースは複数のRepositoryを使いながら、最後にUnit of Workで確定します。
class CreateOrderUseCase {
constructor(private readonly uow: UnitOfWork) {}
async execute(input: CreateOrderInput): Promise<void> {
try {
const order = Order.create(input);
await this.uow.orders.save(order);
await this.uow.stocks.reserve(input.items);
await this.uow.payments.createFor(order);
await this.uow.commit();
} catch (error) {
await this.uow.rollback();
throw error;
}
}
}
実際の実装では、ORMやDBクライアントのトランザクション機能を使います。
ORMとの関係
多くのORMには、Unit of Workに近い仕組みがあります。
| 機能 | Unit of Workとの関係 |
|---|---|
| Entity Manager | 変更対象を管理する |
| Transaction | commit/rollbackを実行する |
| Change Tracking | 変更されたオブジェクトを追跡する |
| Save Changes | まとめてDBへ反映する |
ORMがUnit of Workを内部で持っている場合、独自に厚い抽象を作りすぎる必要はありません。
向いている場面
| 向いている | 向いていない |
|---|---|
| 複数集約を一貫して更新する | 1テーブルの単純更新 |
| commit/rollbackを明示したい | ORMのトランザクションだけで十分 |
| Repositoryを複数使う | 参照だけの処理 |
| テストで作業単位を差し替えたい | 小さなスクリプト |
よくある誤解
すべての処理にUnit of Workが必要
参照だけの処理や単純な更新には不要です。複数の変更を一貫して扱いたい場面で使います。
Unit of Workに業務ルールを書く
Unit of Workは変更の確定を管理する役割です。業務判断はドメインやユースケースに置きます。
トランザクションを長く持てば安全
長いトランザクションはロックや性能問題を起こします。作業単位は短く保ちます。
チェックリスト
- 途中失敗時に戻すべき変更が複数あるか
- commit/rollbackの境界がユースケースと一致しているか
- トランザクションを長く持ちすぎていないか
- RepositoryとUnit of Workの責務が混ざっていないか
- ORMの機能で十分ではないか
まとめ
Unit of Workは、複数の変更を1つの作業単位として扱うための設計です。
Repositoryが保存対象を扱い、Unit of Workが変更の確定タイミングを扱うと考えると整理しやすくなります。
参考リソース
- Martin Fowler: Unit of Work
- Martin Fowler: Repository
- Microsoft: Implementing repository and unit of work patterns