Decisões Arquiteturais

Este documento registra decisões arquiteturais importantes tomadas durante o desenvolvimento do projeto Arah.

Status: Aceito
Data: 2024
Contexto: A especificação original marcava Marketplace como funcionalidade POST-MVP, mas durante o desenvolvimento identificamos que:

  1. Necessidade de validação: A funcionalidade de marketplace é crítica para validar o modelo de negócio territorial
  2. Dependências já implementadas: As dependências necessárias (stores, listings, inquiries) já estavam sendo desenvolvidas para outros casos de uso
  3. Feedback de usuários: Stakeholders indicaram que marketplace seria essencial para o MVP

Decisão: Implementar Marketplace completo no MVP, incluindo:

  • Gestão de lojas (stores)
  • Listagens de produtos e serviços (listings)
  • Sistema de inquiries
  • Carrinho e checkout
  • Taxas de plataforma

Consequências:

  • ✅ Funcionalidade completa disponível no MVP
  • ✅ Maior complexidade no MVP inicial
  • ✅ Necessidade de testes abrangentes
  • ⚠️ Possível necessidade de ajustes baseados em feedback real

Alternativas Consideradas:

  1. Implementar apenas inquiries (rejeitado - muito limitado)
  2. Implementar apenas stores sem listings (rejeitado - não valida modelo completo)
  3. Adiar para POST-MVP (rejeitado - perde oportunidade de validação)

Status: Aceito
Data: 2024
Contexto: Necessidade de garantir entrega confiável de notificações in-app mesmo em caso de falhas.

Decisão: Implementar padrão Outbox/Inbox para notificações:

  • Eventos de domínio geram mensagens no Outbox
  • Worker processa Outbox e cria notificações no Inbox
  • Inbox é consultado pela API de notificações

Consequências:

  • ✅ Garantia de entrega (at-least-once)
  • ✅ Resiliência a falhas
  • ✅ Possibilidade de reprocessamento
  • ⚠️ Complexidade adicional
  • ⚠️ Necessidade de worker processando Outbox

Alternativas Consideradas:

  1. Notificações síncronas diretas (rejeitado - sem garantia de entrega)
  2. Fila externa (rejeitado - adiciona dependência externa no MVP)

Status: Aceito
Data: 2024
Contexto: Princípio fundamental do projeto - território é geográfico e neutro.

Decisão: Território contém apenas dados geográficos. Toda lógica social (memberships, visibilidade, moderação) fica em camadas separadas que referenciam o território.

Consequências:

  • ✅ Território pode existir sem usuários
  • ✅ Lógica social pode evoluir independentemente
  • ✅ Evita centralização indevida
  • ✅ Mais claro e justo
  • ⚠️ Mais complexidade em queries que precisam juntar dados

Alternativas Consideradas:

  1. Território contém lógica social (rejeitado - viola princípio fundamental)
  2. Território é apenas ID (rejeitado - perde dados geográficos essenciais)

Status: Aceito
Data: 2024
Contexto: MVP exige presença física para vínculo, mas diferentes políticas podem ser necessárias.

Decisão: Implementar PresencePolicy configurável:

  • None: Nenhum vínculo exige geo
  • ResidentOnly: Apenas RESIDENT exige geo (padrão)
  • VisitorAndResident: Ambos exigem geo

Consequências:

  • ✅ Flexibilidade para diferentes contextos
  • ✅ Configurável via appsettings
  • ✅ Validação consistente em toda aplicação
  • ⚠️ Necessidade de documentar política escolhida

Alternativas Consideradas:

  1. Sempre exigir geo para todos (rejeitado - muito restritivo)
  2. Nunca exigir geo (rejeitado - viola princípio do MVP)

Status: Aceito
Data: 2024
Contexto: Posts podem ter GeoAnchors, mas client não deve definir manualmente.

Decisão: GeoAnchors são derivados automaticamente de metadados de mídias quando disponíveis. Client não envia GeoAnchors manualmente.

Consequências:

  • ✅ Dados geográficos mais confiáveis (vêm de EXIF)
  • ✅ Simplifica API do client
  • ✅ Posts podem existir sem GeoAnchors (não aparecem no mapa)
  • ⚠️ Dependência de processamento de mídia (POST-MVP)

Alternativas Consideradas:

  1. Client define GeoAnchors manualmente (rejeitado - menos confiável)
  2. GeoAnchors obrigatórios (rejeitado - muito restritivo)

