Executando verificação de segurança...
3

Três Olhares sobre Arquitetura de Software: Fowler, Evans e Uncle Bob: 1.3 - Domain Model: o coração da arquitetura em três vozes

Capa

Nos últimos vinte anos, três nomes se tornaram referência inevitável sempre que falamos sobre arquitetura de software: Martin Fowler, Eric Evans e Robert C. Martin. Cada um deles trouxe uma perspectiva única, mas complementar, para o ofício de projetar sistemas duradouros. Fowler, com seus padrões de aplicação empresarial, nos ofereceu um catálogo de soluções pragmáticas para problemas recorrentes. Evans, com o Domain-Driven Design, nos mostrou como aproximar o software da linguagem do negócio e como dar forma ao que realmente importa em um sistema: o domínio. E Uncle Bob, com a Clean Architecture, reforçou os princípios e as camadas que protegem esse domínio da corrosão do tempo, dos frameworks e da infraestrutura.

Esta série de artigos nasce do desejo de cruzar esses três olhares. A cada padrão descrito por Fowler, vamos buscar o eco que ele encontra em Evans e em Martin. Em alguns momentos, haverá convergência; em outros, choque. Mas em todos, a discussão nos ajuda a entender não apenas o “como” programar, mas o “porquê” de certas escolhas arquiteturais.

Vamos usar exemplos em C#, trazer referências aos livros originais e sempre terminar com uma reflexão sobre quando e por que adotar (ou evitar) cada padrão. A ideia não é canonizar um estilo, mas oferecer um mapa de raciocínio para que você, como engenheiro de software, tenha mais clareza ao tomar suas decisões.


Índice da Série

Parte 1 – A lógica do domínio

Parte 2 – Persistência e Infraestrutura

Parte 3 – Apresentação e Integração

1.3 Domain Model: o coração da arquitetura em três vozes

Introdução

Quando Martin Fowler apresenta o Domain Model, ele o descreve como uma ruptura com os estilos anteriores. Diferente do Transaction Script, onde a lógica fica espalhada em funções procedurais, ou do Table Module, onde as tabelas ditam o ritmo do sistema, o Domain Model traz a lógica de negócio para dentro de objetos ricos em comportamento.

Eric Evans, em Domain-Driven Design (2003), transforma essa ideia em filosofia: o domínio deve ser o centro do software, sustentado por uma Linguagem Ubíqua que conecta especialistas de negócio e desenvolvedores.

Robert C. Martin, no clássico Clean Architecture (2017), dá o passo seguinte: proteger esse núcleo de regras de negócio contra a corrosão causada por frameworks, bancos de dados e interfaces. Para ele, a arquitetura deve “gritar” o domínio, e não detalhes técnicos.

Assim, o Domain Model se torna o ponto de convergência dos três autores: Fowler nos dá o padrão, Evans a metodologia de modelagem, e Uncle Bob a disciplina para preservar esse núcleo ao longo do tempo.


Modelando a variabilidade no coração do domínio

Um dos maiores desafios em modelos de domínio é lidar com variabilidade de regras de negócio. Em qualquer sistema real, certas lógicas mudam conforme o contexto: cálculo de frete, políticas de desconto, métodos de pagamento.

É justamente nesses cenários que Fowler recomenda o uso de padrões como o Strategy. Evans também reforça essa prática, mostrando como padrões clássicos se integram de forma natural ao modelo de domínio. Já Uncle Bob enxerga nisso a aplicação direta do Princípio Aberto/Fechado (OCP): nosso código deve permitir extensões sem exigir modificações constantes.

Exemplo prático: processamento de pagamentos

Vamos imaginar um e-commerce em que diferentes métodos de pagamento têm diferentes taxas. Essa lógica é naturalmente variável e, portanto, um terreno fértil para o uso do Strategy Pattern.

using System;
using System.Collections.Generic;
using System.Linq;

public interface IPaymentStrategy
{
    decimal CalculateFee(decimal amount);
}

