Pular para o conteúdo principal

Boas Práticas e Decisões Técnicas

Decisões tomadas com base na documentação oficial da Microsoft e padrões consolidados da indústria.


Retorno de métodos: sem tuplas em APIs públicas

Por que evitar tuplas?

Tuplas ((string Name, int Age)) são problemáticas em métodos públicos porque:

  • Não são autodocumentadas — quem chama o método não sabe o significado de cada campo
  • Não podem ter validações ou comportamentos associados
  • Difíceis de evoluir sem quebrar código existente
  • Em entrevistas técnicas e code reviews, são um sinal negativo

O que usar no lugar:

SituaçãoTipo recomendadoExemplo
Dados de saída (DTO)recordpublic record ClientDto(Guid Id, string Name);
Resultado de operação com possível erroResult<T>Result<ClientDto>
Método privado simplesTupla é aceitávelvar (x, y) = GetCoords();

Records para DTOs (C# 9+, recomendado pela Microsoft):

// ✅ CORRETO — Record imutável, autodocumentado
public record CreateClientResponse(Guid Id, string Name, string Email);

// ❌ EVITAR — Tupla em método público
public (Guid, string, string) CreateClient(...) { ... }

Result<T> para Commands — retorna sucesso ou erro sem lançar exceções para fluxos esperados:

// ✅ CORRETO
public async Task<Result<CreateClientResponse>> Handle(CreateClientCommand command)
{
if (await _repository.ExistsAsync(command.Email))
return Result.Failure<CreateClientResponse>(new DuplicateError("E-mail já cadastrado"));

// ...
return Result.Success(new CreateClientResponse(client.Id, client.Name, client.Email));
}

Fonte: C# Records — Microsoft Learn | Best practices for exceptions


ORM: EF Core + Dapper (padrão híbrido)

A Microsoft documenta explicitamente o padrão híbrido para projetos CQRS + DDD:

"For the command side... you can use a full ORM like EF Core... For the query side, you can use a simpler approach like Dapper."

Microsoft .NET Microservices Architecture Guide

EF Core — lado de Commands (escrita)

Usado para todas as operações de escrita e domínio:

// HasQueryFilter garante isolamento multitenant em TODAS as queries
builder.HasQueryFilter(x => x.TenantId == _tenantContext.CurrentTenantId);

Vantagens para este projeto:

  • HasQueryFilter — filtro global de tenant (obrigatório no projeto)
  • Change Tracking — detecta mudanças em agregados automaticamente
  • Migrations — controle de schema versionado
  • Owned Entities — mapeia Value Objects como parte da mesma tabela
  • Compiled Queries — performance muito boa no EF Core 8+/10

Dapper — lado de Queries (leitura)

Adicionado quando o módulo Reports precisar de queries complexas:

// Dapper para projeções de leitura — SQL direto, sem overhead de tracking
var result = await connection.QueryAsync<ClientSummaryDto>(
"SELECT c.Id, c.Name, COUNT(d.Id) as DealCount FROM Clients c ...",
new { TenantId = tenantId });

Quando adicionar Dapper: ao implementar o módulo Reports (queries de dashboard, agregações, relatórios com múltiplos joins).


Naming Conventions — Microsoft Official

Fonte: Framework Design Guidelines

ElementoConvençãoExemplo
Classes, RecordsPascalCase substantivoClientAggregate, CreateClientResponse
InterfacesI + PascalCaseIClientRepository, ITenantContext
MétodosPascalCase verboCreateClient(), GetByIdAsync()
Métodos assíncronossufixo AsyncCreateClientAsync()
Propriedades booleanasIs/Can/Has + PascalCaseIsActive, CanDelete, HasTenant
Variáveis/parâmetroscamelCaseclientId, tenantContext
Campos privados_camelCase_repository, _eventBus
NamespacesProduto.Modulo.CamadaMicroCrm.Clients.Domain

Interfaces — sempre programar para a abstração

// ✅ CORRETO — depende da interface, não da implementação
public class CreateClientHandler
{
private readonly IClientRepository _repository; // interface
private readonly IEventBus _eventBus; // interface

public CreateClientHandler(IClientRepository repository, IEventBus eventBus)
{
_repository = repository;
_eventBus = eventBus;
}
}

// ❌ EVITAR — depende da implementação concreta
public class CreateClientHandler
{
private readonly ClientRepository _repository; // classe concreta
}

Por quê: facilita testes (mock da interface), permite trocar implementação sem mudar o código, é o princípio D do SOLID (Dependency Inversion).

Fonte: Dependency inversion principle — Microsoft Learn