Skip to content

Domain-Driven Design (DDD) ​

🎯 O que é Domain-Driven Design? ​

Domain-Driven Design (DDD) é uma abordagem filosófica e metodológica para o desenvolvimento de software que coloca o domínio de negócio no centro absoluto de todas as decisões de design e arquitetura. Criado por Eric Evans em 2003 através do livro seminal "Domain-Driven Design: Tackling Complexity in the Heart of Software", o DDD representa uma mudança paradigmática na forma como pensamos sobre desenvolvimento de software.

Definição Profunda ​

DDD não é apenas uma técnica de programação, mas uma estratégia de modelagem que busca:

  1. Capturar a Essência do Negócio: O software deve ser um reflexo fiel da realidade do negócio, não uma representação técnica distorcida
  2. Criar Linguagem Compartilhada: Desenvolvedores e especialistas de domínio devem falar a mesma língua
  3. Gerenciar Complexidade: Dividir sistemas complexos em partes menores e mais gerenciáveis
  4. Evoluir com o Negócio: O software deve evoluir naturalmente conforme o domínio evolui

Princípios Fundamentais ​

1. Domínio como Centro de Gravidade

  • Toda decisão técnica deve ser subordinada às necessidades do domínio
  • A arquitetura serve o domínio, não o contrário
  • O código deve "falar" a linguagem do negócio

2. Modelagem Colaborativa

  • Desenvolvedores e especialistas de domínio trabalham juntos
  • O conhecimento do domínio é compartilhado e refinado continuamente
  • A modelagem é um processo iterativo e colaborativo

3. Linguagem Ubíqua

  • Uma linguagem única e consistente usada por todos
  • Elimina a tradução entre linguagem técnica e de negócio
  • Reflete-se diretamente no código e documentação

4. Contextos Delimitados

  • Divide o sistema em áreas de responsabilidade bem definidas
  • Cada contexto tem suas próprias regras e linguagem
  • Reduz a complexidade através da separação de conceitos

DDD vs Outras Abordagens ​

AspectoDDDAnêmicoCRUDSOA
FocoDomínioDadosOperaçõesServiços
LógicaEntidadesServiçosControllersServices
LinguagemUbíquaTécnicaTécnicaTécnica
ComplexidadeGerenciadaEspalhadaSimplesDistribuída
ManutenibilidadeAltaBaixaMédiaMédia

🚀 Motivo da Existência ​

O DDD surgiu como resposta a problemas crônicos que afligem o desenvolvimento de software há décadas. Eric Evans identificou que a maioria dos projetos falha não por problemas técnicos, mas por problemas de comunicação e modelagem.

Problemas Fundamentais que o DDD Resolve ​

1. Crise de Comunicação entre Mundos Diferentes ​

Problema Real:

  • Desenvolvedores falam em "tabelas", "APIs", "endpoints"
  • Especialistas de domínio falam em "pedidos", "clientes", "estoque"
  • Resultado: Tradução constante que gera erros e mal-entendidos

Como o DDD Resolve:

  • Cria uma linguagem ubíqua que ambos os lados usam
  • Elimina a necessidade de tradução
  • O código fala diretamente a linguagem do negócio
csharp
// ❌ Antes - Tradução constante
public class OrderController
{
    public void ProcessOrder(OrderDTO orderData) // Tradução: Order -> OrderDTO
    {
        var orderEntity = MapToEntity(orderData); // Tradução: DTO -> Entity
        _orderService.Save(orderEntity); // Tradução: Save -> Process
    }
}

// ✅ Depois - Linguagem ubíqua
public class OrderProcessor
{
    public void FulfillOrder(Order order) // Linguagem direta do domínio
    {
        order.Confirm(); // Método que faz sentido no domínio
        _orderRepository.Save(order);
    }
}

2. Complexidade Técnica Ocultando Complexidade Real ​

Problema Real:

  • Desenvolvedores focam em "como implementar" em vez de "o que implementar"
  • A complexidade técnica mascara a complexidade do domínio
  • Resultado: Soluções técnicas elegantes para problemas errados

Como o DDD Resolve:

  • Força o foco na complexidade do domínio
  • A complexidade técnica serve ao domínio, não o contrário
  • Separa claramente o que é regra de negócio do que é detalhe técnico

3. Modelos Anêmicos e Lógica Espalhada ​

Problema Real:

  • Classes são apenas containers de dados (anêmicas)
  • Lógica de negócio fica espalhada em serviços, controllers, etc.
  • Resultado: Violação do princípio de responsabilidade única

Como o DDD Resolve:

  • Encapsula lógica de negócio nas entidades de domínio
  • Cada entidade é responsável por suas próprias regras
  • Elimina a necessidade de serviços que "orquestram" dados
csharp
// ❌ Modelo Anêmico
public class Order
{
    public Guid Id { get; set; }
    public string Status { get; set; }
    public List<OrderItem> Items { get; set; }
}

public class OrderService
{
    public void AddItem(Order order, Product product, int quantity)
    {
        // Lógica de negócio espalhada no serviço
        if (order.Status != "Draft")
            throw new Exception("Cannot add items to confirmed order");
        
        var item = new OrderItem { ProductId = product.Id, Quantity = quantity };
        order.Items.Add(item);
    }
}

// ✅ Modelo Rico
public class Order : AggregateRoot<OrderId>
{
    public OrderId Id { get; private set; }
    public OrderStatus Status { get; private set; }
    public List<OrderLine> Lines { get; private set; }
    
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        // Lógica de negócio encapsulada na entidade
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        var line = new OrderLine(productId, quantity, unitPrice);
        Lines.Add(line);
        RecalculateTotal();
    }
}

4. Mudanças de Requisitos como Pesadelo ​

Problema Real:

  • Mudanças de negócio requerem mudanças em múltiplas camadas
  • Código fortemente acoplado dificulta evolução
  • Resultado: Custo alto de manutenção e evolução

Como o DDD Resolve:

  • Isola mudanças em contextos específicos
  • Mudanças de domínio afetam apenas o domínio
  • Facilita refatoração e evolução

5. Escalabilidade e Manutenibilidade Comprometidas ​

Problema Real:

  • Código monolítico e difícil de entender
  • Dependências circulares e acoplamento alto
  • Resultado: Equipes grandes não conseguem trabalhar eficientemente

Como o DDD Resolve:

  • Divide o sistema em contextos bem definidos
  • Cada contexto pode evoluir independentemente
  • Facilita trabalho em paralelo de múltiplas equipes

Impacto Real dos Problemas ​

ProblemaImpactoCusto
Comunicação Ruim40% dos bugs$50k-500k por projeto
Modelos Anêmicos60% mais tempo de desenvolvimento$100k-1M por projeto
Mudanças Difíceis80% do tempo em manutenção$200k-2M por projeto
Complexidade Técnica50% de features desnecessárias$75k-750k por projeto

Por que DDD é a Solução ​

  1. Reduz Custo de Comunicação: Linguagem ubíqua elimina traduções
  2. Aumenta Velocidade: Foco no que importa (domínio)
  3. Melhora Qualidade: Lógica centralizada e testável
  4. Facilita Mudanças: Isolamento de responsabilidades
  5. Permite Escalabilidade: Contextos independentes

🏗️ Conceitos Principais do DDD ​