// Concrete strategies
public class CreditCardPaymentStrategy : IPaymentStrategy
{
    public decimal CalculateFee(decimal amount) => amount * 0.025m;
}

public class PayPalPaymentStrategy : IPaymentStrategy
{
    public decimal CalculateFee(decimal amount) => (amount * 0.03m) + 0.30m;
}

public class BankTransferPaymentStrategy : IPaymentStrategy
{
    public decimal CalculateFee(decimal amount) => 0m;
}

// Value Object for money
public readonly record struct Money(decimal Amount, string Currency)
{
    public static Money operator +(Money a, Money b)
    {
        if (a.Currency != b.Currency)
            throw new InvalidOperationException("Currency mismatch.");
        return new Money(a.Amount + b.Amount, a.Currency);
    }
}

// Entity
public class Order
{
    private readonly List<OrderItem> _items = new();
    private readonly IPaymentStrategy _paymentStrategy;

    public Guid Id { get; private set; } = Guid.NewGuid();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    public Money Total => new(_items.Sum(i => i.Total.Amount), Currency);

    public string Currency { get; }

    public Order(IPaymentStrategy paymentStrategy, string currency)
    {
        _paymentStrategy = paymentStrategy ?? throw new ArgumentNullException(nameof(paymentStrategy));
        Currency = currency ?? throw new ArgumentNullException(nameof(currency));
    }

    public void AddItem(string productName, decimal unitPrice, int quantity)
    {
        _items.Add(new OrderItem(productName, new Money(unitPrice, Currency), quantity));
    }

    public Money CalculateTotalWithFees()
    {
        var fee = _paymentStrategy.CalculateFee(Total.Amount);
        return new Money(Total.Amount + fee, Currency);
    }
}

// Value Object for order item
public readonly record struct OrderItem(string ProductName, Money UnitPrice, int Quantity)
{
    public Money Total => new(UnitPrice.Amount * Quantity, UnitPrice.Currency);
}

Três olhares sobre o mesmo modelo

No exemplo do processamento de pagamentos, podemos enxergar nitidamente como os três autores se encontram. Fowler ficaria satisfeito em ver que a lógica de negócio — o cálculo das taxas e o total do pedido — está encapsulada dentro do próprio modelo, em vez de espalhada em controladores ou scripts utilitários. A escolha do Strategy dá ao modelo a maleabilidade necessária para acomodar novos métodos de pagamento sem virar um código rígido e frágil.

Evans, por sua vez, reconheceria de imediato o vocabulário do domínio presente nas nossas estruturas. O uso de Value Objects imutáveis como Money e OrderItem ecoa a linguagem dos especialistas do negócio, enquanto a entidade Order protege sua identidade e seus invariantes. O código conversa com o domínio de forma quase natural: falamos em pedidos, valores e taxas, e não em tabelas ou IDs de framework.

Já Uncle Bob apontaria para a pureza desse núcleo. O domínio não sabe nada sobre bancos de dados, ORMs ou APIs externas. Ele é testável por natureza e protegido contra detalhes de infraestrutura que mudam com o tempo. Assim, preservamos o que realmente interessa: as regras que definem o negócio.

Esse design permite evoluir o sistema sem comprometer o que já existe. Se amanhã surgir um novo método de pagamento (como criptomoedas), basta criar uma nova estratégia — sem alterar o domínio existente.


Conclusão

O Domain Model marca a transição de sistemas centrados em dados para sistemas centrados no negócio. Ele é o coração da arquitetura de software: Fowler deu-lhe forma, Evans deu-lhe vocabulário, e Martin deu-lhe resiliência.

Nosso exemplo mostrou como um simples padrão de projeto, aplicado dentro do domínio, pode resolver um problema de variabilidade sem sacrificar clareza ou estabilidade.

No próximo artigo, veremos a Service Layer, que se posiciona acima do modelo para orquestrar casos de uso sem poluir o domínio. Se o Domain Model é o coração, a Service Layer será o maestro que conduz a sinfonia.


Carregando publicação patrocinada...