Skip to content

Domain-Driven Design (DDD)

O que é DDD

Definição: Domain-Driven Design (DDD)

Criado por Eric Evans em 2003 (livro Domain-Driven Design: Tackling Complexity in the Heart of Software), foi o primeiro grande padrão a colocar o domínio de negócio no centro do design de software — buscando torná-lo forte, expressivo e isolado de detalhes técnicos externos. Surgiu como resposta a aplicações monolíticas e desorganizadas, trazendo técnicas para dividir responsabilidades, delimitar contextos e alcançar simplicidade no entendimento do domínio junto com especialistas da área.

flowchart TD
    UI["UI"] --> AS["Application Services"]
    AS --> Repo["Repositories"]
    Repo --> Entities["Entities · Value Objects<br/>Domain Events · Aggregates"]
    Entities --> DS["Domain Services"]
    DS -.-> Repo

Definição: Linguagem onipresente (Ubiquitous Language)

Uma linguagem comum, compartilhada entre desenvolvedores e especialistas do negócio, usada tanto nas conversas sobre o domínio quanto no próprio código (nomes de classes, métodos, variáveis). Numa aplicação bancária, por exemplo, termos como "juros", "transferência" e "saldo" devem ser descritos da mesma forma pelos especialistas de negócio e pelos desenvolvedores — eliminando a tradução (e a perda de significado) entre "como o negócio fala" e "como o código está escrito".

O DDD foi a base para o surgimento de outros padrões arquiteturais de código, como a Arquitetura Hexagonal e a Clean Architecture, e é implicitamente utilizado quando se trabalha com microsserviços — no livro, Eric Evans orienta ter módulos isolados por responsabilidade (um módulo para pagamentos, outro para clientes, outro para pedidos), com relacionamentos fortes e bem definidos entre si. Esse raciocínio evoluiu depois para a Arquitetura de Microsserviços, na qual cada módulo se torna um pequeno projeto com ciclo de vida isolado dos demais.

Definição: DDD x Hexagonal/Clean Architecture — a diferença de foco

As três abordagens propõem isolar o domínio de detalhes técnicos, mas o DDD vai além: seu foco está na forte especificação e modelagem do domínio de negócio, buscando criar um modelo rico e alinhado à linguagem onipresente — não só isolar tecnicamente o domínio via portas/camadas, mas também estruturar como o domínio é pensado e nomeado.

Quando usar (e quando não usar) DDD

Definição: DDD deve ser usado com cautela

O esforço de desenvolvimento e a complexidade aumentam consideravelmente ao adotar DDD — vale sempre avaliar se o contexto realmente justifica esse investimento antes de aplicá-lo.

Vantagens: melhor compreensão do negócio e dos requisitos através da linguagem onipresente; modelo de domínio expressivo e detalhado; melhor comunicação entre especialistas de negócio e desenvolvedores; separação de responsabilidades bem delimitada e definida.

Desafios: curva de aprendizado (aplicar corretamente é difícil, principalmente em times sem experiência prévia com o padrão); modelos de domínio complexos podem ser difíceis de gerenciar; a necessidade de revisão contínua do modelo aumenta o custo de manutenção.

Quando usar: projetos grandes e complexos, com regras de negócio complicadas; projetos em que a comunicação clara das regras de negócio é determinante; projetos com múltiplas equipes que precisam colaborar e compartilhar uma linguagem comum; projetos de grande escala, onde a separação de responsabilidades é essencial.

Quando não usar: projetos com prazos curtos ou limitados (quick win) que não justificam o investimento inicial; projetos experimentais, onde o tempo é crítico e o foco é entrega rápida de funcionalidades para validação de hipóteses; projetos com regras de negócio simples e diretas, que não exigem uma modelagem complexa do domínio; equipes sem experiência prévia em DDD e sem tempo/recursos disponíveis para treinamento.

Blocos de construção do DDD

Entidades (Entities)