1. Ubiquitous Language (Linguagem Ubíqua) ​

A Linguagem Ubíqua é o coração do DDD e representa uma revolução na comunicação entre desenvolvedores e especialistas de domínio. Não é apenas um vocabulário compartilhado, mas uma transformação cultural na forma como equipes se comunicam.

Definição Profunda ​

A Linguagem Ubíqua é um vocabulário estruturado e consistente que:

  • É usado por todos os membros da equipe (desenvolvedores, analistas, especialistas de domínio)
  • Reflete-se diretamente no código (nomes de classes, métodos, propriedades)
  • Evolui com o domínio através de discussões e refinamentos contínuos
  • Elimina ambiguidades e mal-entendidos

Por que é Crítica? ​

Problema Real:

Especialista de Domínio: "Quando um cliente faz um pedido, precisamos reservar o estoque"
Desenvolvedor: "Entendi, vou criar um endpoint POST /api/orders que chama o service.ReserveInventory()"

Resultado: Tradução constante que gera erros e perda de contexto.

Solução com Linguagem Ubíqua:

Ambos: "Quando um cliente faz um pedido, precisamos reservar o estoque"
Código: order.ReserveStock() // Linguagem direta do domínio

Implementação Prática ​

1. Nomenclatura Consistente

csharp
// ❌ Inconsistente - Mistura linguagens
public class OrderController
{
    public void ProcessOrder(OrderDTO orderData) // "Process" é técnico
    {
        var orderEntity = MapToEntity(orderData); // "Entity" é técnico
        _orderService.Save(orderEntity); // "Save" é técnico
    }
}

// ✅ Consistente - Linguagem ubíqua
public class OrderProcessor
{
    public void FulfillOrder(Order order) // "Fulfill" é do domínio
    {
        order.Confirm(); // "Confirm" é do domínio
        _orderRepository.Persist(order); // "Persist" é do domínio
    }
}

2. Documentação Alinhada

csharp
/// <summary>
/// Confirma um pedido, reservando estoque e gerando fatura
/// </summary>
/// <remarks>
/// Um pedido confirmado não pode ser modificado e deve ter pelo menos um item.
/// A confirmação dispara eventos de domínio para reserva de estoque e geração de fatura.
/// </remarks>
public void Confirm()
{
    if (Status != OrderStatus.Draft)
        throw new InvalidOperationException("Apenas pedidos em rascunho podem ser confirmados");
    
    if (Lines.Count == 0)
        throw new InvalidOperationException("Pedido deve ter pelo menos um item para ser confirmado");
    
    Status = OrderStatus.Confirmed;
    ConfirmedAt = DateTime.UtcNow;
    
    AddDomainEvent(new OrderConfirmedEvent(Id));
}

3. Glossário de Domínio

csharp
/// <summary>
/// Glossário de Domínio - Gestão de Pedidos
/// 
/// Pedido (Order): Representa uma solicitação de compra feita por um cliente
/// - Rascunho (Draft): Pedido em criação, pode ser modificado
/// - Confirmado (Confirmed): Pedido validado, estoque reservado
/// - Cancelado (Cancelled): Pedido cancelado pelo cliente ou sistema
/// - Enviado (Shipped): Pedido enviado para o cliente
/// - Entregue (Delivered): Pedido entregue ao cliente
/// 
/// Item de Pedido (OrderLine): Produto específico em um pedido
/// - Quantidade: Número de unidades do produto
/// - Preço Unitário: Preço por unidade no momento do pedido
/// - Total: Quantidade × Preço Unitário
/// 
/// Confirmação: Processo de validar e finalizar um pedido
/// - Reserva estoque automaticamente
/// - Gera fatura
/// - Dispara eventos para outros contextos
/// </summary>

4. Eventos de Domínio com Linguagem Clara

csharp
// ❌ Eventos técnicos
public class OrderSavedEvent : DomainEvent
{
    public Guid OrderId { get; set; }
}

// ✅ Eventos com linguagem ubíqua
public class OrderConfirmedEvent : DomainEvent
{
    public OrderId OrderId { get; }
    public DateTime ConfirmedAt { get; }
    
    public OrderConfirmedEvent(OrderId orderId)
    {
        OrderId = orderId;
        ConfirmedAt = DateTime.UtcNow;
    }
}

Benefícios da Linguagem Ubíqua ​

  1. Reduz Erros de Comunicação: 60% menos bugs relacionados a mal-entendidos
  2. Acelera Desenvolvimento: Elimina tempo gasto em traduções
  3. Melhora Manutenibilidade: Código auto-documentado
  4. Facilita Onboarding: Novos desenvolvedores entendem o domínio rapidamente
  5. Alinha Equipes: Todos falam a mesma língua

Desafios e Soluções ​

Desafio 1: Resistência à Mudança

  • Solução: Workshops regulares de refinamento da linguagem

Desafio 2: Linguagem Técnica Persistente

  • Solução: Code reviews focados em linguagem ubíqua

Desafio 3: Evolução da Linguagem

  • Solução: Documentação viva e atualizada constantemente

2. Bounded Context (Contexto Delimitado) ​

O Bounded Context é uma das estratégias mais poderosas do DDD para gerenciar complexidade. É a técnica de dividir um sistema grande e complexo em áreas de responsabilidade bem definidas, cada uma com seu próprio modelo, linguagem e regras.

Definição Profunda ​

Um Bounded Context é uma fronteira conceitual onde:

  • Um modelo específico se aplica de forma consistente
  • Uma linguagem ubíqua específica é usada
  • Regras de negócio específicas são aplicadas
  • Conceitos têm significados específicos dentro desse contexto

Por que Bounded Contexts são Essenciais? ​

Problema Real:

Sistema monolítico com 50+ entidades
- Customer significa "cliente que compra" em alguns lugares
- Customer significa "usuário do sistema" em outros lugares
- Customer significa "conta bancária" em outros lugares

Resultado: Confusão, bugs e dificuldade de manutenção.

Solução com Bounded Contexts:

Contexto: Gestão de Pedidos
- Customer = "Cliente que faz pedidos"
- Regras: Validação de crédito, histórico de compras

Contexto: Gestão de Usuários  
- Customer = "Usuário do sistema"
- Regras: Autenticação, permissões, perfis

Contexto: Gestão Financeira
- Customer = "Conta bancária"
- Regras: Saldo, transações, limites

Implementação Prática ​

1. Identificação de Contextos

csharp
/// <summary>
/// Contextos identificados para o sistema de E-commerce
/// 
/// 1. OrderManagement (Gestão de Pedidos)
///    - Responsabilidade: Criação, modificação e acompanhamento de pedidos
///    - Conceitos: Order, OrderLine, OrderStatus, ShippingAddress
///    - Regras: Validação de estoque, cálculo de preços, status de pedido
/// 
/// 2. CustomerManagement (Gestão de Clientes)
///    - Responsabilidade: Cadastro e gestão de clientes
///    - Conceitos: Customer, CustomerProfile, CustomerStatus, Address
///    - Regras: Validação de dados, segmentação, histórico
/// 
/// 3. ProductCatalog (Catálogo de Produtos)
///    - Responsabilidade: Gestão de produtos e categorias
///    - Conceitos: Product, Category, ProductVariant, Pricing
///    - Regras: Validação de produtos, hierarquia de categorias
/// 
/// 4. Inventory (Gestão de Estoque)
///    - Responsabilidade: Controle de estoque e disponibilidade
///    - Conceitos: StockItem, Warehouse, Reservation, StockMovement
///    - Regras: Reserva de estoque, movimentação, alertas
/// 
/// 5. Payment (Processamento de Pagamentos)
///    - Responsabilidade: Processamento e gestão de pagamentos
///    - Conceitos: Payment, PaymentMethod, Transaction, Refund
///    - Regras: Validação de pagamento, processamento, reembolsos
/// 
/// 6. Shipping (Logística e Entrega)
///    - Responsabilidade: Gestão de entregas e logística
///    - Conceitos: Shipment, ShippingMethod, Tracking, Delivery
///    - Regras: Cálculo de frete, rastreamento, confirmação
/// </summary>

