Imported from alexssantos/meli-item-comparator (
AGENTS.md). Install upstream withnpx skills add alexssantos/meli-item-comparator. Copyright stays with the author.
Agent Instructions — Meli Item Comparator API
Stack
- Runtime: .NET 10 (C# 14)
- Framework: ASP.NET Core Controllers
- ORM: Dapper (micro-ORM)
- Banco (dev): SQLite in-memory
- Banco (testes): PostgreSQL 16 via TestContainers
- Logging: Serilog (structured logging)
- Docs: OpenAPI + Scalar
- Testes: xUnit + FluentAssertions + NSubstitute + TestContainers
Princípios Fundamentais
- KISS — Sempre escolha a solução mais simples que resolve o problema. Não crie abstrações para código que é usado apenas uma vez.
- YAGNI — Não implemente funcionalidades especulativas. Construa apenas o que foi solicitado.
- Fail Fast — Valide inputs nas bordas do sistema e falhe cedo com mensagens claras.
- Código é documentação — Nomes descritivos > comentários. Comente apenas o "porquê", nunca o "o quê".
Arquitetura
src/
├── Api/ # Endpoints, middlewares, configuração
├── Application/ # Use cases, interfaces, DTOs
├── Domain/ # Entidades, value objects, regras de negócio
└── Infrastructure/ # Implementações externas (HTTP clients, cache, etc.)
tests/
├── Unit/ # Testes de domain e application (rápidos, sem I/O)
└── Integration/ # Testes com dependências reais (TestContainers)
Regras de dependência
Domainnão referencia nenhum outro projeto.Applicationreferencia apenasDomain.InfrastructurereferenciaApplicationeDomain.Apireferencia todos.
Padrões Obrigatórios
Result Pattern
Use Result<T> para operações que podem falhar de forma esperada. Nunca use exceções para fluxo de controle.
public enum ErrorType
{
Validation,
NotFound,
Conflict,
Unexpected
}
public sealed record Error(string Code, string Message, ErrorType Type = ErrorType.Validation);
public sealed class Result<T>
{
public T? Data { get; }
public Error? Error { get; }
public bool IsSuccess => Error is null;
private Result(T data) => Data = data;
private Result(Error error) => Error = error;
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(Error error) => new(error);
}
Regras de uso
- Todo use case retorna
Result<T>— nunca lance exceções para erros de domínio/validação. - Use
ErrorTypepara classificar o erro — o tipo determina o HTTP status code na camada API. - Na API, use
result.ToActionResult()— extensão emApi/Extensions/ResultExtensions.csque converteResult<T>emIActionResultcom Problem Details (RFC 9457). - Nunca mapeie manualmente
Result→ HTTP status no controller — use sempre a extensão.
Mapeamento ErrorType → HTTP Status
| ErrorType | HTTP Status |
|---|---|
Validation |
400 Bad Request |
NotFound |
404 Not Found |
Conflict |
409 Conflict |
Unexpected |
500 Internal Server Error |
Exemplo no use case
// Validação de input → ErrorType.Validation (default)
return Result<T>.Failure(new Error("INVALID_PAGE", "Page must be >= 1."));
// Recurso não encontrado → ErrorType.NotFound
return Result<T>.Failure(new Error("PRODUCT_NOT_FOUND", $"Product with id {id} not found.", ErrorType.NotFound));
Exemplo no controller
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id, CancellationToken ct)
{
var result = await getByIdUseCase.ExecuteAsync(id, ct);
return result.ToActionResult();
}
Error Handling
- Custom Exceptions apenas para erros irrecuperáveis ou infraestrutura (ex:
ExternalServiceException). - Global Exception Handler via
IExceptionHandlerpara capturar exceções não tratadas e retornar Problem Details (RFC 9457). - Erros de domínio fluem via
Result<T>, nunca via exceções.
Resiliência
- Use
Microsoft.Extensions.Http.Resilience(Polly v8) para HTTP clients. - Defina políticas de retry, circuit breaker e timeout por client externo.
- Timeouts sempre explícitos — nunca dependa de defaults.
Logging
- Serilog com structured logging. Configuração via
builder.Host.UseSerilog(). - Log em boundaries (request pipeline via
UseSerilogRequestLogging()) e erros. - Não adicione logging em todo lugar — apenas boundaries e erros.
Modelo EAV (Entity-Attribute-Value)
- Atributos dinâmicos na tabela
ProductAttributecomName,Value,NumericValue,Unit,IsComparable. - Normalização write-time via
AttributeNormalizerno Domain (ex: "1TB" → NumericValue=1024, Unit="GB"). - Filtros por atributo usam
EXISTSsubqueries (não JOINs) para ignorar atributos inexistentes silenciosamente.
Banco de Dados Dual
- Dev: SQLite in-memory (conexão singleton mantida aberta). Zero dependência externa.
- Testes: PostgreSQL 16 via TestContainers. Fidelidade ao banco de produção.
- DDL deve ser compatível com ambos: usar
INTEGER/BOOLEANdinâmico para booleanos,REALpara decimais.
Documentação
Código
- XML docs apenas em membros públicos de
ApplicationeDomain. - Mantenha os summaries em uma linha sempre que possível.
- Não documente o óbvio (ex:
/// <summary>Gets the id.</summary>).
API
- Use XML docs (
/// <summary>e/// <remarks>) nos controllers. - OpenAPI gerado automaticamente via
Microsoft.AspNetCore.OpenApi. - Documentação interativa via Scalar (
/scalar/v1).
README
- Seções: Objetivo, Como rodar, Arquitetura (diagrama simples), Decisões técnicas.
- Máximo 2 páginas. Se precisar de mais, crie um
/docs.
Testes
Unitários
- Um arquivo de teste por classe/use case.
- Nomeação:
MetodoSobTeste_Cenario_ResultadoEsperado. - Arrange-Act-Assert sem comentários separadores (o código deve ser claro).
- Mocks apenas para dependências externas (interfaces de infraestrutura).
Integração
- Use
WebApplicationFactory<Program>para testes de API. - Use
TestContainerspara dependências externas (Redis, banco, etc.). - Cada teste deve ser independente — sem estado compartilhado entre testes.
Cobertura
- Foque em cobrir lógica de negócio e edge cases, não getters/setters.
- Não persiga 100% de cobertura — persiga confiança no deploy.
Estilo de Código
file-scoped namespacessempre.primary constructorsquando reduz boilerplate.recordpara DTOs e value objects imutáveis.sealedem classes que não foram projetadas para herança.- Nullable reference types habilitados — trate warnings como errors.
- Não use
#region.
Ao Implementar
- Comece pelo domínio (entidades, regras de negócio).
- Depois application (use cases com interfaces).
- Depois infraestrutura (implementações concretas).
- Por último, API (endpoints, DI, middlewares).
- Escreva testes junto com cada camada.
Sugestões e Análise
Ao finalizar qualquer implementação, inclua:
Melhorias Futuras
- Liste 2-3 melhorias concretas que fariam sentido se o projeto escalar.
- Seja específico (ex: "Adicionar cache distribuído no endpoint X" e não "melhorar performance").
Análise Prós/Contras
Para cada decisão arquitetural relevante, apresente brevemente:
| Decisão | Prós | Contras |
|---|---|---|
| Ex: Controllers | Convenções claras, XML docs, model binding robusto | Mais boilerplate que Minimal APIs |
Complexidade Computacional
Ao implementar algoritmos de busca, query, filtro, ordenação ou manipulação de coleções relevantes, documente a complexidade com um comentário inline no formato:
// O(n) — percorre a lista uma vez para filtrar itens ativos
var activeItems = items.Where(i => i.IsActive).ToList();
// O(n log n) — ordenação por preço, aceitável para listas < 10k
var sorted = items.OrderBy(i => i.Price).ToList();
// O(n²) — atenção: nested loop, considerar otimizar se n > 1000
Escala de referência
| Complexidade | Avaliação | Ação |
|---|---|---|
| O(1) | Excelente | Nenhuma |
| O(log n) | Ótimo | Nenhuma |
| O(n) | Tranquilo | Documentar |
| O(n log n) | Aceitável | Documentar + justificar |
| O(n²) | Atenção | Documentar + avaliar alternativa |
| O(n³+) | Crítico | Refatorar ou justificar fortemente |
Quando documentar
- Queries LINQ com múltiplas operações encadeadas.
- Loops sobre coleções de tamanho variável.
- Chamadas a APIs externas dentro de iterações.
- Algoritmos de comparação, merge ou deduplicação.
- Não documente operações triviais (acesso a dicionário,
.FirstOrDefault()em lista pequena fixa).
O que NÃO fazer
- Não crie interfaces para classes que terão apenas uma implementação (exceto para testabilidade de I/O).
- Não adicione camadas de abstração sem justificativa clara.
- Não use padrões como Repository genérico, Unit of Work, ou MediatR sem necessidade comprovada.
- Não crie DTOs de mapeamento intermediários desnecessários.
- Não adicione logging em todo lugar — log em boundaries e erros.
- Não crie pastas vazias "para o futuro".