O carrinho antes do caixa
No supermercado, você não paga item por item enquanto anda pelos corredores — coloca tudo no carrinho e paga uma vez, no caixa. Se desistir no meio, devolve o carrinho e nada foi cobrado. O Unit of Work é o carrinho: as mudanças vão sendo acumuladas enquanto a operação acontece, e só no 'caixa' (o commit) tudo é cobrado de uma vez. Pagar item a item pelos corredores seria gravar a cada passo — e não teria como desistir depois de já ter pago metade.
Quem decide quando gravar
Sem Unit of Work, cada repositório grava quando é chamado, e a fronteira da transação fica implícita e espalhada. Com ele, os repositórios só registram o que mudou; a decisão de confirmar vira uma só, no fim da operação.
Essa centralização é o valor real: quem orquestra a operação de negócio passa a controlar a atomicidade dela inteira. 'Salvar pedido e baixar estoque' vira uma unidade — as duas gravações vivem ou morrem juntas.
O Unit of Work anda de mãos dadas com o Repository: o repositório é a coleção-como-se-estivesse-em-memória, e o Unit of Work é quem sincroniza essa 'memória' com o banco de uma vez. Por isso, na maioria dos ORMs, eles vêm juntos e escondidos — a session do Hibernate/JPA e o DbContext do Entity Framework são Units of Work que rastreiam objetos sujos e gravam tudo no flush/SaveChanges. Muitas vezes o padrão certo é usar o que o ORM já dá, não reimplementá-lo.
Há um limite que o Unit of Work não cruza: ele garante atomicidade dentro de um banco, via transação. Quando a operação precisa mudar o banco e publicar um evento, ou tocar dois bancos, a transação local não abrange o segundo destino — e aí o padrão certo passa a ser o Outbox (gravar o evento na mesma transação e despachá-lo depois) ou uma saga (passos com compensação). O Unit of Work resolve o 'tudo ou nada' de uma fronteira transacional; além dela, a consistência vira eventual.
Como isso aparece na prática
O pedido salvo sem baixar o estoque
Cenário
Uma finalização de compra salvava o pedido e, em seguida, baixava o estoque em chamadas separadas, cada uma com seu commit. Quando a baixa de estoque falhava, o pedido já estava gravado — vendido sem reservar o produto.
Como o padrão entra
As duas gravações passaram a acontecer sob um Unit of Work: registradas durante a operação e confirmadas num commit único. Uma falha na baixa de estoque reverte o pedido junto.
Trade-off: A transação passou a abranger as duas escritas, o que a torna um pouco mais longa e disputada. Em troca, o estado meio-aplicado — vendido sem estoque — deixou de ser possível.
O UoW reinventado por cima do ORM
Cenário
Um time construiu um Unit of Work manual completo, com rastreamento próprio de objetos sujos, sobre um ORM que já fazia exatamente isso na sua session. O código de tracking virou fonte de bugs sutis.
Como o padrão entra
O Unit of Work manual foi removido e a operação passou a usar a transação e o flush do próprio ORM, que já implementava o padrão de forma testada.
Trade-off: Perdeu-se o controle fino que o tracking manual dava em casos raros. Em troca, sumiram os bugs de sincronização e o código ficou muito menor, apoiado no que o ORM já garantia.
Onde isto dá errado
Cada repositório commitando sozinho
Se os repositórios gravam na hora, não há fronteira transacional para a operação de negócio: uma falha no meio deixa parte aplicada. O commit tem que ser único, no fim, controlado pelo Unit of Work.
Segurar a transação por tempo demais
Acumular mudanças e só confirmar lá na frente alonga a transação. Se, no meio, a operação chama um serviço externo lento, os locks ficam presos esperando — a transação deve conter só o trabalho de banco e ser curta.
Reinventar o que o ORM já faz
A session do Hibernate e o DbContext do EF já são Units of Work testados. Construir um do zero por cima deles costuma duplicar tracking e introduzir bugs de sincronização — vale usar o que o framework oferece.
Do commit por operação ao commit único
// SEM Unit of Work: cada repositorio grava sozinho, na hora.
await pedidos.salvar(pedido); // grava e commita
await estoque.baixar(itens); // grava e commita
// Se ESTA linha falhar, o pedido ja foi salvo. Estado meio-aplicado.
// COM Unit of Work: as mudancas sao ACUMULADAS e vao juntas.
const uow = new UnitOfWork();
uow.registrar(pedido); // so marca "sujo", nao grava
uow.registrar(estoqueBaixado); // idem
await uow.commit(); // UMA transacao: ou tudo, ou nada
// Falhou no meio? rollback. Nada foi aplicado. Sem meio-termo.
// O UoW rastreia o que foi criado, alterado e removido durante a
// operacao, e traduz isso em um unico commit atomico no fim.Quando usar × quando evitar
Use quando
3- Quando uma operação altera vários objetos que precisam ser salvos juntos.
- Para dar a quem orquestra o controle da fronteira transacional.
- Junto com repositórios, tirando deles a decisão de quando gravar.
Evite quando
2- Quando cada operação é uma escrita atômica única.
- Quando o ORM já oferece o mesmo agrupamento.
Testar o que ficou
3 armadilhas de Unit of Work viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
SaveChanges() do Entity FrameworkO EF rastreia as mudanças nos objetos e grava todas numa transação quando você chama SaveChanges — um Unit of Work embutido.a session do Hibernate / JPAA sessão acumula as alterações dos objetos gerenciados e as sincroniza com o banco no flush, dentro de uma transação.o commit() do repositórioUm repositório que junta várias operações e expõe um commit único está implementando um Unit of Work à mão.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Uma transação rastreia todas as mudanças.
uow.add(pedido); await uow.commit();O que ele cobra
1 salto a mais entre a chamada e o efeito.
- Rastrear o que mudou exige um mecanismo de tracking, com seu próprio custo e casos de borda
- Segurar as mudanças até o commit alonga a transação, aumentando a janela de contenção no banco
Não vale se: cada operação já é uma escrita atômica independente — sem várias mudanças que precisam ir juntas, o Unit of Work não tem o que agrupar.
Costuma ser confundido com
Onde isto aparece
Continue por aqui
- RepositoryUma coleção de objetos de domínio que finge não ter banco atrás. O repositório expõe operações na linguagem do negócio — 'pedidos pendentes há mais de uma hora' — e é o único lugar que sabe como isso vira consulta.
- Níveis de isolamentoTransações concorrentes se atrapalham de formas específicas e nomeadas. O nível de isolamento é a escolha de quais dessas anomalias o banco vai impedir — e cada degrau a mais custa concorrência, ou custa transações abortadas.
- Transactional OutboxSalvar no banco e publicar um evento são duas operações em dois sistemas — e não há transação que cubra as duas. O outbox transforma o evento numa linha da mesma transação, e deixa a publicação para depois.