Pular para o conteúdo

apps/architecture/ddd

043 · Arquiteturas de Alto Nível (Macro-Architecture) · ≈ 6 min de estudo

Domain-Driven Design (DDD)

DDD tático coloca a regra de negócio dentro do modelo: Money é um Value Object imutável em centavos, Account é o Aggregate Root que garante saldo e limite diário e registra Domain Events a cada mudança. Use no subdomínio core, onde a regra é rica; em CRUD simples, o tático só adiciona cerimônia.

passos
6
arquivos
8
testes
0
tecnologias
4
Infraestrutura realTypeScriptBunPostgreSQLDocker
Baixar cartão

Cenário

Banco digital, contexto de conta-corrente. Alice transfere R$ 5.000,00 para Bruno. A conta de origem só debita se houver saldo e se o total transferido no dia couber no limite; um débito recusado não deixa rastro. O crédito em Bruno não entra na mesma transação: ele reage ao fato account.money.debited, gravado junto com o débito.

Planta

Sequência
8/8
BEGIN, SELECT origem FOR UPDATE1debit(Money, transferId, destino, hoje)2invariantes ok, evento account.money.debited3UPDATE accounts + INSERT account_events, COMMIT4BEGIN, debito pendente FOR UPDATE SKIP LOCKED5credit(Money, transferId)6evento account.money.credited7UPDATE accounts + INSERT evento + processed_at, COMMIT8UseCaseTransferRequestAccount origemPostgreSQLUseCaseCreditApplyAccount destino
8

8 passos — reproduza para seguir o fluxo

Como funciona

6 passos

Contextos delimitados, linguagem ubíqua.

Esconde cada passo: lembre antes de tocar para revelar.

  1. 01

    Money aceita só centavos inteiros não negativos, nunca mistura moedas e devolve nova instância a cada operação

  2. 02

    Account.debit checa as invariantes antes de mudar o estado — saldo, limite do dia (zera sozinho na virada do dia), valor zero, transferência para si mesma

  3. 03

    Cada mudança registra um Domain Event; eventsPull() os entrega uma única vez ao serviço de aplicação

  4. 04

    UseCaseTransferRequest altera um único aggregate por transação: trava a origem com FOR UPDATE, debita, salva e grava os eventos na mesma transação

  5. 05

    UseCaseCreditApply pega um débito pendente com FOR UPDATE SKIP LOCKED, credita o destino e marca o evento como processado — tudo numa transação, então cada débito é creditado exatamente uma vez

  6. 06

    UNIQUE (transfer_id, type) em account_events recusa o mesmo transferId duas vezes: repetir a requisição não debita de novo

Trade-offs

O que se ganha, o que se paga

4 vantagenscada ganho tem um preço4 custos

Vantagens

  • Regra no aggregate: nenhum chamador consegue violar saldo ou limite

  • Uma transação por aggregate: sem lock cruzado entre contas

  • Evento gravado com o dado: o fato nunca se perde

  • Money elimina float e mistura de moedas

Custos

  • Mais tipos e camadas que um CRUD

  • Consistência eventual — entre débito e crédito o dinheiro está em trânsito

  • O handler de crédito precisa rodar e ser monitorado

  • Conversão entre linha e aggregate no repositório

apps/architecture/ddd

8 arquivos

src/domain/

  • money.tsDomínio
  • account.tsDomínio
  • event_account.tsDomínio
  • repository_account.tsDomínio

src/application/

  • use_case_transfer.tsAplicação

src/infra/

  • repository_account_postgres.tsInfraestrutura
  • config_ddd.tsInfraestrutura

sql/

  • 01_schema.sqlschemaInfraestrutura

Executar · com Docker

  1. ./docker.sh up# sobe PostgreSQL
  2. cp .env.example .env# variáveis de ambiente
  3. bun install# dependências
  4. bun run demo# roda o cenário
  5. bun run test# integração contra o serviço real
Requisitos
BunDocker
Sobe junto
PostgreSQL
Esc

↑ ↓ navegarEnter abrir191 resultados