Dominando a Arquitetura de C# com Boas Práticas e Padrões de Design

Dominando a Arquitetura de C# com Boas Práticas e Padrões de Design

“`html





Dominando a Arquitetura de C# — do desenho ao código

Arquitetura que escala no dia a dia

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).
Prática que eu sigo: se um projeto “baixo” precisa conhecer detalhes de um “alto”, eu redistribuo
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#:
DomainApplicationInfrastructureAPI.

  • 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.

O resultado é direto: você troca um provedor (ex.: banco) sem reescrever caso de uso, e seu domínio permanece estável.

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.

Regra prática: se você precisa de “ifs” espalhados por vários serviços para manter consistência,
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.

Quando a arquitetura está bem feita, mudar regra de negócio não exige “varrer” endpoints e reescrever persistência.

+ 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.



Ver outros posts



“`