# Arquitetura
## Classificação
**Onion Architecture** (na convenção .NET), com o domínio isolado e a regra de dependência verificada pelo compilador.
Uma ressalva honesta desde já: é um Onion **sem a camada de aplicação**. As três camadas presentes — Domain, Infrastructure, API — implementam a inversão de dependência corretamente, mas a orquestração de casos de uso ainda vive nos serviços de domínio (`CustomCrud`), não numa camada de Application própria. A extração dessa camada é a próxima evolução natural (ver [Aberto](#aberto)).
### Por que Onion e não Hexagonal
As duas concordam na regra central — dependências apontam para dentro — mas divergem em dois pontos concretos, e o código deste projeto cai claramente do lado Onion:
| Critério | Hexagonal (Ports & Adapters) | Este projeto |
|---|---|---|
| Forma | Simétrica: núcleo + adaptadores de entrada e saída, sem hierarquia | **Camadas concêntricas nomeadas** (Domain no centro) → Onion |
| Portas de entrada (*driving*) | Obrigatórias: a UI chama um caso de uso por uma interface | **Ausentes** — o controller fala com o repositório direto |
| Portas de saída (*driven*) | Presentes | Presentes (`IGenericRepository`, `IJwtTokenService`, …) |
O ponto decisivo é o segundo. O `BaseController` depende de `IGenericRepository<...> baseService` e o invoca diretamente — não há uma porta de entrada (um `IUseCase`) entre a entrega HTTP e a lógica. A Hexagonal exige portas nos **dois** lados; aqui só existe o lado de saída. Isso, somado às camadas concêntricas, caracteriza Onion — que é também como a comunidade .NET nomeia o arranjo de projetos Domain / Infrastructure / API.
---
## Diagnóstico anterior
O projeto usava vocabulário de arquitetura hexagonal (`Core/1 - API`, `Core/2 - Feature`, `Core/3 - Shared/{Domain,Infrastructure}`) sem a inversão que o justifica. Medição antes da correção:
| Métrica | Valor |
|---|---|
| Arquivos do domínio importando infraestrutura | **61 de 66 (92%)** |
| Projetos na solução | **1** — nenhuma fronteira compilada |
| `Core/2 - Feature` (camada de aplicação) | **0 arquivos** — anunciada no README, nunca existiu |
Independentemente do nome — Onion, Hexagonal ou Clean —, as três concordam que **dependências apontam para dentro**. Aqui apontavam para fora.
A maior parte das 61 violações, porém, era acidente de nomenclatura — não acoplamento real. O que o domínio importava de "Infrastructure" era `NotificationErrors`, `DefaultMessage`, `IAnalysisResult` e os validadores (`IsEmail`, `IsCnpj`): todos conceitos de domínio que moravam numa pasta com o nome errado.
## Estrutura atual
```mermaid
flowchart RL
API["StockMaster.API
controllers, DTOs, middleware, filtros"]
INF["StockMaster.Infrastructure
EF Core, repositórios, JWT, interceptor"]
DOM["StockMaster.Domain
entidades, portas, validação, Result"]
API --> INF
INF --> DOM
API -.-> DOM
style DOM fill:#ddffdd
style INF fill:#ddeeff
style API fill:#ffe8cc
```
| Projeto | Referencia | Papel |
|---|---|---|
| `StockMaster.Domain` | **nada** | Entidades, regras, portas de saída (`IGenericRepository`, `IUserRepository`, `IInventoryService`, `ICurrentUser`, `IJwtTokenService`, `IPasswordHasherService`, `IUnitOfWork`, `IApplicationTimeZone`), Notification e Result |
| `StockMaster.Infrastructure` | Domain | Adaptadores: EF Core, PostgreSQL, migrations, hash de senha, emissão de JWT, interceptor de auditoria |
| `StockMaster.API` | Infrastructure (e Domain) | Entrega HTTP: controllers, DTOs, middleware, filtros, composição |
O domínio não referencia projeto algum. É isso que faz a regra valer: **o compilador impede** que ele conheça EF Core, HTTP ou banco.
## O que o compilador encontrou na separação
Separar os projetos revelou três acoplamentos que a revisão manual tinha apontado como suspeita e que agora viraram erro de compilação:
1. **`IMovementAuditRepository` morava em Infrastructure** enquanto as outras 12 interfaces de auditoria estavam em Domain — e era consumida por `MovementCustomCrud`, no domínio. Movida para `Domain/Repository`.
2. **`BootStrapper` (Infrastructure) registrava middlewares da API.** Registro movido para `Program.cs`: middleware é camada de entrega.
3. **`DomainBase.SetId` é `internal`** e só funcionava porque tudo era um assembly. Em vez de torná-lo público — o que abriria a atribuição de identidade para qualquer consumidor — a exceção virou nominal e explícita via `InternalsVisibleTo("StockMaster.Infrastructure")`.
## Testes de arquitetura
`test/StockMaster.API.UnitTest/Architecture/DependencyRuleTests.cs` verifica a regra no build:
- domínio não depende de Infrastructure, API, EF Core nem ASP.NET Core;
- infraestrutura não depende da API;
- DTOs não referenciam schemas de persistência.
Duas verificações extras evitam que os testes virem decoração:
- **sanidade do scan** — falha se o analisador não enxergar tipo nenhum, o que faria todas as regras passarem vazias;
- **sanidade da detecção** — exige que a regra *acuse* a dependência real de Infrastructure para EF Core. Se essa passar, o mecanismo quebrou.
## Decisões deliberadas
**Sem MediatR/CQRS.** Para uma API majoritariamente CRUD, um handler por operação adiciona cerimônia sem resolver acoplamento. A auditoria já apontava overengineering na hierarquia de 6 interfaces do `Result`; empilhar mais camadas pioraria.
**`Core/2 - Feature` removida.** Pasta que anuncia camada de aplicação e entrega zero arquivo é pior que ausência. As regras de negócio hoje vivem nos `CustomCrud`, acionados pelo repositório genérico. Se um dia justificarem camada própria, ela nasce com conteúdo.
**Logging no domínio.** `Microsoft.Extensions.Logging.Abstractions` é a única dependência externa do `StockMaster.Domain`. É abstração pura, sem implementação nem acoplamento a infraestrutura — concessão consciente para não reescrever os 13 `CustomCrud`.
## Aberto
Estes três pontos são o que separa o projeto de um Onion completo. São conhecidos e deliberadamente deixados como evolução, não descuidos:
- **A camada de aplicação continua ausente.** É a lacuna que impede chamar isto de "Onion completo". A regra de negócio vive dentro de `CustomCrud`, acionada pelo repositório — acoplamento entre persistência e domínio que a separação de projetos, sozinha, não desfaz. Extrair casos de uso (`Application`) seria o próximo passo.
- **`GenericBaseRepository` segue acumulando** validação, mapeamento, auditoria e persistência (God class de ~280 linhas).
- **A pasta `CustomCrud` no domínio** carrega nome de CRUD, não de negócio; `SaleItemCustomCrud` é, na prática, um caso de uso de venda — e viveria melhor na camada de aplicação ausente.