Definição: Entidade (DDD)

Objeto com identidade própria, geralmente representado por um identificador único — mesmo que dois objetos tenham todos os outros atributos iguais, são considerados diferentes se seus identificadores forem diferentes. Num sistema de e-commerce, Produto seria uma Entidade.

public class Product {
    private Long id;
    private String name;
    // outros atributos e métodos
}

Objetos de Valor (Value Objects)

Definição: Objeto de Valor (Value Object)

Objeto que descreve uma característica ou atributo de uma Entidade, mas não tem identidade própria — é definido inteiramente pelos seus valores, e pode ser reaproveitado por uma ou mais Entidades. Um Address (endereço) é um Objeto de Valor: a mesma instância pode ser atribuída a mais de um cliente (pessoas de uma mesma família que moram no mesmo lugar).

public class Address {
    private String streetName;
    private String zipCode;
    // outros atributos e métodos
}

Agregados (Aggregates) e Aggregate Root

Definição: Agregado (Aggregate)

Aglomerado de Entidades e Objetos de Valor tratados, conceitualmente, como uma única unidade de negócio identificável. Num sistema de e-commerce, um Order (pedido) seria um Agregado contendo o cliente (Entidade), os itens do pedido (Objeto de Valor) e seus próprios dados.

public class Order {
    private Long id;
    private Customer customer;
    private List<OrderLine> orderLines;
    // outros atributos e métodos
}

Definição: Aggregate Root

A Entidade principal de um Agregado — a única porta de entrada para qualquer interação com ele. Toda regra de negócio, validação e modificação de dados internos do Agregado deve passar exclusivamente por ela; nenhum código cliente deveria acessar diretamente um Objeto de Valor ou Entidade interna do Agregado por fora da raiz. No exemplo do pedido, Order seria o Aggregate Root: acessar diretamente um OrderLine e mudar seu valor, por fora de Order, comprometeria a consistência de todo o Agregado — é a raiz quem garante que as regras de negócio do conjunto inteiro sejam sempre respeitadas.

Repositórios (Repositories)

Definição: Repositório (DDD)

Abstração que permite a manipulação de Entidades, escondendo os detalhes de acesso aos dados de quem só precisa manipulá-las — mais próxima da linguagem do domínio do que um DAO tradicional.

public interface ProductRepository {
    Product findById(Long id);
    void save(Product product);
    void delete(Product product);
    // outros métodos
}

Serviços de Domínio (Domain Services)

Definição: Serviço de Domínio (Domain Service)

Classe que realiza operações relacionadas ao domínio que não se encaixam naturalmente em nenhuma Entidade específica — normalmente operando sobre um Agregado ou conjunto de Entidades.

public interface OrderService {
    void placeOrder(Order order);
    void cancelOrder(Order order);
    // outros métodos
}

Fábrica (DDD)

Definição: Fábrica (DDD)

Responsável por criar instâncias complexas de objetos, encapsulando a lógica de criação — comumente usada para criar Agregados, assegurando a consistência entre as Entidades e Objetos de Valor que os compõem. Diferente dos padrões de criação do catálogo GoF (Factory Method, Abstract Factory, Builder — ver Padrões Arquiteturais), a Fábrica no DDD tem um objetivo mais específico: garantir que um Agregado nasça sempre num estado internamente consistente, escondendo do cliente a complexidade de montar suas Entidades e Objetos de Valor internos. Pode ser implementada diretamente na Aggregate Root ou externalizada numa classe própria.

public class OrderFactory {
    public static Order createOrder(Customer customer, List<OrderLine> orderLines) {
        // lógica de criação do pedido
    }
}

Serviços de Aplicação (Application Services)

Definição: Serviço de Aplicação (Application Service)

Orquestra o fluxo de trabalho da aplicação, atuando como controlador do caso de uso — invocando os objetos do domínio (Agregados, Serviços de Domínio e Fábricas) e lidando com infraestrutura (repositórios, filas, transações).

