Pular para conteúdo

DDD - Domain-Driven Design

Visão Geral

Domain-Driven Design (DDD) é uma abordagem de desenvolvimento de software que coloca o domínio do negócio no centro do processo de desenvolvimento. O foco está em criar um modelo de domínio rico que reflita fielmente as regras e processos do negócio.

Definição: "DDD é uma abordagem para desenvolver software para necessidades complexas através de uma profunda conexão entre a implementação e os conceitos centrais do negócio." - Eric Evans


Conceitos Fundamentais

1. Ubiquitous Language (Linguagem Ubíqua)

Definição: Uma linguagem comum compartilhada entre desenvolvedores e especialistas do domínio.

Características:

  • Mesma terminologia no código e nas conversas
  • Nomes de classes, métodos e variáveis refletem o negócio
  • Documentação usa os mesmos termos
  • Reduz ambiguidade e mal-entendidos

Exemplo no Santa Mão:

// ❌ Evitar termos técnicos genéricos
public class Data { }
public class Manager { }

// ✅ Usar termos do domínio
public class Request { }  // Solicitação de serviço
public class Job { }      // Categoria de serviço
public class Order { }    // Pedido confirmado

2. Bounded Context (Contexto Delimitado)

Definição: Um limite explícito dentro do qual um modelo de domínio é definido e aplicável.

No Santa Mão:

Bounded Context

3. Domain Model (Modelo de Domínio)

Definição: Representação conceitual do domínio que incorpora tanto comportamento quanto dados.

Componentes:

  • Entities (Entidades)
  • Value Objects (Objetos de Valor)
  • Aggregates (Agregados)
  • Domain Services (Serviços de Domínio)
  • Domain Events (Eventos de Domínio)

Building Blocks do DDD

1. Entities (Entidades)

Definição: Objetos definidos pela sua identidade, não pelos seus atributos.

Características:

  • Possuem identidade única (ID)
  • Podem mudar de estado ao longo do tempo
  • Comparados pela identidade, não pelos atributos

Exemplo:

public class User : Entity
{
    public Guid Id { get; private set; }
    public Name Name { get; private set; }
    public Email Email { get; private set; }
    public Status Status { get; private set; }

    private User() { }

    public static User Create(string firstName, string lastName, string email)
    {
        var user = new User
        {
            Id = Guid.NewGuid(),
            Name = new Name(firstName, lastName),
            Email = new Email(email),
            Status = Status.Active
        };

        user.AddDomainEvent(new UserCreatedEvent(user.Id));
        return user;
    }
}

2. Value Objects (Objetos de Valor)

Definição: Objetos sem identidade conceitual, definidos apenas pelos seus atributos.

Características:

  • Imutáveis
  • Comparados pelos valores, não pela identidade
  • Podem ser compartilhados
  • Sem efeitos colaterais

Exemplo:

public class Email : ValueObject
{
    public string Value { get; private set; }

    public Email(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("Email cannot be empty");

        if (!IsValidEmail(value))
            throw new ArgumentException("Invalid email format");

        Value = value.ToLower();
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

public class Money : ValueObject
{
    public decimal Amount { get; private set; }
    public string Currency { get; private set; }

    public Money(decimal amount, string currency = "BRL")
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative");

        Amount = amount;
        Currency = currency;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
}

3. Aggregates (Agregados)

Definição: Cluster de objetos de domínio tratados como uma única unidade para mudanças de dados.

Características:

  • Possui um Aggregate Root (raiz do agregado)
  • Define limites de consistência
  • Acesso externo apenas pela raiz
  • Transações limitadas ao agregado

Exemplo:

public class User : AggregateRoot
{
    public Guid Id { get; private set; }
    public Name Name { get; private set; }

    private readonly List<UserAddress> _addresses = new();
    public IReadOnlyCollection<UserAddress> Addresses => _addresses.AsReadOnly();

    public void AddAddress(UserAddress address)
    {
        if (_addresses.Count >= 5)
            throw new DomainException("User cannot have more than 5 addresses");

        if (_addresses.Count == 0)
            address.SetAsDefault();

        _addresses.Add(address);
        AddDomainEvent(new UserAddressAddedEvent(Id, address.Id));
    }
}

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

Definição: Operações do domínio que não pertencem naturalmente a uma entidade ou value object.

Quando usar:

  • Operação envolve múltiplos agregados
  • Lógica de negócio não pertence a nenhuma entidade específica
  • Cálculos complexos ou algoritmos de domínio

Exemplo:

public interface IJobPricingService
{
    Money CalculatePrice(Job job, JobTask task);
}

public class JobPricingService : IJobPricingService
{
    public Money CalculatePrice(Job job, JobTask task)
    {
        var basePrice = task.BasePrice;
        // Lógica de cálculo com multiplicadores
        return basePrice;
    }
}

5. Domain Events (Eventos de Domínio)

Definição: Algo significativo que aconteceu no domínio.

Características:

  • Representam fatos passados (UserCreated, OrderPlaced)
  • Imutáveis
  • Usados para comunicação entre agregados

Exemplo:

public class UserCreatedEvent : DomainEvent
{
    public Guid UserId { get; }
    public string Email { get; }
    public DateTime OccurredOn { get; }

    public UserCreatedEvent(Guid userId, string email)
    {
        UserId = userId;
        Email = email;
        OccurredOn = DateTime.UtcNow;
    }
}

Nota: Para detalhes sobre como eventos são publicados e consumidos, veja EDA Pattern e Outbox Pattern.

6. Repositories (Repositórios)

Definição: Abstração para persistência de agregados.

Características:

  • Um repositório por aggregate root
  • Encapsula acesso a dados
  • Trabalha com agregados completos
  • Interface definida no domínio, implementação na infraestrutura

Exemplo:

public interface IUserRepository
{
    Task<User> GetByIdAsync(Guid id);
    Task<User> GetByEmailAsync(string email);
    Task AddAsync(User user);
    Task UpdateAsync(User user);
}

Padrões Táticos

1. Aggregate Pattern

Regras:

  • Referência externa via ID, não objeto
  • Um repositório por agregado
  • Transações não devem cruzar agregados
  • Invariantes apenas dentro do agregado

2. Factory Pattern

Quando usar: Criação complexa de agregados com validações extensas

public static User CreateFromRegistration(string firstName, string lastName, string email)
{
    // Lógica de criação e validação
    return User.Create(firstName, lastName, email);
}

3. Specification Pattern

Quando usar: Regras de negócio complexas e validações reutilizáveis

public class UserIsActiveSpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User user)
    {
        return user.Status == Status.Active;
    }
}