Status: Aceito
Data: 2024
Contexto: Necessidade de suportar desenvolvimento rápido e produção robusta.

Decisão: Implementar repositórios InMemory para desenvolvimento/testes e Postgres para produção. Switch via configuração Persistence:Provider.

Consequências:

  • ✅ Desenvolvimento rápido sem dependências externas
  • ✅ Testes rápidos e isolados
  • ✅ Produção robusta com Postgres
  • ✅ Mesma interface para ambos
  • ⚠️ Necessidade de manter duas implementações sincronizadas

Alternativas Consideradas:

  1. Apenas Postgres (rejeitado - dificulta desenvolvimento)
  2. Apenas InMemory (rejeitado - não adequado para produção)

Status: Aceito
Data: 2024
Contexto: Necessidade de proteger comunidade de conteúdo inadequado rapidamente.

Decisão: Implementar moderação automática quando threshold de reports únicos é atingido (padrão: 3 reports). Ações automáticas incluem ocultar conteúdo e aplicar sanções.

Consequências:

  • ✅ Proteção rápida da comunidade
  • ✅ Reduz carga de moderação manual
  • ✅ Threshold configurável
  • ⚠️ Possibilidade de falsos positivos
  • ⚠️ Necessidade de auditoria

Alternativas Consideradas:

  1. Apenas moderação manual (rejeitado - muito lento)
  2. Threshold muito baixo (rejeitado - muitos falsos positivos)
  3. Threshold muito alto (rejeitado - pouco efetivo)

Status: Aceito
Data: 2024
Contexto: Diferentes territórios podem precisar de funcionalidades diferentes.

Decisão: Implementar feature flags por território, gerenciadas por curadores.

Consequências:

  • ✅ Flexibilidade por território
  • ✅ Rollout gradual de funcionalidades
  • ✅ Possibilidade de desabilitar funcionalidades problemáticas
  • ⚠️ Necessidade de gerenciar flags

Alternativas Consideradas:

  1. Feature flags globais (rejeitado - menos flexível)
  2. Sem feature flags (rejeitado - muito rígido)

Status: Aceito
Data: 2026
Contexto: O sistema precisa de filas de revisão para diferentes domínios (verificação, curadoria, moderação) sem criar “N filas” específicas e inconsistentes.

Decisão: Introduzir um modelo genérico WorkItem (Work Queue) com:

  • Type, Status, Outcome
  • TerritoryId opcional (itens globais vs territoriais)
  • RequiredSystemPermission e/ou RequiredCapability
  • SubjectType/SubjectId + PayloadJson para contexto mínimo

Consequências:

  • ✅ Filas padronizadas e extensíveis (novos tipos sem refatorar infra)
  • ✅ Governança clara (SystemAdmin vs Curator/Moderator)
  • ✅ Auditoria consistente de criação/conclusão
  • ⚠️ PayloadJson exige disciplina para evitar acoplamento excessivo

Alternativas Consideradas:

  1. Filas específicas por domínio (rejeitado - duplicação e divergência)
  2. “Somente notificações” sem fila (rejeitado - falta rastreabilidade e SLA humano)

Status: Aceito
Data: 2026
Contexto: Precisamos suportar evidências/mídias em storage externo (S3/MinIO), controlando acesso e auditando downloads.

Decisão: Adotar download por proxy inicialmente:

  • Cliente chama API
  • API autentica/autoriza, audita e faz streaming do storage

Consequências:

  • ✅ Controle total de acesso e auditoria
  • ✅ Simplifica (não exige política de expiração e assinatura no client)
  • ⚠️ Maior carga na API (bandwidth/streaming)

Alternativas Consideradas:

  1. URLs pré-assinadas (rejeitado nesta fase - mais variáveis operacionais no client)
  2. URLs públicas (rejeitado - risco de vazamento)

Status: Aceito
Data: 2026
Contexto: A documentação do projeto prevê um Backend for Frontend (BFF) para reduzir chamadas de rede e expor jornadas formatadas para UI. Após a Fase 1 (BFF como módulo interno na API), evoluiu-se para a Fase 2: o BFF não é um módulo interno — é outra aplicação.

