# 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.