2. Estrutura de Namespaces

csharp
// Contexto: Gestão de Pedidos
namespace OrderManagement.Domain.Entities
{
    public class Order : AggregateRoot<OrderId>
    {
        public OrderId Id { get; private set; }
        public CustomerId CustomerId { get; private set; }
        public List<OrderLine> Lines { get; private set; }
        public OrderStatus Status { get; private set; }
        public Money TotalAmount { get; private set; }
        public ShippingAddress ShippingAddress { get; private set; }
        
        public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
        {
            // Regras específicas do contexto de pedidos
            if (Status != OrderStatus.Draft)
                throw new InvalidOperationException("Cannot add items to confirmed order");
            
            var line = new OrderLine(productId, quantity, unitPrice);
            Lines.Add(line);
            RecalculateTotal();
        }
        
        public void Confirm()
        {
            // Regras de confirmação específicas do contexto de pedidos
            if (Lines.Count == 0)
                throw new InvalidOperationException("Order must have at least one item");
            
            if (ShippingAddress == null)
                throw new InvalidOperationException("Shipping address is required");
            
            Status = OrderStatus.Confirmed;
            AddDomainEvent(new OrderConfirmedEvent(Id));
        }
    }
}

// Contexto: Gestão de Clientes
namespace CustomerManagement.Domain.Entities
{
    public class Customer : AggregateRoot<CustomerId>
    {
        public CustomerId Id { get; private set; }
        public string Name { get; private set; }
        public Email Email { get; private set; }
        public CustomerStatus Status { get; private set; }
        public CustomerProfile Profile { get; private set; }
        public List<Address> Addresses { get; private set; }
        
        public void UpdateProfile(string name, Email email)
        {
            // Regras específicas do contexto de clientes
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentException("Name cannot be empty");
            
            Name = name;
            Email = email;
            
            AddDomainEvent(new CustomerProfileUpdatedEvent(Id, Name, Email));
        }
        
        public void AddAddress(Address address)
        {
            // Regras específicas do contexto de clientes
            if (address == null)
                throw new ArgumentException("Address cannot be null");
            
            if (Addresses.Count >= 5)
                throw new InvalidOperationException("Maximum 5 addresses allowed");
            
            Addresses.Add(address);
        }
    }
}

3. Mapeamento entre Contextos

csharp
/// <summary>
/// Mapeamento entre contextos - Context Mapping
/// 
/// 1. Customer (CustomerManagement) -> CustomerReference (OrderManagement)
///    - Tipo: Shared Kernel
///    - Propósito: Referência a cliente em pedidos
///    - Dados: Apenas Id e Name (dados mínimos necessários)
/// 
/// 2. Product (ProductCatalog) -> ProductReference (OrderManagement)
///    - Tipo: Published Language
///    - Propósito: Referência a produto em pedidos
///    - Dados: Id, Name, Price (snapshot no momento do pedido)
/// 
/// 3. Order (OrderManagement) -> OrderSummary (CustomerManagement)
///    - Tipo: Customer-Supplier
///    - Propósito: Histórico de pedidos do cliente
///    - Dados: Id, TotalAmount, Status, CreatedAt
/// 
/// 4. Payment (Payment) -> PaymentInfo (OrderManagement)
///    - Tipo: Conformist
///    - Propósito: Informações de pagamento do pedido
///    - Dados: PaymentId, Amount, Status, Method
/// </summary>

// Exemplo de mapeamento
namespace OrderManagement.Domain.ValueObjects
{
    public class CustomerReference : ValueObject
    {
        public CustomerId Id { get; }
        public string Name { get; }
        
        public CustomerReference(CustomerId id, string name)
        {
            Id = id;
            Name = name;
        }
        
        protected override IEnumerable<object> GetEqualityComponents()
        {
            yield return Id;
            yield return Name;
        }
    }
}

4. Estratégias de Integração

csharp
/// <summary>
/// Estratégias de integração entre contextos
/// 
/// 1. Shared Kernel (Núcleo Compartilhado)
///    - Contextos compartilham um modelo comum
///    - Usado para conceitos que são realmente compartilhados
///    - Exemplo: Money, Email, Address
/// 
/// 2. Customer-Supplier (Cliente-Fornecedor)
///    - Um contexto fornece dados para outro
///    - Relação direta com dependência clara
///    - Exemplo: ProductCatalog fornece dados para OrderManagement
/// 
/// 3. Conformist (Conformista)
///    - Um contexto se adapta ao modelo de outro
///    - Usado quando não há controle sobre o contexto fornecedor
///    - Exemplo: Integração com sistemas externos
/// 
/// 4. Anti-Corruption Layer (Camada Anticorrupção)
///    - Camada que traduz entre modelos diferentes
///    - Protege o contexto interno de influências externas
///    - Exemplo: Integração com APIs de terceiros
/// 
/// 5. Published Language (Linguagem Publicada)
///    - Linguagem comum para comunicação entre contextos
///    - Usado para integração assíncrona
///    - Exemplo: Eventos de domínio publicados
/// 
/// 6. Open Host Service (Serviço de Hospedagem Aberta)
///    - API pública para outros contextos consumirem
///    - Contrato estável e bem documentado
///    - Exemplo: APIs REST para integração
/// </summary>

Benefícios dos Bounded Contexts ​

  1. Reduz Complexidade: Divide problemas grandes em problemas menores
  2. Facilita Manutenção: Mudanças isoladas em contextos específicos
  3. Permite Evolução Independente: Cada contexto pode evoluir separadamente
  4. Melhora Performance: Otimizações específicas por contexto
  5. Facilita Trabalho em Equipe: Equipes podem trabalhar em contextos diferentes

Desafios e Soluções ​

Desafio 1: Identificar os Contextos Certos

  • Solução: Event Storming e workshops com especialistas de domínio

Desafio 2: Gerenciar Integrações

  • Solução: Context Mapping bem definido e documentado

Desafio 3: Evitar Over-Engineering

  • Solução: Começar com poucos contextos e evoluir gradualmente

3. Entities (Entidades) ​

As Entidades são o coração do modelo de domínio e representam objetos que têm identidade única e podem evoluir ao longo do tempo. Diferentemente dos Value Objects, as entidades são definidas por sua identidade, não por seus atributos.

Definição Profunda ​

Uma Entidade é um objeto que:

  • Mantém identidade única ao longo de sua vida útil
  • Pode mudar seus atributos sem perder sua identidade
  • Representa um conceito do domínio com continuidade temporal
  • Encapsula lógica de negócio relacionada a sua identidade