Decisão: O BFF é uma aplicação separada (Arah.Api.Bff):

  • Projeto próprio: backend/Arah.Api.Bff, deployável de forma independente
  • Expõe as mesmas rotas de jornada sob /api/v2/journeys (onboarding, feed, events, marketplace), encaminhando as requisições para a API principal (Arah.Api)
  • Configuração Bff:ApiBaseUrl na aplicação BFF aponta para a URL da API
  • O frontend consome o BFF; o BFF faz proxy (e futuramente pode orquestrar chamadas à API v1 diretamente)
  • A API principal continua expondo /api/v2/journeys para atender as requisições encaminhadas pelo BFF (ou consumo direto em cenários legados)
  • Cache quando necessário: respostas GET 2xx podem ser cacheadas em memória no BFF (Bff:EnableCache, Bff:CacheTtlSeconds, Bff:CacheTtlByPath) para reduzir chamadas à API; chave inclui path, query e autorização (respostas por usuário). Header de resposta X-Bff-Cache: HIT ou MISS indica origem.

Consequências:

  • ✅ BFF e API são aplicações distintas (deploy, escala e evolução independentes)
  • ✅ Frontend fala apenas com o BFF; a API pode ficar interna à rede
  • ✅ Mesma autenticação JWT: o BFF repassa o token à API
  • ✅ Cache no BFF reduz carga na API em leituras repetidas (GET)
  • ⚠️ Latência adicional de uma hop HTTP entre BFF e API (aceitável na maioria dos cenários)

Referências:


Status: Aceito
Data: 2026
Contexto: O projeto evoluiu para modularização completa: cada contexto de negócio (Feed, Events, Map, Marketplace, Moderation, Assets, etc.) possui Domain e Application no módulo quando aplicável. Core ficou com apenas o núcleo compartilhado.

Decisão:

  • Core.Domain: contém apenas o núcleo compartilhado (Users, Membership, Territories, Governance — ex.: TerritoryModerationRule, RuleType —, Geo, Media, Email, Configuration). Não contém mais Map, Marketplace, Moderation (Work, Evidence, Reports, Sanctions), nem Assets.
  • Core.Application: serviços e eventos transversais (ConnectionService, ReportService, TerritoryModerationService, AnalyticsService, etc.) e interfaces compartilhadas (IAuditLogger, IUnitOfWork, IUserRepository, IFileStorage). Referencia os módulos (Application) para contratos de repositório.
  • Módulos: cada um com domínio próprio expõe Domain, Application e Infrastructure. Exceção: Admin só Infrastructure; Connections não possui Infrastructure no módulo (implementações em Core.Infrastructure).

Consequências:

  • ✅ Módulos autocontidos para Map, Marketplace, Moderation, Assets, Feed, Events, Notifications, Chat, Alerts, Subscriptions, Connections.
  • ✅ Core enxuto: apenas núcleo compartilhado e orquestração.
  • ✅ Testes por módulo (Arah.Tests.Modules.Map, .Marketplace, .Moderation, .Connections, .Subscriptions) executáveis isoladamente.

Estado de desacoplamento atual (modularização completa)

Objetivo: Registrar o estado final da modularização Domain/Application para desacoplamento e evolução independente dos módulos.

Módulo Domain no módulo Application no módulo Infrastructure Testes módulo
Connections ✅ (IUserConnectionRepository, IConnectionPrivacySettingsRepository, IAcceptedConnectionsProvider) Core.Infrastructure (Postgres/InMemory) Arah.Tests.Modules.Connections
Módulo Domain no módulo Application no módulo Infrastructure no módulo Testes módulo
Feed ✅ (IFeedRepository, etc.)
Events ✅ (ITerritoryEventRepository, IEventParticipationRepository)
Notifications ✅ (INotificationInboxRepository, INotificationConfigRepository)
Chat
Alerts ✅ (IHealthAlertRepository)
Subscriptions ✅ (ISubscriptionRepository, etc.) Arah.Tests.Modules.Subscriptions
Assets ✅ (ITerritoryAssetRepository, etc.)
Map ✅ (IMapEntityRepository, etc.) Arah.Tests.Modules.Map
Marketplace ✅ (IStoreRepository, ICheckoutRepository, etc.) Arah.Tests.Modules.Marketplace
Moderation ✅ (IReportRepository, IWorkItemRepository, etc.) Arah.Tests.Modules.Moderation

Módulos só Infrastructure

Módulo Motivo
Admin Apenas registros de infraestrutura; sem domínio de negócio próprio.
  • Core.Application → Core.Domain, Feed.Application, Events.Application, Notifications.Application, Chat.Application, Alerts.Application, Connections.Application, Subscriptions.Application, Assets.Application, Map.Application, Marketplace.Application, Moderation.Application, Shared.
  • Core.Infrastructure → Core.Domain, Core.Application, Feed.Application, Connections.Application, Marketplace.Application (ex.: PayoutProcessingWorker), Moderation.Domain (ex.: StorageProvider para IFileStorage).
  • Modules.*.Infrastructure → Application (e Domain) do próprio módulo; Core quando necessário. Não referenciam outros módulos.
  • Modules.*.Domain → podem referenciar Core.Domain (ex.: Geo, Territories, Users) quando o domínio do módulo depende do núcleo.

