Arquitetura de Software em C# no Mundo Real: Capítulo 5 – A camada de aplicação não é um lixo eletrônico
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 5 – A camada de aplicação não é um lixo eletrônico
A essa altura, já deixamos para trás os services genéricos e começamos a construir entidades que se comportam como o domínio espera.
Mas então vem aquela dúvida: onde orquestro tudo isso? Onde coloco o código que não pertence nem à entidade nem ao controller?
A resposta quase sempre é: "no Application Service".
E está certo. Em teoria.
Na prática, esse cara acaba virando um novo buraco negro: tudo que você não sabe onde colocar, vai parar ali.
ApplicationService virou o novo utils
Quantas vezes você viu algo assim?
public class TradeApplicationService
{
public async Task ApproveTrade(Guid tradeId)
{
var trade = await _repository.Get(tradeId);
if (trade.Status == TradeStatus.PendingApproval)
{
trade.Approve();
await _repository.Save(trade);
}
if (trade.Type == TradeType.Forex)
{
_logger.LogInfo("Forex trade approved");
}
if (trade.Amount > 1_000_000)
{
await _alertService.NotifyRisk();
}
}
}
Essa classe não está só orquestrando.
Ela está validando, decidindo, persistindo, logando e disparando alertas.
É o mesmo problema do XyzService, agora disfarçado de Application Service — com uma cara mais “arquiteturalmente correta”.
O que realmente pertence ao Application Service?
Application Service é orquestrador de caso de uso.
Ele chama o domínio. Ele não implementa as regras.
Imagine o caso de uso “fechar uma conta bancária”.
A responsabilidade do Application Service seria:
- Receber o comando (DTO ou Command)
- Carregar a entidade (
Account) - Chamar um método do domínio (
Close()) - Persistir as mudanças
- (Opcional) Publicar evento de domínio ou de integração
public class CloseAccountHandler
{
public async Task Handle(CloseAccountCommand command)
{
var account = await _repository.Get(command.AccountId);
account.Close(); // a regra está no domínio
await _repository.Save(account);
await _eventPublisher.Publish(new AccountClosed(account.Id));
}
}
Só isso.
Sem if.
Sem switch.
Sem validação de regra.
Sem log de negócio.
Sem business decisions.
“Mas e quando o domínio precisa de algo externo?”
Nem tudo pode ser encapsulado dentro da entidade. Às vezes, você precisa consultar um serviço externo (como um FxRateService) para tomar uma decisão.
Mas isso não significa que você deve entupir o handler com lógica.
A solução:
- Delegue a lógica para orquestradores especializados
- Ou use objetos de domínio que recebem colaboradores
Em DDD, chamamos isso de Domain Services.
Mas você pode dar nomes mais específicos como FundsAvailabilityChecker ou SettlementInstructionResolver.
A ideia é manter o Application Service fino e dar nomes às responsabilidades.
Você não quer mais que um dev leia 300 linhas de um handler
Você quer:
- Um comando que descreve a intenção (
ApprovePaymentCommand) - Um handler que invoca 2 ou 3 colaboradores claros
- Entidades que cuidam do próprio estado
- Regras explícitas e isoladas em serviços bem nomeados
Quando você lê o handler, deve entender o que acontece,
não como acontece.
Application Service como glue code... com responsabilidade
Sim, Application Services são cola.
Mas até cola tem qualidade.
- Se estiver espalhada, gruda tudo e quebra fácil.
- Se for modular, você troca peças com confiança.
Não tenha medo de criar:
- Pequenas classes auxiliares
- Handlers finos
- Objetos de comando
- Serviços de domínio
Isso não é overengineering. É clareza.
Conclusão
Application Services não devem conter lógica de negócio.
Eles devem invocar comportamentos do domínio, persistir mudanças e coordenar fluxos. Só.
Se você trata essa camada como dumping ground, você está apenas adiando a entropia.
Próximo capítulo:
Agora que temos entidades ricas e application services enxutos, o próximo passo natural é refinar a forma como lidamos com ações e comandos.
É aí que o padrão CQRS entra: separar leitura de escrita, clarear intenções e reduzir acoplamentos invisíveis.