Por que Entidades são Fundamentais? ​

Problema Real:

csharp
// ❌ Abordagem procedural - dados espalhados
public class OrderService
{
    public void UpdateOrderStatus(Guid orderId, string newStatus)
    {
        var order = _repository.GetById(orderId);
        order.Status = newStatus; // Mudança direta de dados
        
        if (newStatus == "Confirmed")
        {
            // Lógica de negócio espalhada
            ReserveInventory(order);
            SendNotification(order);
            UpdateCustomerHistory(order);
        }
    }
}

Resultado: Lógica de negócio espalhada, violação de encapsulamento, difícil manutenção.

Solução com Entidades:

csharp
// ✅ Abordagem orientada a domínio
public class Order : Entity<OrderId>
{
    public void Confirm()
    {
        // Lógica de negócio encapsulada na entidade
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot confirm non-draft order");
        
        if (Lines.Count == 0)
            throw new InvalidOperationException("Cannot confirm empty order");
        
        Status = OrderStatus.Confirmed;
        ConfirmedAt = DateTime.UtcNow;
        
        // Eventos de domínio para coordenação
        AddDomainEvent(new OrderConfirmedEvent(Id));
    }
}

Características Essenciais das Entidades ​

1. Identidade Única e Imutável

csharp
public class OrderId : ValueObject
{
    public Guid Value { get; }
    
    public OrderId(Guid value)
    {
        Value = value;
    }
    
    public static OrderId New() => new(Guid.NewGuid());
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

public class Order : Entity<OrderId>
{
    public OrderId Id { get; private set; } // Imutável após criação
    
    public Order(OrderId id, CustomerId customerId)
    {
        Id = id; // Definido uma única vez
        CustomerId = customerId;
        Lines = new List<OrderLine>();
        Status = OrderStatus.Draft;
    }
}

2. Encapsulamento de Estado

csharp
public class Order : Entity<OrderId>
{
    // Estado privado - só pode ser modificado através de métodos
    private readonly List<OrderLine> _lines;
    private OrderStatus _status;
    private Money _totalAmount;
    
    // Propriedades públicas de leitura
    public IReadOnlyCollection<OrderLine> Lines => _lines.AsReadOnly();
    public OrderStatus Status => _status;
    public Money TotalAmount => _totalAmount;
    
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        // Validação de regras de negócio
        if (_status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        // Modificação controlada do estado
        var line = new OrderLine(productId, quantity, unitPrice);
        _lines.Add(line);
        RecalculateTotal();
    }
    
    private void RecalculateTotal()
    {
        _totalAmount = _lines.Aggregate(Money.Zero, (total, line) => total + line.Total);
    }
}

3. Lógica de Negócio Encapsulada

csharp
public class Order : Entity<OrderId>
{
    public void Confirm()
    {
        // Invariantes de negócio
        ValidateCanBeConfirmed();
        
        // Mudança de estado
        _status = OrderStatus.Confirmed;
        ConfirmedAt = DateTime.UtcNow;
        
        // Efeitos colaterais através de eventos
        AddDomainEvent(new OrderConfirmedEvent(Id));
    }
    
    public void Cancel()
    {
        // Invariantes de negócio
        if (_status == OrderStatus.Delivered)
            throw new InvalidOperationException("Cannot cancel delivered order");
        
        // Mudança de estado
        _status = OrderStatus.Cancelled;
        CancelledAt = DateTime.UtcNow;
        
        // Efeitos colaterais
        AddDomainEvent(new OrderCancelledEvent(Id));
    }
    
    private void ValidateCanBeConfirmed()
    {
        if (_status != OrderStatus.Draft)
            throw new InvalidOperationException("Only draft orders can be confirmed");
        
        if (_lines.Count == 0)
            throw new InvalidOperationException("Order must have at least one item");
        
        if (ShippingAddress == null)
            throw new InvalidOperationException("Shipping address is required");
    }
}

4. Eventos de Domínio

csharp
public class Order : Entity<OrderId>
{
    private readonly List<IDomainEvent> _domainEvents = new();
    
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
    
    protected void AddDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }
    
    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
    
    public void Confirm()
    {
        // ... lógica de confirmação ...
        
        // Evento de domínio para coordenação
        AddDomainEvent(new OrderConfirmedEvent(Id, CustomerId, TotalAmount));
    }
}

Padrões de Design para Entidades ​

1. Factory Pattern para Criação

csharp
public class OrderFactory
{
    public static Order CreateDraft(CustomerId customerId)
    {
        var orderId = OrderId.New();
        return new Order(orderId, customerId);
    }
    
    public static Order CreateFromExistingOrder(OrderId orderId, CustomerId customerId, 
        IEnumerable<OrderLine> lines, OrderStatus status)
    {
        var order = new Order(orderId, customerId);
        
        foreach (var line in lines)
        {
            order.AddItem(line.ProductId, line.Quantity, line.UnitPrice);
        }
        
        if (status == OrderStatus.Confirmed)
            order.Confirm();
        
        return order;
    }
}

2. Specification Pattern para Consultas

csharp
public class OrderSpecification
{
    public static Specification<Order> ByCustomer(CustomerId customerId)
    {
        return new Specification<Order>(order => order.CustomerId == customerId);
    }
    
    public static Specification<Order> ByStatus(OrderStatus status)
    {
        return new Specification<Order>(order => order.Status == status);
    }
    
    public static Specification<Order> ByDateRange(DateTime startDate, DateTime endDate)
    {
        return new Specification<Order>(order => 
            order.CreatedAt >= startDate && order.CreatedAt <= endDate);
    }
    
    public static Specification<Order> HighValueOrders(Money minimumAmount)
    {
        return new Specification<Order>(order => order.TotalAmount >= minimumAmount);
    }
}

3. Repository Pattern para Persistência

csharp
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(OrderId id);
    Task<IEnumerable<Order>> GetBySpecificationAsync(Specification<Order> spec);
    Task AddAsync(Order order);
    Task UpdateAsync(Order order);
    Task DeleteAsync(OrderId id);
}

public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;
    
    public async Task<Order> GetByIdAsync(OrderId id)
    {
        var orderEntity = await _context.Orders
            .Include(o => o.Lines)
            .FirstOrDefaultAsync(o => o.Id == id.Value);
        
        return orderEntity != null ? MapToDomain(orderEntity) : null;
    }
    
    public async Task<IEnumerable<Order>> GetBySpecificationAsync(Specification<Order> spec)
    {
        var query = _context.Orders.Include(o => o.Lines);
        var filteredQuery = spec.ToExpression().Compile();
        
        var entities = await query.ToListAsync();
        return entities.Select(MapToDomain).Where(filteredQuery);
    }
}

Boas Práticas para Entidades ​

1. Mantenha Entidades Pequenas e Focadas

csharp
// ❌ Entidade muito grande
public class Order : Entity<OrderId>
{
    // 50+ propriedades e métodos
    // Múltiplas responsabilidades
    // Difícil de manter e testar
}

// ✅ Entidade focada
public class Order : Entity<OrderId>
{
    // Apenas propriedades essenciais
    // Métodos relacionados ao ciclo de vida do pedido
    // Responsabilidade única
}