O que permanece no Core.Domain

Núcleo compartilhado: Users, Membership, Territories, Governance (TerritoryModerationRule, RuleType), Geo, Media (contratos compartilhados), Email, Configuration. Módulos referenciam Core.Domain para tipos compartilhados (ex.: TerritoryId, UserId, GeoCoordinate).

Testes por módulo

  • Arah.Tests.Modules.Connections: Domain + Application.
  • Arah.Tests.Modules.Subscriptions: Application + Api + Performance.
  • Arah.Tests.Modules.Map, .Marketplace, .Moderation: Domain (entidades do módulo).

Validação (build e testes)

  • Build da solution: sucesso.
  • Testes modulares executáveis isoladamente por projeto (dotnet test Arah.Tests.Modules.Map, etc.).

Status: Aceito
Data: 2026
Contexto: Com a modularização do backend (Domain/Application por módulo), faz sentido organizar os testes em níveis que reflitam o desacoplamento e permitam rodar suites por escopo.

Decisão:

  • Arah.Tests (Core): Testes do núcleo — Api (integração), Application (serviços centrais), Domain (entidades centrais), Infrastructure, Bff, Performance. Referencia Core + módulos necessários. Continua sendo o projeto principal de testes.
  • Arah.Tests.Shared: Artefatos compartilhados entre projetos de teste — por exemplo TestIds (IDs pré-populados e disponíveis para testes). Sem dependências de Application/Infrastructure para permitir uso por qualquer projeto de teste. Referenciado por Arah.Tests e por projetos de teste de módulos.
  • Arah.Tests.Modules.* (por módulo): Testes específicos de um módulo (Domain, Application, Api e Performance). Ex.: Arah.Tests.Modules.Connections (Domain + Application); Arah.Tests.Modules.Subscriptions (Application + Api + Performance). Referenciam o módulo (Domain/Application), Core quando necessário (ex.: InMemory, Api, Application services), e Tests.Shared. Permite rodar apenas os testes do módulo (dotnet test Arah.Tests.Modules.Connections) e evita que o projeto Core de testes fique poluído com testes de todos os módulos.

Consequências:

  • ✅ Desacoplamento: testes do módulo podem evoluir e ser executados isoladamente.
  • ✅ CI pode definir níveis (ex.: core primeiro, depois módulos) ou rodar só o módulo alterado.
  • ✅ Mesma massa de testes; apenas reorganização em projetos.
  • ✅ Projetos de teste por módulo existentes: Connections, Subscriptions, Map, Marketplace, Moderation.

Status: Aceito
Data: 2026
Contexto: Consolidar convenções e regras de dependência após a modularização completa do backend.

Decisão:

  1. Estrutura por módulo: Cada módulo de negócio possui (quando aplicável) três projetos: Arah.Modules.<Nome>.Domain, Arah.Modules.<Nome>.Application, Arah.Modules.<Nome>.Infrastructure. O Admin mantém apenas Infrastructure; o Connections não possui Infrastructure no módulo (implementações em Core.Infrastructure).

  2. Core.Domain: Contém apenas o núcleo compartilhado (Users, Membership, Territories, Governance, Geo, Media, Email, Configuration). Módulos podem referenciar Core.Domain para tipos compartilhados (TerritoryId, UserId, GeoCoordinate, etc.).

  3. Core.Application: Contém serviços transversais e interfaces compartilhadas (IAuditLogger, IUnitOfWork, IUserRepository, IFileStorage). Referencia todos os módulos (Application) para contratos de repositório. Não contém entidades de contexto de módulo.

  4. Dependências: Modules..Infrastructure referenciam apenas o próprio módulo (Domain/Application) e Core quando necessário. Modules..Domain podem referenciar Core.Domain. Módulos não referenciam outros módulos.

  5. Namespaces: Alguns módulos (Connections, Chat, Events, Alerts, Notifications, Subscriptions) mantêm namespaces Arah.Domain.* nos tipos de domínio por compatibilidade; o código está nos projetos Arah.Modules.*.Domain. Migração futura para Arah.Modules.*.Domain é opcional.