@Service
public class OrderApplicationService {
    private final OrderRepository orderRepository;

    public void placeOrder(Customer customer, List<OrderLine> orderLines) {
        Order order = OrderFactory.createOrder(customer, orderLines);
        orderRepository.save(order);
        // enviar e-mail, emitir nota, iniciar pagamento etc.
    }
}

Derivando um modelo a partir dos requisitos

Os blocos de construção acima descrevem o que cada peça do DDD é — mas, na prática, o maior desafio costuma ser chegar a esse modelo a partir de requisitos brutos trazidos por um stakeholder, geralmente escritos em prosa, sem nenhuma estrutura. Um processo prático para isso:

Definição: Passo a passo para derivar um modelo com DDD

  1. Identificar Entidades candidatas nos requisitos — geralmente os substantivos que se repetem com frequência e carregam relevância para o domínio (num sistema de venda de ingressos: Cliente, Evento, Sessão, Ingresso, Sala, Cadeira, Administrador, Pagamento).
  2. Identificar relacionamentos entre pares de Entidades candidatas, um requisito de cada vez — sem se preocupar ainda com o modelo completo.
  3. Unificar o modelo: juntar as Entidades repetidas nos relacionamentos unitários numa única representação, e validar o resultado com os especialistas de negócio antes de seguir.
  4. Classificar Entidade x Objeto de Valor, aplicando o teste: "isto precisa de um identificador único, ou pode ser reaproveitado por múltiplas instâncias de outros objetos?". Se a resposta for identificador único, é uma Entidade; se for reaproveitável (o mesmo valor podendo pertencer a mais de um "dono"), é um Objeto de Valor.
  5. Definir os Agregados, delimitando responsabilidades e fronteiras de interação — decidindo qual Entidade de cada grupo será a Aggregate Root.

Um exemplo do passo 2, aplicado a um requisito isolado ("o sistema deve permitir o cadastro e gestão de eventos por um administrador"):

flowchart LR
    Administrador -->|Cadastra e faz gestão de| Evento

Repetindo esse processo para cada requisito, e depois unificando as Entidades repetidas entre os relacionamentos unitários, chega-se a um modelo único como o seguinte (para um sistema de gestão de eventos e venda de ingressos):

flowchart TD
    Cliente -->|Possui um ou mais| Pagamento
    Pagamento -->|Possui ingressos de| Sessao[Sessão]
    Evento -->|Tem uma ou mais| Sessao
    Administrador -->|Cadastra e faz gestão de| Evento
    Administrador -->|Cadastra e faz gestão de| TipoIngresso[Tipo de Ingresso]
    Administrador -->|Cadastra e faz gestão de| Sala
    Sessao -->|Tem uma ou mais instância de| TipoIngresso
    Sala -->|Tem uma ou mais| Cadeira
    Sessao -->|Tem uma| Sala

Definição: O teste Entidade x Objeto de Valor, aplicado

No exemplo do sistema de ingressos: Cliente, Pagamento, Ingresso, Administrador, Evento, Sessão e Sala são Entidades — cada instância precisa de identidade própria (não deveria existir duas instâncias do mesmo Cliente, por exemplo, identificadas pelo mesmo CPF). Já Cadeira não tem identificador único: a mesma combinação de valores ("fileira A", "posição 5") pode ser reaproveitada em várias Salas diferentes — por isso é modelada como Objeto de Valor.

Com as Entidades e Objetos de Valor classificados, o passo final é agrupá-los em Agregados, delimitando qual Entidade de cada grupo assume o papel de Aggregate Root:

flowchart TD
    subgraph AgregadoCliente["Agregado Cliente"]
        Cliente
    end
    subgraph AgregadoPagamentos["Agregado Pagamentos"]
        Pagamento
    end
    subgraph AgregadoEventos["Agregado Eventos"]
        Evento -->|Tem uma ou mais| Sessao[Sessão]
    end
    subgraph AgregadoIngressos["Agregado Ingressos"]
        TipoIngresso[Tipo de Ingresso]
    end
    subgraph AgregadoSalas["Agregado Salas"]
        Sala -->|Tem uma ou mais| Cadeira
    end
    Cliente -->|Possui um ou mais| Pagamento
    Pagamento -->|Possui ingressos de| Sessao
    Administrador -->|Cadastra e faz gestão de| Evento
    Administrador -->|Cadastra e faz gestão de| TipoIngresso
    Administrador -->|Cadastra e faz gestão de| Sala
    Sessao -->|Tem uma ou mais instância de| TipoIngresso
    Sessao -->|Tem uma| Sala

Definição: Por que agrupar Evento e Sessão no mesmo Agregado

A decisão de colocar Evento e Sessão no mesmo Agregado (com Evento como Aggregate Root) vem do entendimento de negócio: Sessão é parte da composição de dados de Evento, e nenhuma outra Entidade deveria acessar ou manipular diretamente os dados de uma Sessão sem ter conhecimento de qual Evento está associado — imagine o impacto de deletar uma sessão que já teve ingressos vendidos sem esse controle centralizado na raiz.

Definição: Esse processo é iterativo, não uma etapa única

O modelo obtido pela análise inicial dos requisitos deve ser constantemente validado e refinado em conjunto com os especialistas de negócio — o alinhamento contínuo é o que garante que a representação do sistema reflita corretamente as regras e expectativas do domínio real, não só a interpretação inicial de quem modelou.

Combinando DDD com Clean Architecture

Suponha um sistema de tarefas em que cada tarefa precisa de uma lista de passos (ex.: "Fazer o trabalho da escola" → "Definir o tema", "Estudar sobre o tema", "Redigir o texto", "Enviar o trabalho") e deve ser sinalizada como concluída só quando todos os passos estiverem finalizados.

classDiagram
    class TaskFactory {
        +create(taskName: String, steps: List~String~) bool
    }
    class Task {
        -name: String
        -startDate: TaskStartDate
        -steps: List~Step~
        +isValidStartDate() bool
        +finishStep(desc: String)
        +isTaskDone() bool
    }
    class Step {
        -description: String
        -finished: bool
        +finish()
    }
    class TaskStartDate {
        -startDate: LocalDate
        +isValidStartDate() bool
    }
    TaskFactory ..> Task : Uses
    Task ..> Step : Uses
    Task ..> TaskStartDate : Uses
  • Task é o Aggregate Root — toda interação com o agregado ocorre exclusivamente por ela, o que impede que clientes manipulem diretamente os passos ou a data, comprometendo a consistência do conjunto.
  • Step e TaskStartDate são Objetos de Valor: não têm identidade própria e são definidos apenas por seus valores, cada um encapsulando suas próprias validações e regras de negócio.
  • TaskFactory é a Fábrica, responsável por criar o Agregado de forma simples, ocultando dos clientes as complexidades de criar e associar seus objetos internos.
public record Task(String name, TaskStartDate taskStartDate, List<Step> steps) {

    public boolean isValidStartDate() {
        return taskStartDate.isValidStartDate();
    }

    public void finishStep(String stepDescription) {
        this.steps.stream()
            .filter(step -> stepDescription.equals(step.getDescription()))
            .forEach(step -> step.setFinished(true));
    }

    public boolean isTaskDone() {
        return this.steps.stream().allMatch(Step::isFinished);
    }
}
public class TaskFactory {
    public Task create(String taskName, LocalDate startDate, List<String> stepsDescriptions) {
        List<Step> steps = stepsDescriptions.stream()
            .map(Step::new)
            .toList();
        TaskStartDate taskStartDate = new TaskStartDate(startDate);
        return new Task(taskName, taskStartDate, steps);
    }
}
@Test
public void given_Task_whenStartDateGreaterThanActualDate_should_return_success() {
    TaskFactory taskFactory = new TaskFactory();
    Task task = taskFactory.create("Task in the future", LocalDate.parse("2500-01-01"), List.of());
    assertTrue(task.isValidStartDate());
}

