“`html
Dominando a Arquitetura de C#
Eu desenho projetos em C# para que a mudança custe menos do que manter. Aqui vai um guia técnico, direto ao ponto,
para você dominar as decisões de arquitetura: organização de camadas, dependências, contratos, testes e evolução do código.
1 Comece pela regra de dependências (e não pelo padrão)
Arquitetura em C# não é “encaixar um padrão”. É definir quem depende de quem e o que pode atravessar
os limites do sistema. Quando essa regra está clara, o resto flui.
- Domínio primeiro: regras de negócio devem ficar no centro e não depender da infraestrutura.
- Infra por último: EF Core, gateways HTTP, filas e persistência ficam nas bordas.
- UI por último: controllers/endpoints chamam o application layer, nunca o contrário.
- Direção única: evite “loops” (projetos A referenciam B e B referenciam A).
responsabilidades (ou crio contratos) até que a dependência faça sentido.
2 Estruture em camadas (com contratos explícitos)
A separação por camadas evita que um lugar do código vire “tudo e nada”. Um desenho típico que funciona bem em C#:
Domain → Application → Infrastructure → API.
- Domain: entidades, value objects, enums de domínio, agregados e regras puras.
- Application: use cases (casos de uso), orquestração, validações de fluxo e contratos.
- Infrastructure: EF Core, serialização específica, clientes externos, persistência e implementações de gateways.
- API: endpoints, mapeamento HTTP, autenticação/autorizações e composição.
Para que isso não vire “arquitetura de pasta”, eu garantO que o contrato vive no lugar certo:
interfaces que o Application precisa são declaradas no Application (ou em um projeto Contracts), e a Infrastructure implementa.
3 Domine a modelagem: agregados, invariantes e boundaries
O que diferencia uma boa arquitetura em C# é a coerência do modelo. Se o domínio é frágil, o resto vira remendo.
Eu trato modelagem como parte do design arquitetural.
- Agregados: defino limites transacionais por agregado. Tudo que viola invariantes acontece dentro dele.
- Invariantes: regras que nunca podem quebrar ficam no método que modifica estado.
- Value Objects: quando igualdade e validação são essenciais, eu uso value objects para impedir “lixo” no domínio.
- Evite anemia: entidades não podem virar apenas containers de dados com validação na camada de fora.
Em seguida, eu faço boundaries no Application. Casos de uso viram operações com entrada/saída claras,
e o domínio decide o que é possível.
provavelmente está tentando manter invariantes fora do domínio.
4 Testes e evolução: acoplamento mínimo e feedback rápido
Arquitetura sem teste é só esperança. Eu organizo a estratégia pensando em custo e feedback:
- Domain tests: rápido, sem infraestrutura. Valide invariantes e regras puras.
- Application tests: use casos de uso com implementações fake/stub de gateways.
- Integration tests: EF Core e infraestrutura com ambientes controlados, focando em contratos de persistência.
- API tests: valide contrato HTTP (status codes, validações e serialização), sem duplicar lógica do domínio.
Para manter acoplamento mínimo, eu levo a sério o que passa entre camadas:
DTOs de entrada/saída e interfaces de persistência/gateways.
Evite expor entidades do domínio diretamente para a API quando isso quebrar o contrato.
+ Exemplo prático: use case com contrato de repositório
A ideia é simples: Application orquestra, Domain valida/invaria, Infrastructure implementa e API compõe.
Abaixo um exemplo com um caso de uso e um repositório como contrato.
// ========= Domain =========
public sealed class Pedido
{
private Pedido() { }
public Guid Id { get; private set; }
public decimal Total { get; private set; }
public static Pedido Criar(Guid id, decimal total)
{
if (total <= 0) throw new ArgumentOutOfRangeException(nameof(total), "Total deve ser maior que zero.");
return new Pedido
{
Id = id,
Total = total
};
}
}
// ========= Application (contrato) =========
public interface IPedidoRepository
{
Task<Pedido?> BuscarPorIdAsync(Guid id, CancellationToken ct);
Task SalvarAsync(Pedido pedido, CancellationToken ct);
}
public sealed record CriarPedidoCommand(Guid Id, decimal Total);
// ========= Application (use case) =========
public sealed class CriarPedidoHandler
{
private readonly IPedidoRepository _repo;
public CriarPedidoHandler(IPedidoRepository repo) => _repo = repo;
public async Task<Guid> Handle(CriarPedidoCommand cmd, CancellationToken ct)
{
// O domínio garante invariantes: se Total for inválido, explode aqui (com mensagem clara).
var pedido = Pedido.Criar(cmd.Id, cmd.Total);
await _repo.SalvarAsync(pedido, ct);
return pedido.Id;
}
}
// ========= Infrastructure (implementa contrato) =========
// Exemplo conceitual (EF Core costuma ficar aqui):
// public sealed class PedidoRepository : IPedidoRepository { ... }
// ========= API (composição) =========
// O controller chama o Handler, não acessa EF/infra direto.
Quer continuar evoluindo sua base em C#?
Agora que você tem as decisões centrais de arquitetura em mãos, o próximo passo é aprofundar em organização de projeto,
testes e boas práticas de camadas. Leia também outros posts no yurideveloper.com e aplique imediatamente no seu código.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!