Referência: Ver tabelas em Estado de desacoplamento atual neste documento.


Status: Proposto
Data: 2026
Contexto: Cada módulo hoje tem três projetos (Domain, Application, Infrastructure), o que gera muitos .csproj na solution e mais overhead de referências e build. Surge a dúvida: faz sentido ter um único projeto por módulo?

Decisão (alternativa aceitável):

Sim, um projeto por módulo faz sentido e é uma opção válida:

  • Estrutura: Um único projeto Arah.Modules.<Nome> com pastas Domain/, Application/ (Interfaces, serviços do contexto) e Infrastructure/ (Postgres, módulo DI). A separação lógica (camadas) permanece por convenção de pastas e namespaces.
  • Vantagens:
    • Menos projetos na solution (ex.: 12 módulos × 3 = 36 projetos → 12 projetos).
    • Build e referências mais simples; Core referencia um único projeto por módulo.
    • Módulo continua sendo a unidade de deploy e evolução; a fronteira é o módulo, não a camada.
  • Desvantagens:
    • A separação Domain/Application/Infrastructure deixa de ser enforced por assembly (Domain poderia, em tese, referenciar Infrastructure no mesmo projeto). Mitigação: convenção de pastas, code review e talvez analyzers (ex.: Domain não pode depender de Application ou Infrastructure).
  • Quando preferir 1 projeto por módulo: equipes menores, foco em simplicidade da solution, módulos que não precisam ser referenciados “só pelo Domain” ou “só pela Application” por outros assemblies.
  • Quando manter 3 projetos por módulo: quando se quer garantia forte por assembly (Domain sem dependências de infra), reuso fino (outro módulo ou API referenciar só o Domain do módulo X) ou times maiores com ownership por camada.

Migração: Se for adotada a opção “um projeto por módulo”, para cada módulo: criar Arah.Modules.<Nome>.csproj único, mover o conteúdo de Domain, Application e Infrastructure para pastas Domain/, Application/, Infrastructure/ dentro desse projeto, atualizar referências no Core e na solution, e remover os três projetos antigos. Os testes do módulo passam a referenciar apenas o novo projeto único.


Status: Aceito
Data: 2026
Contexto: Após a modularização, havia projetos de teste soltos na raiz do backend e necessidade de documentar a estrutura final, evitar referências circulares e registrar exceções (ex.: Feed → Map).

Decisão:

  1. Estrutura de pastas:

    • backend/Core/ — API, Application, Application.Abstractions, Domain, Infrastructure, Infrastructure.Shared, Shared.
    • backend/Modules/um projeto por módulo: cada módulo (Feed, Events, Map, Moderation, Alerts, Assets, Chat, Notifications, Subscriptions, Marketplace, Admin, Connections) é um único projeto com pastas Domain/, Application/ e Infrastructure/. Connections segue o mesmo padrão: tem ConnectionsDbContext e repositórios Postgres na pasta Infrastructure do módulo; as tabelas são as mesmas (user_connections, connection_privacy_settings), e a migration que as criou permanece no Core para compatibilidade.
    • backend/Tests/ — todos os projetos de teste (Arah.Tests, Arah.Tests.Shared, Arah.Tests.Modules.*) agrupados numa pasta Tests para clareza e boas práticas.
    • backend/Arah.Api.Bff/ — BFF na raiz do backend.
  2. Referências circulares:

    • Arah.Application.Abstractions contém apenas IModule e IUnitOfWorkParticipant (namespace Arah.Application.Interfaces). Módulos que implementam IModule referenciam Application.Abstractions (e não Core.Application), quebrando o ciclo Application ↔ Modules.Infrastructure.
    • Core.Application referencia os módulos (Application); módulos (Infrastructure) não referenciam Core.Application diretamente quando precisam de IModule/IUnitOfWorkParticipant — usam Application.Abstractions ou Infrastructure.Shared.
  3. Exceção: dependência entre módulos:

    • Feed referencia Map (tipos de geo para itens do feed). É a única dependência explícita entre módulos e foge à regra "módulos não referenciam outros módulos". Deve permanecer documentada; migração futura (ex.: mover tipo compartilhado para Core.Domain) é opcional.
  4. Verificação: Não há ciclo no grafo de referências entre projetos. Build e testes passam após a reorganização.

Referência: backend/README.md, backend/docs/BACKEND_LAYERS_AND_NAMING.md.