2. Use Value Objects para Atributos Complexos

csharp
// ❌ Tipos primitivos
public class Order : Entity<OrderId>
{
    public string CustomerName { get; set; }
    public string CustomerEmail { get; set; }
    public decimal TotalAmount { get; set; }
    public string Currency { get; set; }
}

// ✅ Value Objects
public class Order : Entity<OrderId>
{
    public CustomerReference Customer { get; private set; }
    public Money TotalAmount { get; private set; }
    public ShippingAddress ShippingAddress { get; private set; }
}

3. Valide Invariantes de Negócio

csharp
public class Order : Entity<OrderId>
{
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        // Invariantes de negócio
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        if (quantity.Value <= 0)
            throw new ArgumentException("Quantity must be positive");
        
        if (unitPrice.Amount <= 0)
            throw new ArgumentException("Unit price must be positive");
        
        // Lógica de negócio
        var line = new OrderLine(productId, quantity, unitPrice);
        _lines.Add(line);
        RecalculateTotal();
    }
}

Benefícios das Entidades Bem Projetadas ​

  1. Encapsulamento: Lógica de negócio protegida e centralizada
  2. Manutenibilidade: Mudanças isoladas e previsíveis
  3. Testabilidade: Comportamento testável de forma isolada
  4. Consistência: Invariantes de negócio sempre respeitadas
  5. Legibilidade: Código que expressa intenção do domínio
csharp
public class Order : Entity<OrderId>
{
    public OrderId Id { get; private set; }
    public CustomerId CustomerId { get; private set; }
    public List<OrderLine> Lines { get; private set; }
    public OrderStatus Status { get; private set; }
    public Money TotalAmount { get; private set; }
    
    public Order(OrderId id, CustomerId customerId)
    {
        Id = id;
        CustomerId = customerId;
        Lines = new List<OrderLine>();
        Status = OrderStatus.Draft;
        TotalAmount = Money.Zero;
    }
    
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        var line = new OrderLine(productId, quantity, unitPrice);
        Lines.Add(line);
        RecalculateTotal();
    }
    
    public void Confirm()
    {
        if (Lines.Count == 0)
            throw new InvalidOperationException("Cannot confirm empty order");
        
        Status = OrderStatus.Confirmed;
        AddDomainEvent(new OrderConfirmedEvent(Id));
    }
    
    private void RecalculateTotal()
    {
        TotalAmount = Lines.Aggregate(Money.Zero, (total, line) => total + line.Total);
    }
}

public abstract class Entity<TId> where TId : ValueObject
{
    public TId Id { get; protected set; }
    
    protected Entity() { }
    
    protected Entity(TId id)
    {
        Id = id;
    }
    
    public override bool Equals(object obj)
    {
        if (obj is not Entity<TId> other)
            return false;
        
        if (ReferenceEquals(this, other))
            return true;
        
        return Id.Equals(other.Id);
    }
    
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
    
    public static bool operator ==(Entity<TId> left, Entity<TId> right)
    {
        return EqualOperator(left, right);
    }
    
    public static bool operator !=(Entity<TId> left, Entity<TId> right)
    {
        return NotEqualOperator(left, right);
    }
    
    protected static bool EqualOperator(Entity<TId> left, Entity<TId> right)
    {
        if (left is null ^ right is null)
            return false;
        return left is null || left.Equals(right);
    }
    
    protected static bool NotEqualOperator(Entity<TId> left, Entity<TId> right)
    {
        return !EqualOperator(left, right);
    }
}

4. Value Objects (Objetos de Valor) ​

Value objects são objetos imutáveis que são definidos por seus atributos, não por identidade. Eles representam conceitos do domínio que não têm identidade própria.

csharp
public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    public Money(decimal amount, string currency)
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative");
        
        if (string.IsNullOrEmpty(currency))
            throw new ArgumentException("Currency cannot be empty");
        
        Amount = amount;
        Currency = currency.ToUpperInvariant();
    }
    
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Cannot add different currencies");
        
        return new Money(Amount + other.Amount, Currency);
    }
    
    public Money Multiply(decimal factor)
    {
        return new Money(Amount * factor, Currency);
    }
    
    public static Money Zero(string currency) => new(0, currency);
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
    
    public override string ToString()
    {
        return $"{Amount:F2} {Currency}";
    }
}

public class Email : ValueObject
{
    public string Value { get; }
    
    public Email(string value)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Email cannot be empty");
        
        if (!IsValidEmail(value))
            throw new ArgumentException("Invalid email format");
        
        Value = value.ToLowerInvariant();
    }
    
    private static bool IsValidEmail(string email)
    {
        try
        {
            var addr = new System.Net.Mail.MailAddress(email);
            return addr.Address == email;
        }
        catch
        {
            return false;
        }
    }
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
    
    public static implicit operator string(Email email) => email.Value;
}

public abstract class ValueObject
{
    protected abstract IEnumerable<object> GetEqualityComponents();
    
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
            return false;
        
        var other = (ValueObject)obj;
        return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }
    
    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x != null ? x.GetHashCode() : 0)
            .Aggregate((x, y) => x ^ y);
    }
    
    public static bool operator ==(ValueObject left, ValueObject right)
    {
        return EqualOperator(left, right);
    }
    
    public static bool operator !=(ValueObject left, ValueObject right)
    {
        return NotEqualOperator(left, right);
    }
    
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (left is null ^ right is null)
            return false;
        return left is null || left.Equals(right);
    }
    
    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !EqualOperator(left, right);
    }
}

5. Aggregates (Agregados) ​

Agregados são clusters de entidades e value objects que são tratados como uma unidade para transações. Cada agregado tem uma raiz (aggregate root) que controla o acesso aos outros objetos.

csharp
public class Order : AggregateRoot<OrderId>
{
    public OrderId Id { get; private set; }
    public CustomerId CustomerId { get; private set; }
    public List<OrderLine> Lines { get; private set; }
    public OrderStatus Status { get; private set; }
    public Money TotalAmount { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? ConfirmedAt { get; private set; }
    
    public Order(OrderId id, CustomerId customerId)
    {
        Id = id;
        CustomerId = customerId;
        Lines = new List<OrderLine>();
        Status = OrderStatus.Draft;
        TotalAmount = Money.Zero;
        CreatedAt = DateTime.UtcNow;
        
        AddDomainEvent(new OrderCreatedEvent(Id, CustomerId));
    }
    
    public void AddItem(ProductId productId, Quantity quantity, Money unitPrice)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        var existingLine = Lines.FirstOrDefault(l => l.ProductId == productId);
        if (existingLine != null)
        {
            existingLine.UpdateQuantity(existingLine.Quantity.Add(quantity));
        }
        else
        {
            var line = new OrderLine(productId, quantity, unitPrice);
            Lines.Add(line);
        }
        
        RecalculateTotal();
    }
    
    public void RemoveItem(ProductId productId)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot remove items from confirmed order");
        