O teste unitário lida somente com TaskFactory e Task, sem precisar conhecer ou manipular os detalhes internos do domínio — os demais Objetos de Valor e Entidades permanecem encapsulados. Essa é a mesma ideia das Entities da Clean Architecture: a divisão de responsabilidades entre as duas abordagens acontece com os Interactors (casos de uso) utilizando as Fábricas do DDD para realizar a Dança das Entidades.

DDD tático com Portas e Adaptadores na prática

Os blocos do DDD (entidades, objetos de valor, agregados) ganham sentido quando combinados com a Arquitetura Hexagonal. Esta seção usa um aplicativo de entrega de pedidos como fio condutor e reúne as práticas que mais aparecem em projetos reais.

flowchart LR
    subgraph Entrada["Adaptadores de entrada"]
        HTTP[Controller HTTP]
        FILA[Consumidor de fila]
        CSV[Job em lote CSV]
    end
    subgraph Nucleo["Núcleo"]
        UC[Casos de uso<br/>portas de entrada]
        DOM[Domínio: Pedido, Money,<br/>políticas, eventos]
    end
    subgraph Saida["Adaptadores de saída"]
        BD[(Banco)]
        PAG[Gateway de pagamento]
        MSG[Publicador de eventos]
    end
    HTTP --> UC
    FILA --> UC
    CSV --> UC
    UC --> DOM
    UC -. portas de saída .-> BD
    UC -. portas de saída .-> PAG
    UC -. portas de saída .-> MSG

Núcleo rico: invariantes dentro do agregado

Um pedido vazio, com item de quantidade negativa ou que "pula" de rascunho para entregue não deveria existir. O modelo anêmico (uma classe só com getters e setters mais um serviço gigante) obriga todo mundo a lembrar das validações, duplica cálculos e gera testes enormes (modelo anêmico). O caminho: o agregado se protege.

  • A raiz do agregado (Pedido) é a única porta de entrada para os itens e para o endereço de entrega; nunca expõe objetos internos para modificação externa.
  • O status é uma máquina de estados: o enum e os métodos do pedido só permitem as transições válidas (pagar(), enviar(), entregar()); tentar enviar um pedido ainda em preparo é bloqueado e registra o motivo.
  • Cada item responde pelo próprio subtotal; o pedido soma os subtotais.

Dinheiro e unidades como objetos de valor

Nunca use double para dinheiro

double é ponto flutuante binário: 0.1 + 0.2 dá 0.30000000000000004, e um pedido de R$ 49,99 pode virar 49,9900001, quebrando arredondamentos e conciliações. Use BigDecimal (ou inteiros em centavos) dentro de um objeto de valor.

public record Money(BigDecimal amount) {
    public Money {
        Objects.requireNonNull(amount);
        amount = amount.setScale(2, RoundingMode.HALF_UP);          // arredondamento técnico, único
        if (amount.signum() < 0) throw new IllegalArgumentException("valor negativo");
    }
    public static Money zero() { return new Money(BigDecimal.ZERO); }
    public Money add(Money o)        { return new Money(amount.add(o.amount)); }
    public Money multiply(BigDecimal f) { return new Money(amount.multiply(f)); }
    public boolean isGreaterOrEqual(Money o) { return amount.compareTo(o.amount) >= 0; }
}

public record Distance(BigDecimal value, DistanceUnit unit) { /* valida não nulo e não negativo */ }

Duas distinções importantes: arredondamento técnico (casas decimais do valor) fica no Money; arredondamento de negócio ("imposto por item, sempre para cima") é regra de negócio e fica na política de cálculo, não no tipo. Converta para primitivos apenas na borda (portas de entrada e saída).

