ArquiteturaIntermediário7 min de leitura5 seções2002, Data aproximada

Unit of Work

Uma operação de negócio quase nunca é uma escrita só: salva o pedido, baixa o estoque, registra o log. O Unit of Work acumula todas essas mudanças durante a operação e as confirma juntas, numa transação única — ou tudo, ou nada. Ele tira de cada repositório a decisão de quando gravar e a centraliza num commit atômico, evitando o estado meio-aplicado.

Em 10 segundos

O Unit of Work acumula todas as mudanças de uma operação de negócio e as confirma juntas, num commit atômico: ou tudo, ou nada. Ele tira de cada repositório a decisão de quando gravar e a centraliza numa fronteira transacional única, evitando o estado meio-aplicado em que o pedido foi salvo mas o estoque não baixou. Na maioria dos ORMs modernos, ele já existe embutido — a session do Hibernate, o SaveChanges do EF.

Antes de ter nome

Acumular mudanças e confirmá-las juntas é a própria transação de banco levada para a camada de objetos, anterior ao nome do padrão.

  • #ddd
  • #transacao
  • #persistencia
  • #atomicidade
  • #repository
Analogia

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.

01Conceito

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.

02No mundo real

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.

03Cuidado

Onde isto dá errado

  1. 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.

  2. 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.

  3. 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.

04Implementação

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.
05Decisão

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