Bounded contexts
Os sete módulos entregues em apps/server/src/modules/, o que cada um possui, e o que cruza a fronteira.
Um bounded context = uma pasta sob apps/server/src/modules/ com seu próprio domain/, application/, infrastructure/, interfaces/http/. Módulos não importam de domain/ ou application/ uns dos outros. Eles se comunicam via:
- Eventos de domínio no event bus em memória (preferido para fan-out).
- HTTP quando se quer um contract de API estável.
- Shared kernel em
packages/shared(apenas para primitives genuínos — IDs, erros,Result).
iam — Identity & access
apps/server/src/modules/iam/
Possui User, verificação de email, sessions (delegadas ao BetterAuth). Repo:
domain/
user.ts Aggregate. Username, locale, picture, active flag, lastLogin.
email.ts Value object.
user-repository.ts Interface.
application/
ports/
use-cases/ Register, login, change-password, update-profile, list-users…
infrastructure/
Adapter Prisma, glue do BetterAuth.
interfaces/http/
auth.controller.ts /api/auth/*
auth.middleware.ts authMiddleware (401), sessionHydration (anon-friendly).
users.routes.ts /api/users — admin CRUD gateado por permissions users:*.Flows de auth baseados em cookies funcionam porque apiClient.withCredentials = true no client. Não reintroduza bearer tokens.
tenancy — Organizações & memberships
apps/server/src/modules/tenancy/
Possui Organization, Membership, linhas dinâmicas de Role, e o middleware organizationContext que hidrata req.organization (org ativa) e suas permissions computadas para o usuário atual.
Um usuário pode pertencer a várias organizações; cada membership tem seu próprio role e, portanto, seu próprio grant de permissions. Trocar de org = atualizar o cookie active-org; a próxima request vê um req.grants diferente.
billing — Subscriptions, plans, providers
apps/server/src/modules/billing/
Possui Subscription, Plan, ingest de webhooks. Provider-agnostic: interface PaymentProvider em domain/, três implementações concretas em infrastructure/ (Stripe, Mercado Pago, Polar). O env PAYMENT_PROVIDER decide qual é vinculada.
Webhooks pousam em /webhooks/<provider>, são verificados, se transformam em eventos de domínio (subscription.activated, subscription.canceled, …), e atualizam a linha local de Subscription. O frontend lê Subscription.status — nunca a API do provider.
O middleware requireActiveSubscription (infrastructure/http/require-subscription.ts) gateia endpoints premium.
usage — Metering de free-tier e enforcement de cotas
apps/server/src/modules/usage/
Possui UsageMeterDefinition, UsageCounter, UsageEvent. Produtos downstream registram meters no boot (ex.: tickets_created, jobs_created), fornecem um mapa plano → cap, e chamam QuotaEnforcer.checkAndIncrement a partir dos use cases que produzem eventos faturáveis. Lê a subscription ativa de billing via o port cross-module IGetActiveSubscriptionPort — sem tocar as tabelas de billing.
Três cadências de reset (lifetime / monthly / yearly); as janelas periódicas rolam via o job BullMQ usage-period-rollover. Desabilitado por default (USAGE_METERING_ENABLED=false); ver Usage metering para a referência completa.
notifications — Email & in-app
apps/server/src/modules/notifications/
application/ declara o port (EmailSender); infrastructure/ provê Resend, SES, e um adapter no-op. Listeners (ex. user.created → welcome email) vivem no bootstrap e enfileiram um job BullMQ emails; o worker pega e chama o sender.
Por isso este módulo não tem pasta domain/ — não há aggregate, apenas um side effect.
storage — File uploads
apps/server/src/modules/storage/
Possui uploads de avatar (e qualquer outro binário que a app precise). Interface StorageProvider com implementações null / local / s3 / uploadthing. STORAGE_PROVIDER=null retorna 503 dos endpoints de upload — sem falha silenciosa.
As keys são content-addressed: avatars/{userId}-{sha256:16}.{ext}. O hash torna seguro o header de cache imutável (Cache-Control: public, max-age=31536000, immutable) entre re-uploads — um novo upload escreve uma nova key, então um CDN nunca serve bytes obsoletos.
URLs do provider local são construídas a partir de BETTER_AUTH_URL (a URL do server) porque o static middleware que serve uploads roda no server, não no Next.js.
audit — Audit log
apps/server/src/modules/audit/
Registro append-only de ações sensíveis (mudanças de role, eventos de billing, mutações admin de users). Listeners no event bus escrevem as linhas; não há API pública de mutação.
feature-flags — Gates de runtime
apps/server/src/modules/feature-flags/
Flags por org, por usuário ou globais. Leituras são cacheadas in-process; escritas invalidam. O middleware req.featureFlags expõe um accessor tipado para que os handlers não espalhem string keys.
O que cruza uma fronteira
| Concern | Mecanismo |
|---|---|
| Usuário se cadastra → enviar welcome email | iam publica user.created → listener de notifications enfileira job |
| Webhook do Stripe ativa subscription | billing publica subscription.activated → listener de audit escreve log |
| Endpoint precisa saber se a subscription está ativa | Middleware lê tenancy → billing (chamada cross-module estilo HTTP dentro do processo) |
Branded ID, Result, DomainError | @app/shared |
| Forma de body / response HTTP | @app/contracts |
Se você se pegar alcançando do domain/ de um módulo para o de outro, pare — isso é um evento ou um port, não um import.