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

Arquitetura de Software em C# no Mundo Real: Capítulo 1 – Os caminhos que a arquitetura pode tomar

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 1 – Os caminhos que a arquitetura pode tomar

Se você é como eu, provavelmente já passou por aquele momento de desconforto olhando para um serviço gigante cheio de if, validações repetidas e queries SQL pingando direto do controller. A boa notícia é: você não está sozinho. A má notícia? Isso provavelmente é (ou já foi) um Transaction Script disfarçado.

Quando comecei a desenvolver em .NET — ainda nos tempos de ASP clássico migrando para WebForms — minha arquitetura era movida a "deu certo, então tá bom". Mas com o tempo, fui percebendo que existiam formas diferentes de organizar código. E mais importante: algumas escalavam bem, outras viravam monstros com o tempo.

Neste capítulo, quero fazer um panorama real e direto de quatro abordagens arquiteturais que influenciam fortemente como pensamos o design de uma aplicação: Transaction Script, Modelo Anêmico, Modelo Rico e DDD (Domain-Driven Design). Sem floreio. Só o que realmente importa.


Transaction Script – O procedural disfarçado de OOP

O Transaction Script é simples. Funciona bem para sistemas pequenos. A lógica de negócio está basicamente em métodos: um por operação. Criar cliente? Tem um método. Fazer um pagamento? Outro método. Tudo acontece dentro do service — ou pior, do controller.

public void Transfer(string fromAccount, string toAccount, decimal amount)
{
    var debitAccount = _db.Accounts.Single(a => a.Number == fromAccount);
    var creditAccount = _db.Accounts.Single(a => a.Number == toAccount);

    if (debitAccount.Balance < amount)
        throw new Exception("Insufficient funds");

    debitAccount.Balance -= amount;
    creditAccount.Balance += amount;

    _db.SaveChanges();
}

Funciona? Sim. Escala? Não. Essa abordagem acopla tudo: validações, persistência e regras de negócio. À medida que o sistema cresce, o número de scripts explode, e manter coerência entre eles vira um inferno.


Modelo Anêmico – O DTO vestido de entidade

É aqui que muitos de nós fomos treinados a cair: o tal do Modelo Anêmico. Você tem uma classe Account, mas ela só tem propriedades. Toda a lógica está no service.

public class Account
{
    public string Number { get; set; }
    public decimal Balance { get; set; }
}

A regra de negócio? Está num AccountService. Ou pior: num AccountManager, AccountHandler, ou algum outro nome genérico.

O problema é claro: violação do encapsulamento. As entidades não se protegem. Qualquer parte do sistema pode alterar o Balance diretamente. Se alguém esquecer de validar antes de debitar, o sistema aceita — e o bug aparece em produção, claro.


Modelo Rico – Entidades com responsabilidade

Aqui a coisa melhora. No Modelo Rico, a entidade é responsável por manter sua integridade. Regras de negócio vivem dentro da própria entidade.

public class Account
{
    public string Number { get; private set; }
    public decimal Balance { get; private set; }

    public void TransferTo(Account destination, decimal amount)
    {
        if (Balance < amount)
            throw new InvalidOperationException("Insufficient funds");

        this.Balance -= amount;
        destination.Balance += amount;
    }
}

É um passo além. A lógica agora está onde deveria estar. Testar essa entidade é fácil. Ela tem coesão e encapsula comportamento. Só que, em sistemas mais complexos, com múltiplos bounded contexts, integrações, consistência eventual e orquestração de regras, isso ainda pode não ser suficiente.


Domain-Driven Design (DDD) – Modelo Rico com propósito

O Domain-Driven Design vai além. Ele começa com a premissa de que software é uma ferramenta para resolver problemas de domínio — e que o conhecimento de domínio deve guiar o design do sistema.

Com DDD, não se trata apenas de ter entidades ricas. Trata-se de entender o contexto em que elas vivem, de dividir a aplicação em bounded contexts, de criar uma ubiquitous language entre devs e stakeholders.

Você não implementa DDD só criando um AggregateRoot ou usando MediatR. DDD é sobre conversar com o time de negócio e refletir essas conversas no código.

É aqui que entram termos como:

  • Entities com identidade persistente;
  • Value Objects imutáveis que encapsulam regras;
  • Aggregates que mantêm consistência;
  • Domain Events que refletem mudanças importantes;
  • Repositories que respeitam os limites do domínio;
  • Application Layer que orquestra casos de uso sem violar o domínio.

Sim, é mais complexo. Mas em sistemas que lidam com regras complicadas (como finanças, logística, compliance), é aí que a clareza e a resiliência aparecem.


Conclusão

Eu vejo essas abordagens como estágios de maturidade. Todas têm seu lugar — e não existe bala de prata. Já usei Transaction Script para ferramentas simples, Modelo Rico para APIs limpas, e DDD para sistemas onde o domínio precisava respirar no código.

O importante é saber por que você está escolhendo uma abordagem.
E mais importante ainda: saber quando mudar.


Próximo capítulo:
Vamos explorar as limitações do Modelo Anêmico em projetos reais — e como ele pode nos trair quando menos esperamos.

Carregando publicação patrocinada...
5

Acabei de ler todos os capítulos - anotando para repassar em alguns conteúdos novamente. Devo ter deixado umas 20 TabCoins hehe.

Parabéns pelo conteúdo, com certeza foram dias e até semanas escrevendo e buscando material, e anos de estudo, prática e muita frustração. Irei enviar aos meus colegas para melhorar a codebase também huehue.

Uma dica para a experiência da leitura, adicionar os links para os capítulos no índice, senti um pouco a falta disso.

2

Pô, que comentário bacana! De verdade, obrigado por ter lido tudo e ainda mais por deixar um feedback tão sincero. Fico feliz demais que o conteúdo tenha feito sentido a ponto de querer compartilhar com o time.

Sim, eu venho guardando notas sobre experiências e frustrações há bastante tempo. Coisas que vivi, revi e discuti com outros devs. Resolvi finalmente compilar tudo isso nessa série de artigos, com o tom mais direto que eu gostaria de ter lido anos atrás.

Sobre o índice com links: cara, eu pensei nisso também! Só que no meio do fluxo acabei deixando pra depois… e esse depois chegou agora. Vou atualizar pra deixar a navegação mais fluida, valeu pelo empurrão.

Valeu demais pelas TabCoins, pelas palavras e pela leitura com intenção. Esse tipo de retorno faz tudo valer.

4
2
3