        var line = Lines.FirstOrDefault(l => l.ProductId == productId);
        if (line != null)
        {
            Lines.Remove(line);
            RecalculateTotal();
        }
    }
    
    public void Confirm()
    {
        if (Lines.Count == 0)
            throw new InvalidOperationException("Cannot confirm empty order");
        
        Status = OrderStatus.Confirmed;
        ConfirmedAt = DateTime.UtcNow;
        
        AddDomainEvent(new OrderConfirmedEvent(Id));
    }
    
    public void Cancel()
    {
        if (Status == OrderStatus.Cancelled)
            throw new InvalidOperationException("Order is already cancelled");
        
        Status = OrderStatus.Cancelled;
        AddDomainEvent(new OrderCancelledEvent(Id));
    }
    
    private void RecalculateTotal()
    {
        TotalAmount = Lines.Aggregate(Money.Zero, (total, line) => total + line.Total);
    }
}

public class OrderLine : Entity<OrderLineId>
{
    public OrderLineId Id { get; private set; }
    public ProductId ProductId { get; private set; }
    public Quantity Quantity { get; private set; }
    public Money UnitPrice { get; private set; }
    public Money Total => UnitPrice.Multiply(Quantity.Value);
    
    public OrderLine(ProductId productId, Quantity quantity, Money unitPrice)
    {
        Id = new OrderLineId(Guid.NewGuid());
        ProductId = productId;
        Quantity = quantity;
        UnitPrice = unitPrice;
    }
    
    public void UpdateQuantity(Quantity newQuantity)
    {
        Quantity = newQuantity;
    }
}

public abstract class AggregateRoot<TId> : Entity<TId> where TId : ValueObject
{
    private readonly List<IDomainEvent> _domainEvents = new();
    
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
    
    protected void AddDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }
    
    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

6. Domain Events (Eventos de Domínio) ​

Domain events representam algo que aconteceu no domínio. Eles permitem que diferentes partes do sistema reajam a mudanças sem acoplamento direto.

csharp
public interface IDomainEvent
{
    Guid Id { get; }
    DateTime OccurredOn { get; }
}

public abstract class DomainEvent : IDomainEvent
{
    public Guid Id { get; } = Guid.NewGuid();
    public DateTime OccurredOn { get; } = DateTime.UtcNow;
}

public class OrderCreatedEvent : DomainEvent
{
    public OrderId OrderId { get; }
    public CustomerId CustomerId { get; }
    
    public OrderCreatedEvent(OrderId orderId, CustomerId customerId)
    {
        OrderId = orderId;
        CustomerId = customerId;
    }
}

public class OrderConfirmedEvent : DomainEvent
{
    public OrderId OrderId { get; }
    
    public OrderConfirmedEvent(OrderId orderId)
    {
        OrderId = orderId;
    }
}

public class OrderCancelledEvent : DomainEvent
{
    public OrderId OrderId { get; }
    
    public OrderCancelledEvent(OrderId orderId)
    {
        OrderId = orderId;
    }
}

7. Domain Services (Serviços de Domínio) ​

Domain services contêm lógica de negócio que não pertence naturalmente a uma entidade ou value object específico.

csharp
public interface IOrderPricingService
{
    Money CalculateTotal(Order order, Customer customer);
}

public class OrderPricingService : IOrderPricingService
{
    public Money CalculateTotal(Order order, Customer customer)
    {
        var subtotal = order.Lines.Aggregate(Money.Zero("USD"), (total, line) => total + line.Total);
        
        var discount = CalculateDiscount(customer, subtotal);
        var tax = CalculateTax(subtotal.Subtract(discount));
        
        return subtotal.Subtract(discount).Add(tax);
    }
    
    private Money CalculateDiscount(Customer customer, Money subtotal)
    {
        if (customer.IsPremium)
            return subtotal.Multiply(0.1m); // 10% discount for premium customers
        
        return Money.Zero("USD");
    }
    
    private Money CalculateTax(Money amount)
    {
        return amount.Multiply(0.08m); // 8% tax
    }
}

public interface IInventoryService
{
    Task<bool> IsProductAvailable(ProductId productId, Quantity quantity);
    Task ReserveProduct(ProductId productId, Quantity quantity);
}

public class InventoryService : IInventoryService
{
    private readonly IProductRepository _productRepository;
    
    public InventoryService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    
    public async Task<bool> IsProductAvailable(ProductId productId, Quantity quantity)
    {
        var product = await _productRepository.GetByIdAsync(productId);
        return product?.AvailableQuantity >= quantity;
    }
    
    public async Task ReserveProduct(ProductId productId, Quantity quantity)
    {
        var product = await _productRepository.GetByIdAsync(productId);
        if (product == null)
            throw new ProductNotFoundException(productId);
        
        if (product.AvailableQuantity < quantity)
            throw new InsufficientStockException(productId, quantity);
        
        product.ReserveStock(quantity);
        await _productRepository.UpdateAsync(product);
    }
}

8. Repositories (Repositórios) ​

Repositories abstraem a persistência de dados e fornecem uma interface orientada ao domínio para acessar agregados.

csharp
public interface IRepository<T, TId> where T : AggregateRoot<TId> where TId : ValueObject
{
    Task<T> GetByIdAsync(TId id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(TId id);
}

public interface IOrderRepository : IRepository<Order, OrderId>
{
    Task<IEnumerable<Order>> GetByCustomerAsync(CustomerId customerId);
    Task<IEnumerable<Order>> GetByStatusAsync(OrderStatus status);
    Task<IEnumerable<Order>> GetByDateRangeAsync(DateTime startDate, DateTime endDate);
}

public class OrderRepository : IOrderRepository
{
    private readonly ApplicationDbContext _context;
    
    public OrderRepository(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public async Task<Order> GetByIdAsync(OrderId id)
    {
        var orderEntity = await _context.Orders
            .Include(o => o.Lines)
            .FirstOrDefaultAsync(o => o.Id == id.Value);
        
        if (orderEntity == null)
            return null;
        
        return MapToDomain(orderEntity);
    }
    
    public async Task<IEnumerable<Order>> GetByCustomerAsync(CustomerId customerId)
    {
        var orderEntities = await _context.Orders
            .Include(o => o.Lines)
            .Where(o => o.CustomerId == customerId.Value)
            .ToListAsync();
        
        return orderEntities.Select(MapToDomain);
    }
    
    public async Task AddAsync(Order order)
    {
        var orderEntity = MapToEntity(order);
        _context.Orders.Add(orderEntity);
        await _context.SaveChangesAsync();
    }
    
    public async Task UpdateAsync(Order order)
    {
        var orderEntity = await _context.Orders
            .Include(o => o.Lines)
            .FirstOrDefaultAsync(o => o.Id == order.Id.Value);
        
        if (orderEntity == null)
            throw new OrderNotFoundException(order.Id);
        
        UpdateEntityFromDomain(orderEntity, order);
        await _context.SaveChangesAsync();
    }
    
    private Order MapToDomain(OrderEntity entity)
    {
        var order = new Order(new OrderId(entity.Id), new CustomerId(entity.CustomerId));
        
        foreach (var lineEntity in entity.Lines)
        {
            order.AddItem(
                new ProductId(lineEntity.ProductId),
                new Quantity(lineEntity.Quantity),
                new Money(lineEntity.UnitPrice, "USD")
            );
        }
        
        if (entity.Status == OrderStatus.Confirmed)
            order.Confirm();
        
        return order;
    }
    
    private OrderEntity MapToEntity(Order order)
    {
        return new OrderEntity
        {
            Id = order.Id.Value,
            CustomerId = order.CustomerId.Value,
            Status = order.Status,
            TotalAmount = order.TotalAmount.Amount,
            CreatedAt = order.CreatedAt,
            ConfirmedAt = order.ConfirmedAt,
            Lines = order.Lines.Select(l => new OrderLineEntity
            {
                Id = l.Id.Value,
                ProductId = l.ProductId.Value,
                Quantity = l.Quantity.Value,
                UnitPrice = l.UnitPrice.Amount
            }).ToList()
        };
    }
}

9. Application Services (Serviços de Aplicação) ​

Application services orquestram casos de uso da aplicação, coordenando entidades de domínio e serviços.

csharp
public interface IOrderApplicationService
{
    Task<OrderId> CreateOrderAsync(CreateOrderCommand command);
    Task ConfirmOrderAsync(ConfirmOrderCommand command);
    Task CancelOrderAsync(CancelOrderCommand command);
    Task<OrderDto> GetOrderAsync(GetOrderQuery query);
}

public class OrderApplicationService : IOrderApplicationService
{
    private readonly IOrderRepository _orderRepository;
    private readonly ICustomerRepository _customerRepository;
    private readonly IProductRepository _productRepository;
    private readonly IInventoryService _inventoryService;
    private readonly IOrderPricingService _pricingService;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IDomainEventDispatcher _eventDispatcher;
    