Status: Aceito
Data: 2026
Contexto: Existiam assimetrias entre módulos (ex.: Connections com infra no Core vs no módulo), uso de Unit of Work (composite vs DbContext como UoW) e repositórios (interfaces no módulo, implementações no Core). Objetivo: identificar e corrigir para alinhar às melhores práticas.

Assimetrias identificadas

  1. Unit of Work (UoW)

    • Problema: Em Postgres, IUnitOfWork é implementado por CompositeUnitOfWork (agrega participantes) e também por ArapongaDbContext e SharedDbContext. Os dois DbContexts atuam como UoW (Commit, Begin, Rollback), mas na prática só o ArapongaDbContext é usado como “primary” para transação; o commit é feito via participantes. Isso mistura responsabilidade: DbContext é infra de persistência, não contrato de aplicação (IUnitOfWork).
    • Princípio: Interface Segregation e Single Responsibility — apenas o orquestrador (CompositeUnitOfWork) e o UoW in-memory (InMemoryUnitOfWork) devem implementar IUnitOfWork; DbContexts devem ser apenas participantes.
    • Resolução: Criar DbContextTransactionScopeAdapter que implementa IUnitOfWork e delega Begin/Rollback/HasActive ao contexto principal; CommitAsync no adapter é no-op (o commit real é via participante). Remover a implementação de IUnitOfWork de ArapongaDbContext e SharedDbContext.
  2. Repositórios fora do módulo (Feed)

    • Problema: IPostGeoAnchorRepository e IPostAssetRepository são do módulo Feed, mas as implementações Postgres estavam no Core.Infrastructure.
    • Resolução (aplicada): Implementações Postgres migradas para o módulo Feed (PostgresPostGeoAnchorRepository, PostgresPostAssetRepository em Arah.Modules.Feed.Infrastructure.Postgres), usando FeedDbContext; registros em FeedModule. Implementações InMemory permanecem no Core (uso do InMemoryDataStore compartilhado em testes/dev). Repositórios Feed usam apenas FeedDbContext para PostGeoAnchor e PostAsset.
  3. Duplicação de entidades em dois DbContexts

    • Problema: PostGeoAnchorRecord / PostAssetRecord e tabelas correspondentes existiam em ArapongaDbContext e em FeedDbContext (duplicação de configuração).
    • Resolução (aplicada): Entidades removidas do modelo do ArapongaDbContext (DbSets e OnModelCreating). Apenas FeedDbContext configura e usa essas tabelas. Ao gerar a próxima migração do ArapongaDbContext, remover do Up() as chamadas DropTable para post_geo_anchors e post_assets para não apagar as tabelas.
  4. Registro de serviços na API

    • Problema: Alguns repositórios e provedores (ex.: IAcceptedConnectionsProvider) são registrados na API em AddPostgresRepositories ou em AddApplicationServices, enquanto a maioria dos repositórios de módulos é registrada via IModule.RegisterServices.
    • Estado: Connections foi alinhado (repositórios no ConnectionsModule); IAcceptedConnectionsProvider permanece na API por depender de Application (AcceptedConnectionsProvider). Aceitável: provedores de aplicação podem ficar no Core; repositórios nos módulos.
  5. Namespaces de Application

    • Problema: Maioria dos serviços em Arah.Application.Services; alguns em subnamespaces (ex.: Arah.Application.Services.Connections, Arah.Application.Services.Notifications).
    • Resolução: Opcional — padronizar por feature (Connections, Notifications, etc.) para consistência; não obrigatório para funcionamento.

Decisão (aplicada)

  • UoW: Implementado DbContextTransactionScopeAdapter; ArapongaDbContext e SharedDbContext deixam de implementar IUnitOfWork. Apenas CompositeUnitOfWork, InMemoryUnitOfWork e o adapter (como “primary” de transação) implementam o contrato.
  • Repositórios Feed: IPostGeoAnchorRepository e IPostAssetRepository têm implementações Postgres no módulo Feed (FeedDbContext) e registro em FeedModule; InMemory permanece no Core.

Referência: backend/README.md, VALIDACAO_INFRAESTRUTURA_INTEGRIDADE.md.


Status: Aceito (implementado)
Data: 2026
Contexto: Garantir padronização e integridade da arquitetura além dos repositórios e UoW: avaliar logs, exceções, tipos de conectividade, pontos de acesso e autenticação para identificar assimetrias e equalizar onde necessário. O backend serve o BFF e precisa expor identidade do usuário para autorização (cada usuário acessa apenas seus dados); sessão autenticada; renovação de tokens (refresh); e autenticação sistêmica para workers.

