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

Arquitetura de Software em C# no Mundo Real: Capítulo 2 – O modelo anêmico vai te trair (e como saber quando fugir dele)

Essa série de artigos nasceu da frustração — e da prática.

Se você já tentou organizar um sistema .NET complexo, sabe que não é fácil: cada time tem uma visão diferente, os tutoriais se contradizem, e os “padrões” muitas vezes atrapalham mais do que ajudam. Entre um GenericRepository<T> e um PaymentServiceHandlerProcessorFactory, fica difícil saber o que realmente importa.

Aqui, a proposta é simples: falar sobre arquitetura real, com um olhar crítico, direto e sem medo de questionar modinhas. Vamos explorar estilos, padrões, anti-padrões e armadilhas comuns que se acumulam ao longo dos anos — e mostrar alternativas que funcionam fora do slide de apresentação.

Essa série é para desenvolvedores que já sujaram as mãos. Que já herdaram legado, quebraram produção, e tentaram explicar DDD em reunião de grooming.

Sem fórmulas mágicas. Sem purismo. Só código, contexto e experiência real.


Índice da Série

Capítulo 1 – Os caminhos que a arquitetura pode tomar

Um panorama direto e sem rodeios sobre Transaction Script, Modelo Anêmico, Modelo Rico e DDD.

Capítulo 2 – O modelo anêmico vai te trair (e como saber quando fugir dele)

Quando usar um modelo anêmico parece mais simples, mas te afunda em bugs e complexidade acidental.

Capítulo 3 – A falsa promessa do “service pattern” genérico

AccountService, UserManager, FooHandler: nomes que escondem baixa coesão e design acoplado.

Capítulo 4 – Entidades ricas: encapsulamento é mais que private set;

Como construir entidades com comportamento real e proteger sua integridade.

Capítulo 5 – A camada de aplicação não é um lixo eletrônico

Application Services devem orquestrar, não conter regras de negócio nem virar dumping ground de dependências.

Capítulo 6 – Use Cases, Command Handlers e MediatR: o que separa clareza de confusão

Quando aplicar CQRS traz benefícios reais — e quando só adiciona complexidade desnecessária.

Capítulo 7 – Repositórios que respeitam o domínio (e evitam vazamento de infra)

Um repositório não é só um wrapper de DbContext. É um contrato do domínio com a persistência.

Capítulo 8 – Domínio expressivo: Value Objects, Invariantes e Eventos de Domínio

Modelando regras com segurança e intenção. O domínio deve falar por si.

Capítulo 9 – Dividindo para conquistar: a importância dos Bounded Contexts

DDD real exige saber onde termina um modelo e começa outro. E como manter essa separação.

Capítulo 10 – Anti-patterns arquiteturais que todo dev .NET já viu (ou cometeu)

Um olhar honesto sobre os maiores pecados que já cometemos em nome da “arquitetura”.

Capítulo 11 – Mitos e Más Interpretações na Arquitetura .NET

Por que MVC não é arquitetura, repositório não é para toda entidade, padrão não é enfeite, e SOLID não é carta branca pra travar refatoração.

Capítulo 2 – O modelo anêmico vai te trair (e como saber quando fugir dele)

Se tem uma armadilha que parece inofensiva, mas que já derrubou muito dev bom, é o tal do modelo anêmico.
Sabe aquela entidade que tem Id, Name, CreatedAt… e nada mais? Parece um DTO de banco de dados disfarçado de domínio.
E é exatamente isso que ele é.

Eu sei que você já escreveu algo assim. Eu também. Às vezes por pressa. Às vezes por convenção. E muitas vezes por não querer “complicar demais” no começo do projeto.

Só que o barato sai caro.


O que é um modelo anêmico — e por que ele é um problema

No fundo, o modelo anêmico é uma negação do paradigma orientado a objetos.
Ele é um recipiente de dados que depende de uma outra classe (Service) para executar qualquer coisa de relevante.
É como ter um carro que precisa de um mecânico do lado pra ligar o motor.

public class PaymentRequest
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    public PaymentStatus Status { get; set; }
}

