Comece pelas mudanças, não pelas pastas

Antes de criar projetos, pergunte o que precisa mudar de forma independente. Regras de negócio, protocolo HTTP e acesso ao banco têm motivos diferentes para mudar. Separá-los pode limitar o impacto de uma alteração; separar cada classe em seu próprio projeto, por outro lado, costuma adicionar atrito sem criar uma fronteira útil.

Uma estrutura inicial para uma API de pedidos pode ser:

src/
  Pedidos.Domain/
  Pedidos.Application/
  Pedidos.Infrastructure/
  Pedidos.Api/
tests/
  Pedidos.Domain.Tests/
  Pedidos.Application.Tests/
  Pedidos.Infrastructure.Tests/

Os nomes são convenções, não exigências. Um sistema pequeno pode começar com menos projetos e crescer quando as fronteiras ficarem claras. O mais importante é decidir quem pode conhecer quem.

O que pertence a cada projeto?

Domain: conceitos e regras de negócio

Pedidos.Domain contém conceitos como Pedido, ItemPedido, Dinheiro e estados válidos. Aqui vivem invariantes que precisam continuar corretas independentemente de a entrada vir de HTTP, de uma fila ou de uma tarefa agendada. Evite referências a ASP.NET Core, EF Core ou SDKs externos neste projeto.

Application: casos de uso

Pedidos.Application coordena intenções como CriarPedido e ConfirmarPedido. Define comandos e resultados, consulta ou altera o domínio e declara portas que representam as operações externas necessárias. Não deve conter detalhes de rota, SQL ou serialização JSON.

Infrastructure: implementações concretas

Pedidos.Infrastructure implementa persistência, mensageria e integrações. Pode conhecer EF Core e um provedor de banco, porque esses são detalhes que ficam na borda. Ela implementa contratos definidos para atender às necessidades do núcleo, em vez de fazer o domínio importar o ORM.

Api: entrada e composição

Pedidos.Api configura ASP.NET Core, autenticação, endpoints, DTOs HTTP e injeção de dependência. O controller converte a requisição em uma chamada de caso de uso e traduz o resultado para HTTP. Ele não deve decidir sozinho regras como se um pedido pode ser cancelado.

Como devem apontar as referências?

Uma regra prática é fazer as dependências de compilação apontarem para as políticas mais estáveis:

Pedidos.Application  -> Pedidos.Domain
Pedidos.Infrastructure -> Pedidos.Application
Pedidos.Infrastructure -> Pedidos.Domain
Pedidos.Api -> Pedidos.Application
Pedidos.Api -> Pedidos.Infrastructure (somente composição)

A infraestrutura referencia os contratos que implementa; a aplicação não referencia infraestrutura. A API pode conhecer a infraestrutura no ponto de inicialização para registrar implementações concretas, mas controllers devem depender dos casos de uso, não de um contexto de banco. Esse desenho aplica a Dependency Rule da Clean Architecture.

Não é necessário que todas as dependências sejam interfaces. Crie uma abstração quando ela expressa uma fronteira necessária, protege uma política de um detalhe variável ou representa um contrato real. Para aprofundar os princípios por trás dessa decisão, consulte SOLID na prática.

Organize código por funcionalidade quando ajudar

Dentro de Application, uma pasta com dezenas de serviços genéricos pode dificultar encontrar o fluxo de um pedido. É possível agrupar por funcionalidade:

Pedidos.Application/
  Pedidos/
    CriarPedido/
      CriarPedidoCommand.cs
      CriarPedidoHandler.cs
      CriarPedidoResult.cs
    CancelarPedido/
      CancelarPedidoCommand.cs
      CancelarPedidoHandler.cs

Essa organização aproxima arquivos que mudam juntos e mantém cada intenção visível. Não transforme isso em regra rígida: se há poucos casos de uso, a estrutura pode ser mais simples. Uma pasta deve ajudar a localizar e compreender comportamento, não apenas satisfazer uma convenção.

Testes seguem as fronteiras

Teste invariantes do domínio sem subir infraestrutura. Teste os casos de uso com implementações controladas das portas para cobrir decisões e resultados. Depois, use testes de integração para verificar que EF Core, migrations e consultas funcionam com o banco escolhido. Um mock do repositório não prova que o SQL está correto; um banco real não é necessário para cada regra pura.

Quando criar — ou não criar — outro projeto

Crie um novo projeto quando ele estabelece uma fronteira que vale proteger: ciclo de vida diferente, tecnologia substituível, equipe responsável ou dependências que precisam ser restringidas. Evite criar projetos só porque um diagrama tem quatro caixas. Cada projeto aumenta configuração, referências, builds e conceitos que a equipe precisa manter.

Em domínios mais complexos, conceitos e limites de negócio podem orientar a divisão em módulos. DDD ajuda a descobrir esses limites por meio de linguagem ubíqua e contextos delimitados; não implica automaticamente transformar cada contexto em microsserviço.