Pular para o conteúdo principal
Versão: main (dev) 🚧

ADR-004 — DB-backed role/permission resolution with IMemoryCache

Date: 2026-05-08 Status: Accepted

Context

A Fase 2 shipou InMemoryRolePermissionResolver — três roles hard-coded (Admin/Operator/Viewer) com permissões fixas no código. Comentários no DI explicitavam "TEMPLATE PLACEHOLDER — replace before production".

Custos do placeholder:

  • Cada consumidor reescreve antes do primeiro deploy — mas há padrões comuns (tabela + cache) que poderiam viver no template.
  • Cobertura de testes do path de autorização ficava amarrada às 3 roles fixas.
  • Mudanças em roles exigiam deploy de código.

Patterns considerados para produção:

  1. DB-backed — tabela RolePermissions(RoleName, Permission) com cache.
  2. Config-driven InMemoryappsettings:Authorization:Roles lido por IOptionsMonitor, hot reload.
  3. Identity Provider claims — role/permission claims emitidos pelo IdP.

Decision

DB-backed resolver com IMemoryCache em frente, shipado canon. Estrutura:

  • Entidade RolePermission em Infrastructure/Authentication/ (composite PK RoleName + Permission, sem invariantes de domínio — pure infra).
  • RolePermissionConfiguration mapeia a tabela e usa HasData para seed dos 3 roles canônicos (Admin/Operator/Viewer × Orders.*). HasData funciona tanto em migrations relacionais quanto em EF Core InMemory (via EnsureCreated), unificando dev + CI + prod.
  • DbRolePermissionResolver lê por role com IMemoryCache (TTL 5 min). Hot path: 1 query por miss + lookups O(1).
  • DI: AddMemoryCache() + AddDevPackRoleBasedPermissionEvaluator<DbRolePermissionResolver>().
  • AppDbContext expõe DbSet<RolePermission> RolePermissions mas não o expõe via IAppDbContext — handlers de feature não devem queryar essa tabela diretamente; só o resolver lê.

Estender o catálogo:

  1. Adicionar constante em Permissions.cs.
  2. Estender RolePermissionConfiguration.HasData com novas linhas.
  3. dotnet ef migrations add Add{NewRole}.

Multi-instance (Redis):

  • Swap IMemoryCache por IDistributedCache no resolver — mesma superfície.

Consequences

Positivas:

  • Template funcional out-of-the-box: rodar dotnet ef database update + ter usuário com claim de role já basta.
  • Padrão único para todos os consumidores — menos drift entre projetos.
  • Tests integration exercitam o caminho real (DbRolePermissionResolver + HasData seed via EnsureCreated).
  • Cache reduz pressão sobre DB; TTL 5min é trade-off razoável entre frescor e custo.

Negativas:

  • Adiciona uma tabela extra (RolePermissions) ao schema mínimo. Consumidores com permissões 100% IdP-driven precisam remover/ignorar.
  • Cache local (IMemoryCache) em multi-instance fica até 5 min divergente após mudança — IDistributedCache resolve mas exige Redis.
  • Mudança de role exige migration ou edição direta no DB.

Alternatives considered

OpçãoPor que descartada
Manter InMemoryRolePermissionResolver placeholderCada consumidor reescreve; padrão fica perdido.
Config-driven InMemory (appsettings)Hot reload é vantagem pequena; arquivo de config cresce sem governança.
Claims do IdP (sem tabela local)Acopla auth à definição de permissions — mudanças exigem coordenação com o IdP team. Não impede uso futuro com claims (consumidor sobrescreve IRolePermissionResolver).

References

  • CLAUDE.md § Role catalogue (RolePermission table).
  • src/.../Infrastructure/Authentication/DbRolePermissionResolver.cs.
  • src/.../Infrastructure/Persistence/Configurations/RolePermissionConfiguration.cs.
  • DevPack ADR-004 (role-based permission resolution surface, originou daqui).