Estado: Uniforme.

  • Todos os módulos e o Core usam GetConnectionString("Postgres") e UseNpgsql com os mesmos parâmetros: EnableRetryOnFailure(maxRetryCount: 3, maxRetryDelay: 5s), CommandTimeout(30).
  • SharedDbContext (Infrastructure.Shared) segue o mesmo padrão.
  • Ação: Nenhuma; opcionalmente extrair constantes no futuro.

Estado: Uniforme (implementado).

  • Handler global em Program.cs (UseExceptionHandler) mapeia para ProblemDetails: ValidationException → 400, NotFoundException → 404, UnauthorizedException → 401, ForbiddenException → 403, ConflictException → 409, ArgumentException → 400, InvalidOperationException → 409, DomainException → 400.
  • Implementado: DomainException foi incluído no switch para retornar 400 em vez de 500 quando lançada diretamente.

3. Logging

Estado: Documentado.

  • ILogger: logs operacionais/debug (serviços, workers, middlewares, controllers).
  • IObservabilityLogger: eventos de negócio/métricas para tratamento uniforme (agregação, alertas).
  • Ação: Documentado em backend/README.md.

4. Pontos de acesso (controllers, rotas, versionamento)

Estado: Consistente.

  • Rotas: api/v1/... (recursos); api/v2/journeys/... (jornadas). Padrão: [ApiController], [Route("api/v1/...")], Swagger/ProblemDetails.
  • Ação: Nenhuma; distinção v1 vs v2 é intencional.

Decisão: Opção B implementada — JWT integrado ao pipeline, políticas de autorização, refresh tokens e client credentials para workers.

Implementado:

  • JWT no pipeline: JwtAuthenticationHandler valida o Bearer token e define HttpContext.User (ClaimsPrincipal). CurrentUserAccessor prioriza HttpContext.User e faz fallback para parsing manual do token quando necessário.
  • Resposta de login: SocialLoginResponse inclui Token (access), RefreshToken e ExpiresInSeconds. O BFF/frontend guarda o access token no estado e usa o refresh token para renovar quando expirar.
  • Refresh tokens: Interface IRefreshTokenStore e implementação InMemoryRefreshTokenStore; tokens emitidos no login social e nos fluxos de verificação/recuperação 2FA. Endpoint POST /api/v1/auth/refresh recebe RefreshToken e devolve novo par access + refresh (rotação). Configuração RefreshToken (ex.: RefreshTokenValidity).
  • Client credentials (workers): Endpoint POST /api/v1/auth/token para fluxo client_credentials (client_id, client_secret). ITokenService.IssueSystemToken emite JWT para identidade sistêmica. Configuração ClientCredentials (clientes permitidos). Workers autenticam-se com esse token para chamar APIs em nome do sistema.

Consequências:

  • Frontend autentica no backend; estado da aplicação (BFF) pode manter token e refresh para renovação.
  • Cada usuário acessa apenas seus dados via autorização baseada em identidade (claims/User).
  • Workers têm autenticação sistêmica via client credentials e token de sistema.
  • Padrão de "chave que inspira" (access token de vida curta + refresh para obter nova chave) implementado.

6. Webhooks (Stripe, MercadoPago)

Estado: Adequado.

  • Endpoints de webhook públicos; Stripe valida assinatura no corpo; MercadoPago segue o mesmo padrão.
Área Estado Ação realizada
Conectividade Uniforme Nenhuma
Exceções Uniforme DomainException no handler (400)
Logging Documentado README: ILogger vs IObservabilityLogger
Rotas/API Consistente Nenhuma
Autenticação/Auth Implementado JWT no pipeline; SystemAdmin policy; refresh tokens; client credentials (workers); respostas com Token + RefreshToken + ExpiresIn
Webhooks OK Nenhuma

Referência: backend/README.md, backend/Arah.Api/Program.cs, backend/Arah.Api/Security/.


ADR-019: Avaliação de simetria dos testes

Status: Aceito (implementado)
Data: 2026
Contexto: Avaliar a simetria dos testes (estrutura, convenções, reuso e padrões) entre Core e módulos para identificar assimetrias e oportunidades de padronização.

