Have you ever needed to save multiple records to the database as part of the same operation?
Imagine a simple library system. When a user borrows a book, the system needs to:
- Create the loan
- Update the book status to “loaned”
- Record an operation history
All of these actions represent a single business rule.
Now imagine that creating the history fails.
Does it make sense for the book to remain marked as loaned? Or for the loan to exist without a history record?
Probably not.
That’s where the Unit of Work Pattern comes in.
Instead of executing each operation independently, it groups them into a single unit of work:
await unitOfWork.execute(async () => {
const loan = await loanRepository.create(...);
await bookRepository.markAsLoaned(...);
await historyRepository.create(...);
return loan;
});
If any step throws an exception, all changes are automatically rolled back, keeping the data consistent.
But this pattern goes beyond database transactions.
Imagine your application uses Prisma ORM. Instead of the use case needing to know about prisma.$transaction() and infrastructure details, it simply expresses that a business operation should be executed atomically.
This brings several benefits:
- Cleaner code
- Use cases decoupled from infrastructure
- Simpler tests
- A more organized and maintainable architecture
For smaller projects, using prisma.$transaction() directly is often enough.
However, as the application grows, centralizing this behavior behind a Unit of Work can help keep the architecture consistent and reduce coupling between the domain and infrastructure layers.