    public OrderApplicationService(
        IOrderRepository orderRepository,
        ICustomerRepository customerRepository,
        IProductRepository productRepository,
        IInventoryService inventoryService,
        IOrderPricingService pricingService,
        IUnitOfWork unitOfWork,
        IDomainEventDispatcher eventDispatcher)
    {
        _orderRepository = orderRepository;
        _customerRepository = customerRepository;
        _productRepository = productRepository;
        _inventoryService = inventoryService;
        _pricingService = pricingService;
        _unitOfWork = unitOfWork;
        _eventDispatcher = eventDispatcher;
    }
    
    public async Task<OrderId> CreateOrderAsync(CreateOrderCommand command)
    {
        var customer = await _customerRepository.GetByIdAsync(command.CustomerId);
        if (customer == null)
            throw new CustomerNotFoundException(command.CustomerId);
        
        var order = new Order(new OrderId(Guid.NewGuid()), command.CustomerId);
        
        foreach (var item in command.Items)
        {
            var product = await _productRepository.GetByIdAsync(item.ProductId);
            if (product == null)
                throw new ProductNotFoundException(item.ProductId);
            
            if (!await _inventoryService.IsProductAvailable(item.ProductId, item.Quantity))
                throw new InsufficientStockException(item.ProductId, item.Quantity);
            
            order.AddItem(item.ProductId, item.Quantity, product.Price);
        }
        
        await _orderRepository.AddAsync(order);
        await _unitOfWork.SaveChangesAsync();
        
        await DispatchDomainEvents(order);
        
        return order.Id;
    }
    
    public async Task ConfirmOrderAsync(ConfirmOrderCommand command)
    {
        var order = await _orderRepository.GetByIdAsync(command.OrderId);
        if (order == null)
            throw new OrderNotFoundException(command.OrderId);
        
        foreach (var line in order.Lines)
        {
            await _inventoryService.ReserveProduct(line.ProductId, line.Quantity);
        }
        
        order.Confirm();
        await _orderRepository.UpdateAsync(order);
        await _unitOfWork.SaveChangesAsync();
        
        await DispatchDomainEvents(order);
    }
    
    public async Task CancelOrderAsync(CancelOrderCommand command)
    {
        var order = await _orderRepository.GetByIdAsync(command.OrderId);
        if (order == null)
            throw new OrderNotFoundException(command.OrderId);
        
        order.Cancel();
        await _orderRepository.UpdateAsync(order);
        await _unitOfWork.SaveChangesAsync();
        
        await DispatchDomainEvents(order);
    }
    
    public async Task<OrderDto> GetOrderAsync(GetOrderQuery query)
    {
        var order = await _orderRepository.GetByIdAsync(query.OrderId);
        if (order == null)
            throw new OrderNotFoundException(query.OrderId);
        
        return MapToDto(order);
    }
    
    private async Task DispatchDomainEvents(Order order)
    {
        foreach (var domainEvent in order.DomainEvents)
        {
            await _eventDispatcher.DispatchAsync(domainEvent);
        }
        order.ClearDomainEvents();
    }
    
    private OrderDto MapToDto(Order order)
    {
        return new OrderDto
        {
            Id = order.Id.Value,
            CustomerId = order.CustomerId.Value,
            Status = order.Status.ToString(),
            TotalAmount = order.TotalAmount.Amount,
            CreatedAt = order.CreatedAt,
            ConfirmedAt = order.ConfirmedAt,
            Lines = order.Lines.Select(l => new OrderLineDto
            {
                ProductId = l.ProductId.Value,
                Quantity = l.Quantity.Value,
                UnitPrice = l.UnitPrice.Amount,
                Total = l.Total.Amount
            }).ToList()
        };
    }
}

🚀 Guia para Iniciar uma Aplicação com DDD ​

Passo 1: Entender o Domínio ​

  1. Event Storming: Realize workshops com especialistas de domínio para mapear eventos de negócio
  2. Domain Experts: Identifique e trabalhe com especialistas de domínio
  3. Ubiquitous Language: Defina a linguagem compartilhada entre técnicos e especialistas

Passo 2: Identificar Bounded Contexts ​

csharp
// Exemplo de identificação de contextos
public static class BoundedContexts
{
    public const string OrderManagement = "OrderManagement";
    public const string CustomerManagement = "CustomerManagement";
    public const string ProductCatalog = "ProductCatalog";
    public const string Inventory = "Inventory";
    public const string Payment = "Payment";
    public const string Shipping = "Shipping";
}

Passo 3: Definir Entidades e Value Objects ​

csharp
// Value Objects
public class OrderId : ValueObject
{
    public Guid Value { get; }
    
    public OrderId(Guid value)
    {
        Value = value;
    }
    
    public static OrderId New() => new(Guid.NewGuid());
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

public class CustomerId : ValueObject
{
    public Guid Value { get; }
    
    public CustomerId(Guid value)
    {
        Value = value;
    }
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

// Enums como Value Objects
public class OrderStatus : ValueObject
{
    public static readonly OrderStatus Draft = new("Draft");
    public static readonly OrderStatus Confirmed = new("Confirmed");
    public static readonly OrderStatus Cancelled = new("Cancelled");
    public static readonly OrderStatus Shipped = new("Shipped");
    public static readonly OrderStatus Delivered = new("Delivered");
    
    public string Value { get; }
    
    private OrderStatus(string value)
    {
        Value = value;
    }
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

Passo 4: Implementar Agregados ​

csharp
public class Customer : AggregateRoot<CustomerId>
{
    public CustomerId Id { get; private set; }
    public string Name { get; private set; }
    public Email Email { get; private set; }
    public CustomerStatus Status { get; private set; }
    public DateTime CreatedAt { get; private set; }
    
    public Customer(CustomerId id, string name, Email email)
    {
        Id = id;
        Name = name;
        Email = email;
        Status = CustomerStatus.Active;
        CreatedAt = DateTime.UtcNow;
        
        AddDomainEvent(new CustomerCreatedEvent(Id, Name, Email));
    }
    