Políticas plugáveis para regras voláteis

Taxas de entrega, promoções e cashback mudam toda semana. Em vez de um if gigante no controller, cada regra é uma política (padrão Strategy, um serviço de domínio) que recebe dados já normalizados (Money, Distance) e devolve um resultado com motivo e valor (rastreabilidade: o atendimento vê se o desconto veio da "janela de chuva" ou do compromisso de pontualidade).

public interface DeliveryFeePolicy { Money calculate(Money subtotal, Distance distance); }

public interface PromotionPolicy {
    Optional<Discount> apply(Order order);   // Discount(motivo, valor)
}

Um motor de promoções percorre as políticas ativas (ligadas e desligadas por um catálogo ou feature flag, sem deploy) e combina os resultados conforme regras de acúmulo. Nem toda regra merece virar política: só as que mudam por decisão do negócio. Regras estáveis ficam na entidade.

Falha de negócio é resultado, não exceção

Cupom expirado, pagamento recusado ou entregador indisponível não são bugs: são resultados esperados. Modele-os como tipos explícitos que o chamador é obrigado a tratar (um sealed interface em Java):

public sealed interface DiscountResult permits DiscountApplied, DiscountRejected {}
public record DiscountApplied(Money value) implements DiscountResult {}
public record DiscountRejected(String reason) implements DiscountResult {}   // "cupom expirado"

if (order.applyPromotionCode(code) instanceof DiscountRejected r) { presenter.show(r.reason()); }

Reserve exceções para situações realmente excepcionais (invariante violada, falha técnica). Quando é preciso devolver vários erros de uma vez (nome vazio e quantidade negativa), use o Notification Pattern: acumule as falhas numa coleção em vez de lançar a primeira (Tratamento de exceções).

Validação em três níveis

Nível O que valida Onde fica
Formato CPF, e-mail, código de cupom bem formado, quantidade positiva Objetos de valor e comandos de entrada
Regra de negócio contextual Já há cupom aplicado? o desconto passa do limite? Entidade/agregado (depende do estado)
Orquestração A ordem dos passos do caso de uso Caso de uso (serviço de aplicação)

Portas de saída e de entrada

  • Portas de saída na linguagem do domínio. O domínio declara o que precisa (salvar(Pedido), buscarPorStatus(Status)), e não herda um CRUD genérico (findAll, deleteById) "só para reaproveitar". O domínio nunca vê Connection, EntityManager ou HTTP; os adaptadores (JDBC, JPA, REST) implementam a interface (Repositórios).
  • Portas de entrada explícitas: cada caso de uso (CriarPedido, AtribuirEntregador) é uma porta; recebe um comando validado e devolve um resultado. O controller fica fino: converte a requisição em comando, chama o caso de uso e converte o resultado em resposta.
  • Vários canais, um caso de uso: HTTP, fila de mensagens, GraphQL e job em lote (CSV) chamam o mesmo caso de uso. Para não duplicar a formatação da resposta, o caso de uso fala com um presenter (porta de saída de apresentação) que cada canal implementa.

Pagamentos, reembolsos e idempotência

  • Nomeie os motivos de falha do gateway (saldo, cartão recusado, tempo esgotado, Pix pendente) como tipos do domínio (PaymentOutcome). O controller deixa de "adivinhar" por códigos de erro; o domínio decide: liberar o pedido, tentar de novo, marcar reembolso pendente.
  • Idempotência: o cliente pode repetir a requisição (rede instável, duplo clique). Envie ao gateway uma chave de idempotência única por tentativa de cobrança (a maioria dos provedores aceita no cabeçalho), para que repetir não cobre duas vezes. O mesmo vale para consumidores de mensagens (Mensageria confiável).
  • Atribuição de entregadores (matching): a decisão de quem recebe o pedido é do domínio (política de despacho), com eventos para rastreabilidade; a consulta ao banco e a gravação do estado do entregador ficam atrás de portas.

