API REST construída em .NET com arquitetura em camadas, domain-driven design, validações no domínio via construtor, e cobertura de testes unitários em Domain + Application.
// 01 · arquitetura
O projeto segue os princípios de Clean Architecture, isolando o domínio de infraestrutura e frameworks. A dependência sempre aponta para dentro — o Domain não conhece ninguém.
ICommandResult<T> com StatusCode e Message. Zero lógica nos controllers.ICommandResult com StatusCode HTTP embutido. Extension method .ToResult() converte para IActionResult.Service-Método: mensagem em todos os métodos.// 02 · endpoints
Produtos e Pedidos cobertos com operações CRUD completas e regras de negócio específicas por domínio.
PRODUTOS · /api/produtos
PEDIDOS · /api/pedidos
// 03 · máquina de estados
Transições de status são controladas por uma máquina de estados no domínio.
Transições inválidas lançam InvalidOperationException, retornando 400 Bad Request.
Dictionary<string, HashSet<string>> dentro da classe Pedido.Status, garantindo que a regra viva no domínio e não em validações espalhadas.pedido.CancelarPedido() — não há exclusão física. O registro persiste para auditoria.
// 04 · decisões de design
Escolhas técnicas intencionais que diferenciam esta implementação.
new Produto("", 0) lança ArgumentException
imediatamente — impossível criar um objeto inválido. As annotations ficam apenas nos DTOs de entrada da API.
produtoId: 1, qtde: 2 e produtoId: 1, qtde: 3,
o sistema consolida para um único item com quantidade 5. Itens sem diferencial (desconto, presente) não devem se duplicar.
ArgumentException e InvalidOperationException vêm do domínio com mensagens
controladas e são retornadas ao cliente. Exception genérica retorna mensagem genérica —
o cliente não precisa saber de erros internos.
ItemPedido captura produto.Preco no construtor. Se o preço do produto
mudar depois, os pedidos existentes não são afetados. Isso garante integridade histórica dos pedidos.
ICommandResult<T> com HttpStatusCode, Message
e Data. O extension method .ToResult() converte automaticamente para
IActionResult, mantendo os controllers em 3 linhas.
DELETE /produtos/{id} chama produto.Desativar() e persiste.
DELETE /pedidos/{id} chama pedido.CancelarPedido().
Nenhum registro é removido do banco — rastreabilidade preservada.
// 05 · testes
Testes unitários com xUnit, NSubstitute para mocks e FluentAssertions para asserções legíveis.
/scalar após rodar a API..json da pasta /docs.Api.Itau.http com todos os cenários incluindo casos de erro. Rode direto no Visual Studio ou JetBrains Rider.Cobertura por camada
// 06 · roadmap
Esta é uma POC executada em poucas horas. As features abaixo seriam as próximas evoluções naturais.
PaginatedResult<T> com page, pageSize, totalItems e totalPages
para os endpoints GET /produtos e GET /pedidos. Sem paginação, um GetAll em produção é um risco.
Stopwatch nos Services, especialmente no GetAll(),
para logar o tempo de execução e identificar gargalos. Especialmente útil com banco real.
[Authorize] nos controllers.
Diferenciaria roles — por exemplo, apenas admins poderiam criar/deletar produtos.
Idempotency-Key no POST /pedidos.
Se o cliente reenviar a mesma requisição (retry em falha de rede), o servidor retorna o resultado original
sem criar pedido duplicado.
UseInMemoryDatabase por SQL Server ou PostgreSQL.
O AppDbContext e as EntityConfigurations já estão prontos —
é só trocar o provider e rodar dotnet ef migrations add.
WebApplicationFactory<Program>
para testar o pipeline completo HTTP → Controller → Service → Repository → EF Core,
sem mocks.