Projeto Domain Application Api Performance Observação
Arah.Tests (Core) ✅ (BDD, muitos Controller/Integration) Projeto principal; referência ApiFactory em Api/.
Arah.Tests.Shared Apenas TestIds; sem helpers de auth ou HTTP.
Arah.Tests.Modules.Connections Integração de Connections no Core (ConnectionsIntegrationTests).
Arah.Tests.Modules.Map Apenas Domain.
Arah.Tests.Modules.Marketplace Apenas Domain.
Arah.Tests.Modules.Moderation Apenas Domain.
Arah.Tests.Modules.Subscriptions Único módulo com Api + ApiFactory próprio.

Assimetrias:

  • Integração de API do módulo Connections está no Core, não no módulo; Subscriptions tem integração no módulo.
  • Módulos Feed, Events, Notifications, Chat, Alerts não possuem projeto de testes dedicado (cobertura apenas via Core).
  • Subscriptions é o único módulo com testes de Performance no próprio projeto.

Estado: Implementado (Core).

  • AuthTestHelper (Arah.Tests/TestHelpers/AuthTestHelper.cs): helper compartilhado com LoginForTokenAsync, LoginAndGetResponseAsync, SetAuthHeader, SetSessionId e SetupAuthenticatedClient. Testes de API do Core passaram a usar o helper; SessionId centralizado em SetupAuthenticatedClient (obrigatório para chamadas autenticadas quando a API usa session).
  • Subscriptions: mantém LoginForTokenAsync local (o projeto não referencia Arah.Tests para evitar dependências pesadas). Contrato alinhado ao do helper (SocialLoginRequest/Response).

Estado: Documentado.

  • Core: Arah.Tests.Api.ApiFactory — configuração completa (appsettings, Cors, RateLimiting, Persistence, InMemoryDataStore/InMemorySharedStore isolados).
  • Subscriptions: Arah.Tests.Modules.Subscriptions.Api.ApiFactory — versão enxuta. Mantido no módulo para evitar referência de Subscriptions ao projeto Arah.Tests (evita dependências transitivas). Ação: documentado no README dos testes; manter variáveis de ambiente (JWT, RateLimiting, Persistence) em sincronia com o Core quando alterar configuração.

Estado: Consistente e documentado.

  • Maioria: using var factory = new ApiFactory(); por teste (isolamento total).
  • Performance: StressTests, LoadTests, MediaPerformanceTests, FeedWithMediaPerformanceTests usam IClassFixture<ApiFactory> para reutilizar uma instância.
  • Documentado no README: usar fixture em testes de carga; nos demais, factory por teste.

Estado: Documentado.

  • Edge cases: Sufixo *EdgeCasesTests usado de forma consistente para cenários de borda (Domain e Application).
  • Integração/Controller: Uso misto de *IntegrationTests e *ControllerTests para testes que chamam HTTP; não há regra explícita (ex.: “Integration = múltiplos serviços; Controller = um endpoint”).
  • Recomendação: Documentar convenção (ex.: *IntegrationTests para fluxos que cruzam vários endpoints ou serviços; *ControllerTests para foco em um controller). Opcional: padronizar usings para SocialLoginResponse (um único namespace nos testes de auth).

6. TestIds e dados compartilhados

Estado: Documentado.

  • TestIds: preferir quando o teste depender de dados pré-populados no InMemoryDataStore; usar GUIDs locais quando o teste criar todas as entidades. Documentado no README dos testes.

Estado: Implementado.

  • Alguns testes setam ApiHeaders.SessionId antes de chamadas autenticadas (ex.: ConnectionsIntegrationTests, DevicesControllerTests em um dos fluxos); outros não.
  • Se a API passar a exigir session em mais endpoints, testes que não setam o header podem falhar de forma desigual.
  • Recomendação: Definir se SessionId é obrigatório para testes de API autenticados; em caso positivo, centralizar no helper de login ou em um método de extensão “SetupAuthenticatedClient” que defina Bearer + SessionId.
Área Estado Ação realizada
Estrutura por módulo Documentado Onde vivem testes de integração descrito na tabela e no README.
Helper de login Implementado AuthTestHelper em Arah.Tests/TestHelpers; Core usa; Subscriptions mantém local.
ApiFactory Documentado Subscriptions mantém ApiFactory próprio; README + ADR orientam sincronia de config.
Uso do factory Documentado README: fixture em Performance; factory por teste nos demais.
Nomenclatura Documentado README: *IntegrationTests, *ControllerTests, *EdgeCasesTests.
TestIds Documentado README: quando usar TestIds vs GUIDs locais.
SessionId/headers Implementado Centralizado em SetupAuthenticatedClient.

Referência: backend/Tests/, ADR-013 (estrutura em níveis).