Testes guiados pelo domínio

Uma pirâmide que protege cada camada: testes de domínio (rápidos, sem infraestrutura, cobrindo invariantes e políticas), testes de casos de uso com dublês das portas, testes de adaptadores e poucos ponta a ponta (o fluxo vital, como checkout). Dicas:

  • Prefira fakes (implementação em memória da porta) a mocks "que mentem": mantenha o fake junto dos testes, e o compilador obriga a atualizá-lo quando a porta muda.
  • Testes de contrato reutilizáveis: uma suíte única que toda implementação da porta (JDBC, em memória) precisa passar.
  • Um teste da política de frete teria pego um erro de arredondamento no CI antes do deploy (Qualidade).

Evoluir por contextos, não por tecnologia

Ao ver o monólito lento, a tentação é "quebrar em microsserviços" copiando pastas para outro repositório, com banco compartilhado e chamadas síncronas: o pior dos mundos. O caminho seguro:

  1. Modularizar dentro do monólito por contexto delimitado (Pedidos, Pagamentos, Logística), nunca por camada técnica.
  2. Explicitar contratos de integração entre contextos (portas).
  3. Ensaiar a extração com adaptadores internos (a mesma porta, implementação local).
  4. Proteger o domínio com uma Camada Anticorrupção (ACL) quando o outro lado é legado ou um parceiro com modelo diferente.
  5. Extrair gradualmente, trocando só o adaptador (do JDBC para HTTP/mensageria); o restante do domínio não muda (Microsserviços, Strangler Fig).

Preocupações transversais sem poluir o domínio

  • Observabilidade: logs soltos dentro do domínio o deixam acoplado à ferramenta e não reconstroem a jornada. Gere um Correlation/Trace ID na entrada, propague-o pelos casos de uso e adaptadores, e instrumente por decoradores das portas (um CheckoutComLogging que envolve o caso de uso) e por eventos e métricas de negócio (Observability).
  • Transactional Outbox: gravar o pedido e publicar o evento de forma atômica: o evento vai para uma tabela de outbox na mesma transação, e um relay o publica depois (entrega at-least-once, então o consumidor deve ser idempotente) (Mensageria confiável).
  • Segurança como regra de domínio: o ataque clássico é trocar o ID na URL (/pedidos/123/cancelar → 124, 125...), o chamado IDOR/BOLA (Broken Object Level Authorization). A identidade do usuário entra no domínio por uma porta, e o caso de uso/entidade decide se ele é o dono do pedido; se não for, lança uma exceção de autorização que o controller converte em HTTP 403 (Segurança).
  • Resiliência com "adaptadores-escudo": a falha de um serviço externo raramente é total, é lentidão, que esgota threads. O adaptador aplica timeouts, retries com backoff e jitter, circuit breaker, bulkhead (um compartimento de threads reservado para cada integração, para que a lenta não derrube as outras) e fallbacks, mantendo o domínio ignorante disso (Engenharia do caos).
  • Transações distribuídas (Sagas): em vez de "tudo ou nada", cada passo tem uma compensação (se a cobrança foi feita, o estorno; se reservou entregador, libera a reserva). O estado da saga é persistido para sobreviver a reinícios (Saga).

Checklist rápido

  • [ ] Regras do pedido dentro do agregado, com transições de estado controladas.
  • [ ] Dinheiro e unidades como objetos de valor (BigDecimal, nunca double).
  • [ ] Regras voláteis como políticas plugáveis; falhas de negócio como resultados.
  • [ ] Portas na linguagem do domínio; controller fino; vários canais chamam o mesmo caso de uso.
  • [ ] Idempotência em pagamentos e consumidores; outbox para eventos.
  • [ ] Autorização no domínio; adaptadores resilientes; observabilidade por ID de correlação.
  • [ ] Evolução por contextos delimitados, com ACL, e não por camadas técnicas.