Camadas da Arquitetura

Estrutura em Camadas

Arquitetura em Camadas

Dependências

  • Presentation → Application → Domain
  • Application → Domain
  • Infrastructure → Application
  • Domain → Nada (núcleo independente)

Boas Práticas no Santa Mão

1. Sempre Valide no Domínio

// ✅ Bom
public class User
{
    public void UpdateEmail(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
            throw new DomainException("Email cannot be empty");

        Email = new Email(email);
    }
}

// ❌ Ruim - validação só na aplicação
public class UpdateUserCommandHandler
{
    public async Task Handle(UpdateUserCommand request)
    {
        if (string.IsNullOrWhiteSpace(request.Email))
            return; // Validação fora do domínio!

        user.Email = request.Email;
    }
}

2. Encapsule Coleções

// ✅ Bom
private readonly List<UserAddress> _addresses = new();
public IReadOnlyCollection<UserAddress> Addresses => _addresses.AsReadOnly();

// ❌ Ruim
public List<UserAddress> Addresses { get; set; }

3. Use Factory Methods

// ✅ Bom
public static User Create(string firstName, string lastName)
{
    var user = new User();
    // Lógica de inicialização
    user.AddDomainEvent(new UserCreatedEvent(user.Id));
    return user;
}

// ❌ Ruim
var user = new User { FirstName = "John", LastName = "Doe" };

4. Mantenha Invariantes

public void AddAddress(UserAddress address)
{
    // Invariante: máximo 5 endereços
    if (_addresses.Count >= 5)
        throw new DomainException("Maximum addresses exceeded");

    _addresses.Add(address);
}

Anti-Patterns a Evitar

1. Anemic Domain Model

Evitar: Entidades apenas com getters/setters

// Anêmico - apenas dados
public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

Correto: Modelo rico com comportamento

public class User
{
    public Name Name { get; private set; }
    public Email Email { get; private set; }

    public void UpdateName(string firstName, string lastName)
    {
        Name = new Name(firstName, lastName);
        AddDomainEvent(new UserNameUpdatedEvent(Id));
    }
}

2. Domain Logic Leakage

Evitar: Lógica de domínio fora do domínio

// Na Application Layer
if (user.Status == Status.Active && !user.IsDeleted)
{
    // Lógica de negócio no handler!
}

Correto: Lógica no domínio

// No Domain
public bool CanPerformAction()
{
    return Status == Status.Active && !IsDeleted;
}

3. Aggregate Boundaries Errados

Evitar: Agregado muito grande

public class User
{
    public List<Order> Orders { get; set; }  // Order deveria ser agregado separado!
    public List<Request> Requests { get; set; }  // Request deveria ser agregado separado!
}

Correto: Agregados independentes

public class User
{
    // User é um agregado
}

public class Order
{
    public Guid UserId { get; set; }  // Referência via ID
}

Benefícios do DDD

✅ Código reflete fielmente o negócio
✅ Regras de negócio centralizadas e protegidas no domínio
✅ Comunicação melhorada entre desenvolvedores e especialistas do negócio
✅ Código expressivo e de fácil manutenção
✅ Mudanças nos requisitos incorporadas de forma natural

Quando Usar e Quando NÃO Usar

✅ Usar DDD Quando:

  • Domínio complexo com muitas regras de negócio
  • Projeto de longo prazo que evoluirá
  • Equipe tem acesso a especialistas do domínio
  • Múltiplos contextos bounded precisam interagir
  • Valor do negócio justifica o investimento inicial

❌ NÃO Usar DDD Quando:

  • CRUD simples sem lógica de negócio
  • Projeto pequeno ou temporário
  • Sem acesso a especialistas do domínio
  • Equipe sem experiência em DDD
  • Deadline muito curto

Relacionamento com Outros Padrões

DDD trabalha em conjunto com:

  • CQRS - Aggregates executam Commands e geram eventos, Queries leem sem afetar o domínio
  • EDA - Domain Events são publicados quando estado do Aggregate muda
  • Outbox Pattern - Domain Events são persistidos de forma confiável

Fluxo Integrado:

Aggregate Root → Domain Event → Outbox → Redpanda → Workers

Conclusão

DDD no Santa Mão é mais que padrões técnicos - é uma filosofia de desenvolvimento que coloca o negócio em primeiro lugar.

Resultados Alcançados:

✅ Modelagem fiel do negócio no código
✅ Regras de negócio centralizadas no domínio
✅ Código expressivo e manutenível
✅ Comunicação efetiva entre técnicos e especialistas do negócio

A implementação consistente de DDD em todo o projeto garante que o código reflita o negócio e que mudanças nos requisitos possam ser incorporadas de forma natural e sustentável.


Última atualização: Novembro 2025
Maintainers: Equipe de Arquitetura Santa Mão