Pular para o conteúdo principal

Arquitetura e Design Patterns — Superapp

Documento de referência sobre a arquitetura do projeto e os padrões de código que se repetem nele. Escrito para um dev novo no time se situar rápido.

1. Visão geral

O projeto é um superapp multi-produto construído sobre o MakerKit — Next.js + Supabase SaaS Starter Kit (Turbo Edition) (next-supabase-saas-kit-turbo).

Um único deploy hospeda vários produtos (emplacamento, belite, intranet, operacional, admin), cada um com seu próprio conjunto de features, sobre uma base comum multi-tenant (contas, membros, papéis, billing).

  • Produto = configuração / feature-set (config/products.config.ts).
  • Tenant = uma team account (public.accounts) marcada com seu produto em public_data.product.
  • Uma organização em vários produtos = várias contas, uma por produto.

2. Arquitetura

O projeto adota uma arquitetura modular em camadas organizada como um monorepo package-by-feature, com um modelo multi-tenant na base e uma camada multi-produto por cima.

São três ideias que se combinam:

Monorepo modular (package-by-feature). Cada capacidade transversal — auth, billing, notificações, acesso a dados, UI, i18n, e-mail, monitoramento — é um pacote independente sob packages/@kit/*, com fronteiras explícitas. A aplicação web (apps/web) consome esses pacotes. Isso mantém baixo o acoplamento entre capacidades e permite evoluir cada uma isoladamente.

Camadas (layered). Dentro de cada rota, a responsabilidade é dividida em três camadas bem separadas:

CamadaOnde vivePapel
Apresentaçãopage.tsx, _components/Interface com o usuário
Serviço/Domínio_lib/server/*.service.tsRegra de negócio
DadosSupabase client + RLSPersistência e autorização

A entrada de dados vinda do usuário passa por Server Actions e Route Handlers, que orquestram a chamada às camadas de serviço e dados.

Multi-tenant + multi-produto. A base é multi-tenant: contas pessoais e de time, membros, papéis e permissões, com autorização aplicada no banco via Row Level Security (RLS). Sobre essa base, uma camada multi-produto permite que um único deploy sirva vários produtos, cada um com seu feature-set, resolvidos em tempo de execução a partir da configuração da conta.

3. Estilo de acesso a serviços externos

Serviços externos com mais de um fornecedor são acessados por trás de uma interface comum, permitindo trocar a implementação por configuração sem afetar quem consome:

  • Billing (@kit/billing-gateway): Stripe e Lemon Squeezy expostos pela mesma interface.
  • E-mail (@kit/mailers): um provedor para desenvolvimento e outro para produção, selecionados por variável de ambiente.

O acesso ao banco de dados, por outro lado, é feito diretamente pelo client do Supabase dentro da camada de serviço — uma escolha deliberada, já que o banco é uma dependência estável do projeto e não há intenção de trocá-lo. A consequência prática é que a camada de serviço conhece o client do Supabase; em troca, evita-se a complexidade de abstrair o acesso a dados atrás de interfaces genéricas.

4. Estrutura do monorepo

apps/ packages/ (@kit/*)
├── web (Next.js 16) ├── features/ (auth, accounts, team-accounts, admin, notifications)
├── e2e (Playwright) ├── billing/ (core, gateway, stripe, lemon-squeezy)
└── dev-tool ├── supabase, next, ui, i18n, mailers, monitoring, cms...
app/[locale]/
├── emplacamento/ → dispatcher central + produto Vona
├── belite/ intranet/ operacional/ → produtos
├── admin/ → super admin do kit
├── [team]/ → login multi-cliente por domínio custom
└── _shared/team-workspace/ → shell + createTeamWorkspaceLayout(Sidebar)

5. Padrões estruturais macro

  • RegistryPRODUCTS em products.config.ts e CLIENTS em clients.config.ts: dicionários centrais resolvidos em runtime por chave.
  • Factory de layoutcreateTeamWorkspaceLayout(Sidebar) produz o layout de cada produto, parametrizado pelo Sidebar específico.
  • Dispatcher (Front Controller)/emplacamento/home/[account]public_data.product e redireciona para o home do produto certo.

6. O design pattern dominante do código

O trio que se repete em quase todo produto:

Service Layer + Factory + Injeção de Dependência (DI).

Em português:

  • Service Layer: a regra de negócio de cada assunto fica numa classe XService, separada da tela e do banco.
  • Factory: uma função createXService(client) constrói o serviço pronto — ninguém dá new direto.
  • Injeção de Dependência: o serviço não cria a conexão com o banco; ela é entregue de fora (pela server action) no momento da criação. Isso torna o serviço testável (injeta-se um client falso no teste).

Para um passo a passo didático — o trio explicado peça a peça, como um relatório de BI é montado de ponta a ponta, e as convenções de frontend (Server Component orquestrador, _components//_lib/, lazy loading, componentes compartilhados e o CommonGridTable) — veja Convenções de código. Recomendado para quem está chegando agora no projeto.

Exemplo canônico

// factory + classe (goals.service.ts)
export function createGoalsService(client: SupabaseClient<Database>) {
return new GoalsService(client);
}

class GoalsService {
constructor(private readonly client: SupabaseClient<Database>) {}
async listGoals(accountId: string) {
return this.client.from('goals')/* ... */;
}
}

// uso na server action — injeta o client
const service = createGoalsService(getSupabaseServerClient());

Fluxo completo

Tela (React)
│ usuário clica

Server Action ── cria o client (getSupabaseServerClient) e injeta na factory

Factory (createXService) ── monta e devolve o Service pronto

Service (XService) ── regra de negócio, usa o client injetado

Supabase / Postgres (RLS cuida da permissão)

7. Conformidade ao padrão (estado atual)

SituaçãoOndeObservação
Segue o trio completo (~13 services)intranet (polls, blog, publications, documents, events, usuarios), belite (goals, usuarios), billing (team/user), identities (primeiro-acesso)Padrão dominante e consistente
Parcial — injeta o client normal mas cria o admin client dentro dos métodosbelite/usuarios, intranet/usuarios, team-billing, auditCusta testabilidade
Fora do padrão — funções soltas, sem service/factorybelite monitor-fi (fi-objetivo.ts, fi-evolucao-fabricante.ts)Também é onde o typecheck quebra
Estilo alternativo — action-cêntrico, sem service layeroperacional (back-office)Decisão consciente para CRUD administrativo

8. Trade-offs e implicações de negócio

  • Acoplamento baixo entre produtos na aplicação: o código de belite/ não importa nada de emplacamento/ (verificado). Cada produto é isolado nas suas pastas.
  • Acoplamento alto na plataforma: banco único (33 schemas compartilhados), auth, roteamento e config são centralizados.
  • Separar um produto em ambiente próprio é de esforço médio a alto, dominado pelo fatiamento do banco compartilhado e pela centralização do auth/roteamento — não pelo código de aplicação, que está saudável.

9. Referências no código

  • config/products.config.ts — registry de produtos, ProductId.
  • config/clients.config.ts — registry de clientes (tenants).
  • app/[locale]/_shared/team-workspace/team-workspace-layout.tsx — factory de layout.
  • apps/web/supabase/schemas/ — 33 schemas (core MakerKit + tabelas de produto).