    public void UpdateProfile(string name, Email email)
    {
        Name = name;
        Email = email;
        
        AddDomainEvent(new CustomerProfileUpdatedEvent(Id, Name, Email));
    }
    
    public void Deactivate()
    {
        Status = CustomerStatus.Inactive;
        AddDomainEvent(new CustomerDeactivatedEvent(Id));
    }
}

Passo 5: Configurar a Estrutura de Pastas ​

src/
├── Domain/
│   ├── Entities/
│   ├── ValueObjects/
│   ├── Aggregates/
│   ├── DomainEvents/
│   ├── DomainServices/
│   └── Exceptions/
├── Application/
│   ├── Commands/
│   ├── Queries/
│   ├── DTOs/
│   ├── Services/
│   └── Interfaces/
├── Infrastructure/
│   ├── Persistence/
│   ├── ExternalServices/
│   └── Messaging/
└── Presentation/
    ├── Controllers/
    ├── Middleware/
    └── Filters/

Passo 6: Implementar Repositories ​

csharp
public interface IUnitOfWork : IDisposable
{
    IOrderRepository Orders { get; }
    ICustomerRepository Customers { get; }
    IProductRepository Products { get; }
    Task<int> SaveChangesAsync();
    Task BeginTransactionAsync();
    Task CommitAsync();
    Task RollbackAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;
    private IDbContextTransaction _transaction;
    
    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        Orders = new OrderRepository(context);
        Customers = new CustomerRepository(context);
        Products = new ProductRepository(context);
    }
    
    public IOrderRepository Orders { get; }
    public ICustomerRepository Customers { get; }
    public IProductRepository Products { get; }
    
    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
    
    public async Task BeginTransactionAsync()
    {
        _transaction = await _context.Database.BeginTransactionAsync();
    }
    
    public async Task CommitAsync()
    {
        await _transaction?.CommitAsync();
    }
    
    public async Task RollbackAsync()
    {
        await _transaction?.RollbackAsync();
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context?.Dispose();
    }
}

Passo 7: Configurar Dependency Injection ​

csharp
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDomainServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderPricingService, OrderPricingService>();
        services.AddScoped<IInventoryService, InventoryService>();
        services.AddScoped<IDomainEventDispatcher, DomainEventDispatcher>();
        
        return services;
    }
    
    public static IServiceCollection AddApplicationServices(this IServiceCollection services)
    {
        services.AddScoped<IOrderApplicationService, OrderApplicationService>();
        services.AddScoped<ICustomerApplicationService, CustomerApplicationService>();
        
        return services;
    }
    
    public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
        
        services.AddScoped<IUnitOfWork, UnitOfWork>();
        services.AddScoped<IOrderRepository, OrderRepository>();
        services.AddScoped<ICustomerRepository, CustomerRepository>();
        services.AddScoped<IProductRepository, ProductRepository>();
        
        return services;
    }
}

// Program.cs ou Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddDomainServices();
    services.AddApplicationServices();
    services.AddInfrastructureServices(Configuration);
}

Passo 8: Implementar Controllers ​

csharp
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderApplicationService _orderService;
    
    public OrdersController(IOrderApplicationService orderService)
    {
        _orderService = orderService;
    }
    
    [HttpPost]
    public async Task<ActionResult<OrderId>> CreateOrder([FromBody] CreateOrderCommand command)
    {
        try
        {
            var orderId = await _orderService.CreateOrderAsync(command);
            return CreatedAtAction(nameof(GetOrder), new { id = orderId.Value }, orderId);
        }
        catch (CustomerNotFoundException ex)
        {
            return NotFound(new { error = ex.Message });
        }
        catch (ProductNotFoundException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
        catch (InsufficientStockException ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(Guid id)
    {
        try
        {
            var order = await _orderService.GetOrderAsync(new GetOrderQuery { OrderId = new OrderId(id) });
            return Ok(order);
        }
        catch (OrderNotFoundException ex)
        {
            return NotFound(new { error = ex.Message });
        }
    }
    
    [HttpPost("{id}/confirm")]
    public async Task<IActionResult> ConfirmOrder(Guid id)
    {
        try
        {
            await _orderService.ConfirmOrderAsync(new ConfirmOrderCommand { OrderId = new OrderId(id) });
            return NoContent();
        }
        catch (OrderNotFoundException ex)
        {
            return NotFound(new { error = ex.Message });
        }
    }
    
    [HttpPost("{id}/cancel")]
    public async Task<IActionResult> CancelOrder(Guid id)
    {
        try
               {
            await _orderService.CancelOrderAsync(new CancelOrderCommand { OrderId = new OrderId(id) });
            return NoContent();
        }
        catch (OrderNotFoundException ex)
        {
            return NotFound(new { error = ex.Message });
        }
    }
}

📚 Recursos Adicionais ​

Livros Recomendados ​

  • "Domain-Driven Design" - Eric Evans
  • "Implementing Domain-Driven Design" - Vaughn Vernon
  • "Domain-Driven Design Distilled" - Vaughn Vernon
  • "Patterns, Principles, and Practices of Domain-Driven Design" - Scott Millett

Artigos e Blogs ​

  • Martin Fowler's Blog
  • DDD Community
  • Microsoft .NET Documentation

⚠️ Considerações Importantes ​

Quando Usar DDD ​

  • Domínios Complexos: Quando o domínio de negócio é complexo
  • Equipes Grandes: Quando há múltiplas equipes trabalhando no mesmo sistema
  • Longa Duração: Para projetos que vão durar anos
  • Mudanças Frequentes: Quando os requisitos mudam frequentemente

Quando NÃO Usar DDD ​

  • CRUD Simples: Para aplicações CRUD básicas
  • Domínios Simples: Quando o domínio é muito simples
  • Projetos Pequenos: Para projetos de curta duração
  • Equipes Pequenas: Quando há poucos desenvolvedores

Desafios Comuns ​

  1. Curva de Aprendizado: DDD tem uma curva de aprendizado íngreme
  2. Over-Engineering: Fácil de aplicar demais e complicar desnecessariamente
  3. Performance: Pode impactar performance se não bem implementado
  4. Complexidade: Pode adicionar complexidade desnecessária

Boas Práticas ​

  1. Comece Pequeno: Implemente DDD gradualmente
  2. Foque no Domínio: Mantenha o foco no domínio de negócio
  3. Linguagem Ubíqua: Use consistentemente a linguagem ubíqua
  4. Teste Adequadamente: Teste entidades, value objects e serviços de domínio
  5. Documente Decisões: Documente decisões arquiteturais importantes

🎯 Conclusão ​

Domain-Driven Design é uma abordagem poderosa para desenvolver software complexo que reflete fielmente o domínio de negócio. Quando aplicado corretamente, ele resulta em código mais legível, manutenível e alinhado com as necessidades do negócio.

A chave para o sucesso com DDD é:

  • Entender profundamente o domínio
  • Usar consistentemente a linguagem ubíqua
  • Manter o foco na complexidade do domínio, não na complexidade técnica
  • Implementar gradualmente e ajustar conforme necessário

Lembre-se: DDD não é uma solução para todos os problemas, mas uma ferramenta poderosa para domínios complexos que merece ser considerada no arsenal de qualquer desenvolvedor senior.