Você bate o olho e pensa: “Ok, simples, direto”.

Mas aí começa a surgir isso aqui:

public class PaymentRequestService
{
    public void Approve(PaymentRequest request)
    {
        if (request.Amount <= 0)
            throw new InvalidOperationException("Invalid amount");

        request.Status = PaymentStatus.Approved;
    }
}

Veja o problema: nada impede outro pedaço de código de fazer isso diretamente:

request.Status = PaymentStatus.Approved; // sem validação, sem regra

Você perdeu o controle. Toda a lógica de domínio ficou solta, espalhada, e pior: vulnerável a ser ignorada.


Sintomas clássicos de um modelo anêmico

Se você identificar esses sinais no seu código, acenda o alerta:

  • Entidades com apenas getters e setters públicos
  • Regras de negócio implementadas em services genéricos (XyzService)
  • Poucos ou nenhum método nas entidades
  • Validações feitas via if em todo lugar
  • Testes unitários testando services e não o domínio
  • Entities sem construtores ou com construtores vazios

Se parece com um DTO, age como um DTO, e precisa de services para funcionar… é um DTO.
E seu “modelo de domínio” está morto por dentro.


“Mas assim é mais simples…”

Sim. No começo.

Projetos pequenos sobrevivem bem com modelo anêmico.
O problema aparece quando:

  • O sistema cresce
  • A regra de negócio muda
  • Mais gente começa a mexer no código
  • Você precisa garantir integridade em fluxos assíncronos
  • Começa a duplicar lógica em vários services “porque ficou mais fácil copiar do que generalizar”

Você entra no ciclo de código frágil.
Refatorar se torna perigoso.
Reutilizar, impossível.
E bugs surgem por bypassar regras silenciosamente.


Qual o caminho, então?

Você não precisa cair direto no DDD hardcore.
O primeiro passo é colocar a lógica onde ela pertence.
E ela pertence à entidade.

public class PaymentRequest
{
    public Guid Id { get; private set; }
    public decimal Amount { get; private set; }
    public PaymentStatus Status { get; private set; }

    public PaymentRequest(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount must be positive");

        Id = Guid.NewGuid();
        Amount = amount;
        Status = PaymentStatus.Pending;
    }

    public void Approve()
    {
        if (Status != PaymentStatus.Pending)
            throw new InvalidOperationException("Cannot approve payment in current state");

        Status = PaymentStatus.Approved;
    }
}

Agora a regra está clara, encapsulada e testável.

var payment = new PaymentRequest(1000);
payment.Approve(); // ✅

payment.Approve(); // ❌ throws exception

Esse é o começo de um modelo rico, onde a entidade protege sua consistência.


Testes mais naturais, código mais coeso

Testar uma entidade rica é direto:

[Fact]
public void Should_Throw_When_Approving_Twice()
{
    var payment = new PaymentRequest(1000);
    payment.Approve();

    Should.Throw<InvalidOperationException>(() => payment.Approve());
}
  • Você não precisa de mock.
  • Não depende de infraestrutura.
  • E pode evoluir o domínio com confiança.

Quando o modelo anêmico ainda pode ser aceitável?

Nem todo projeto precisa de um domínio expressivo.
Cenários onde o modelo anêmico é tolerável:

  • APIs CRUD sem regras de negócio
  • Ferramentas internas de curto prazo
  • Sistemas onde a fonte de verdade é um sistema externo e você só espelha dados

Mesmo nesses casos, isolar o domínio da infra ainda é uma boa prática.


Conclusão

O modelo anêmico é confortável.
Mas conforto é diferente de segurança.

Se você está construindo um sistema com regras complexas, com múltiplos times e onde bugs custam caro — você vai pagar esse preço se continuar tratando seu domínio como um DTO com nome bonito.


Próximo capítulo:
Vamos olhar com lupa para os “services” genéricos que nascem do modelo anêmico e entender por que eles acabam virando depósitos de lógica sem identidade.

Spoiler: XyzService é um cheiro. E dos fortes.

Carregando publicação patrocinada...
1