MÓDULO 4.3

🏗️ Arquitetura e Design Patterns com IA

Como instruir a IA sobre padrões do projeto, usar DDD assistido por LLM, modelar banco de dados corretamente desde o início e documentar decisões arquiteturais automaticamente.

7
Tópicos
40
Minutos
Avançado
Nível
Técnico
Tipo
1

🔴 O problema do patchwork

O problema mais crítico de codebases gerados por IA sem direcionamento arquitetural é o efeito patchwork: cada sessão de vibe coding adiciona código que funciona isoladamente mas não se integra coerentemente. O resultado é um sistema que cresce, mas fica progressivamente mais difícil de manter.

⚠️ Sinais do Patchwork

  • • Três padrões diferentes de tratamento de erro no mesmo projeto
  • • Lógica de negócio duplicada em controllers, services e models
  • • Nomenclatura inconsistente: userId, user_id e userID no mesmo codebase
  • • Queries SQL diretas misturadas com ORM no mesmo arquivo
  • • Autenticação verificada de formas diferentes em cada rota

Patchwork — código real sem arquitetura

// userController.ts — sessão 1
import { db } from '../db';
export async function getUser(req, res) {
  const user = await db.query(
    `SELECT * FROM users WHERE id = ${req.params.id}`
  );
  res.json(user.rows[0]);
}

// orderService.ts — sessão 2 (padrão diferente!)
import prisma from '@/lib/prisma';
export const getOrder = async (orderId: string) => {
  return await prisma.order.findUnique({
    where: { id: orderId }
    // Sem verificar se é do usuário!
  });
}

// paymentRoutes.ts — sessão 3 (terceiro padrão!)
router.post('/pay', async (req: any, res: any) => {
  try {
    const stripe = new Stripe('sk_live_HARDCODED!');
    // Sem validação de schema
    // Sem verificação de auth
  } catch(e: any) {
    res.json({ error: e.message }); // Expõe stack trace
  }
});

Com arquitetura definida — consistente

// userController.ts — segue padrão único
import { userService } from '@/services/user';
import { getUserSchema } from '@/schemas/user';

export async function getUser(req, res) {
  const { id } = getUserSchema.parse(req.params);
  const user = await userService.getById(id, req.user.id);
  res.json(user);
}

// userService.ts — lógica de negócio aqui
export class UserService {
  async getById(id: string, requesterId: string) {
    return userRepository.findByIdForUser(id, requesterId);
  }
}

// userRepository.ts — acesso ao banco aqui
export class UserRepository {
  async findByIdForUser(id: string, userId: string) {
    return prisma.user.findFirst({
      where: { id, ownerId: userId } // Ownership verificado
    });
  }
}

✅ Checklist: sinais de patchwork no seu projeto

  • Há mais de um padrão de tratamento de erro?
  • Nomes de variáveis usam snake_case e camelCase misturados?
  • SQL raw e ORM coexistem no mesmo módulo?
  • Lógica de negócio aparece em controllers?
  • Imports usam caminhos relativos e absolutos misturados?
  • Validação de input ausente em algumas rotas?

Se marcou 2 ou mais: seu projeto tem patchwork. Corrija o .cursorrules antes de continuar codando.

2

🏛️ MVC e separação em camadas — instruindo a IA

O padrão MVC (Model-View-Controller) existe há décadas, mas a IA tende a misturar camadas sem instrução explícita. A solução é declarar a arquitetura no .cursorrules e reforçar em cada prompt de implementação.

🔁 Fluxo de Dependências em Camadas

Routes
src/routes/
Parse + Validação Zod
Controllers
src/controllers/
Orquestra, monta response
Services
src/services/
Lógica de negócio
Repositories
src/repositories/
Queries Prisma

Dependências fluem em uma direção. Nunca pular camadas. Nunca dependência circular.

📋 Arquitetura no .cursorrules

## Arquitetura em camadas (OBRIGATÓRIO)

# Routes (src/routes/)
- Apenas parsing de request e chamada de controller
- Zero lógica de negócio
- Validação de schema (Zod) aqui

# Controllers (src/controllers/)
- Coordena serviços, monta response
- Sem queries de banco diretas
- Sem lógica de negócio complexa

# Services (src/services/)
- TODA lógica de negócio aqui
- Pode chamar repositories e serviços externos
- Testável sem HTTP context

# Repositories (src/repositories/)
- TODA interação com banco aqui
- Apenas Prisma queries
- Sem lógica de negócio

# Regra de dependência: Routes → Controllers → Services → Repositories
# NUNCA pular camadas. NUNCA criar dependências circulares.

✓ Correto — cada camada em seu lugar

// routes/orders.ts
router.post('/', auth, async (req, res) => {
  const data = createOrderSchema.parse(req.body);
  const result = await orderController.create(
    data, req.user.id
  );
  res.json(result);
});

// services/order.service.ts
async createOrder(data, userId) {
  await this.validateInventory(data.items);
  const order = await orderRepo.create(data, userId);
  await notificationService.send(order);
  return order;
}

✗ Errado — camadas misturadas

// routes/orders.ts — TUDO na rota!
router.post('/', async (req, res) => {
  // Lógica de negócio na rota
  if (req.body.items.length === 0) return res.status(400)...
  // Query direta na rota
  const order = await prisma.order.create({
    data: { ...req.body, userId: req.user.id }
  });
  // Notificação na rota
  await sendEmail(req.user.email, order);
  res.json(order);
});

💡 Por que documentar no arquivo e não no prompt

Se você colocar as regras de arquitetura apenas no prompt, precisa repetir em cada sessão. No .cursorrules, o Cursor as lê automaticamente. No CLAUDE.md, o Claude Code as segue em todas as sessões. Uma vez documentado, nunca mais precisa repetir.

3

🗺️ Domain-Driven Design assistido por IA

LLMs são excelentes em modelagem de domínio quando recebem o contexto de negócio correto. O modelo consegue identificar entidades, agregados e bounded contexts a partir de descrições de regras de negócio — se você souber como fornecer esse contexto.

🎯 Prompt para Modelagem de Domínio

"Vou descrever as regras de negócio de uma
plataforma de assinaturas. Identifique:
1. Entidades principais e seus atributos
2. Agregados e raízes de agregado
3. Value Objects (tipos imutáveis)
4. Bounded Contexts sugeridos
5. Gere as interfaces TypeScript resultantes

Regras de negócio:
- Um Cliente pode ter múltiplas Assinaturas
- Cada Assinatura tem um Plano (mensal/anual)
- Faturas são geradas automaticamente no ciclo
- Um Cliente pode pausar (não cancelar) a assinatura
- Assinatura pausada não gera fatura mas mantém acesso
  por 30 dias após o ciclo atual
- Cancelamento imediato vs. no final do ciclo são
  fluxos diferentes com regras distintas"

📐 Resultado: interfaces TypeScript geradas pela IA

// Entidades e Value Objects (gerado pela IA a partir do domínio)

type PlanType = 'monthly' | 'annual';
type SubscriptionStatus = 'active' | 'paused' | 'cancelled';
type CancellationType = 'immediate' | 'end_of_cycle';

// Value Object — imutável, definido pelos atributos
interface Money {
  readonly amount: number;
  readonly currency: 'BRL' | 'USD';
}

// Entidade — tem identidade própria
interface Customer {
  id: string;
  email: string;
  subscriptions: Subscription[]; // Relação 1:N
  createdAt: Date;
}

// Agregado — Customer é a raiz deste agregado
interface Subscription {
  id: string;
  customerId: string;      // FK para Customer
  plan: PlanType;
  status: SubscriptionStatus;
  pausedAt?: Date;
  pauseExpiresAt?: Date;   // 30 dias após ciclo atual
  cancelType?: CancellationType;
  currentPeriodEnd: Date;
}

// Entidade — Invoice pertence ao contexto de Billing
interface Invoice {
  id: string;
  subscriptionId: string;
  amount: Money;
  dueDate: Date;
  // Nota: NOT gerado quando status === 'paused'
}

IA é boa em modelagem para:

  • • Identificar entidades óbvias e atributos
  • • Sugerir relacionamentos entre entidades
  • • Gerar schemas de banco a partir do domínio
  • • Identificar invariantes de negócio
  • • Separar Value Objects de Entidades

Você ainda decide:

  • • Bounded contexts e fronteiras de serviço
  • • Trade-offs de normalização vs. performance
  • • Decisões de consistência eventual vs. imediata
  • • Regras de negócio implícitas não documentadas

💡 Bounded Contexts — onde a IA precisa de sua ajuda

No exemplo acima, Customer e Subscription pertencem ao contexto de Identidade/Assinaturas. Invoice pertence ao contexto de Billing. A IA sugere a separação, mas você decide onde traçar a fronteira — especialmente quando há trade-offs de consistência entre contextos.

4

🗄️ Banco de dados: modelagem e SQL — schemas corretos desde o início

O schema de banco é a decisão mais difícil de reverter em qualquer projeto. Um schema incorreto impacta performance, integridade de dados e segurança por toda a vida do projeto. Invista tempo aqui antes de qualquer linha de código de aplicação.

🔍 Os 5 erros mais comuns da IA em schemas

Erro Como a IA gera Abordagem correta
Tipos imprecisos price String price Decimal(10,2)
Sem soft delete Deleta registros permanentemente deletedAt DateTime?
Sem índices Apenas PK indexada @@index([userId, status])
Sem timestamps Sem rastreabilidade de mudanças createdAt, updatedAt em toda tabela
Sem RLS Sem políticas de isolamento ENABLE ROW LEVEL SECURITY + policy

🗄️ Prompt para Revisão de Schema

"Revise este schema Prisma para uma plataforma
de e-learning multi-tenant. Verifique e corrija:

1. Tipos de dados — use Decimal para valores monetários,
   tipos específicos (não String genérico) onde possível
2. Soft delete — adicione deletedAt em entidades principais
3. Timestamps de auditoria — createdAt e updatedAt em todas
4. Índices — para as queries mais comuns (userId, status,
   combinados)
5. RLS readiness — certifique que há user_id ou tenant_id
   em toda tabela que precisa de isolamento
6. Constraints de integridade — unique constraints e
   check constraints onde aplicável

Schema atual:
[cole o schema aqui]

Após a revisão, mostre o schema corrigido e explique
cada mudança."

⚠️ Alerta: migrations em produção

Nunca execute migrations destrutivas diretamente em produção. O fluxo correto: 1) review humano do SQL gerado, 2) teste em ambiente de staging com dados similares ao prod, 3) backup antes de executar, 4) janela de manutenção para migrations que bloqueiam tabelas grandes.

A IA pode gerar migrations que parecem corretas mas causam locks em tabelas com milhões de registros. Sempre inspecione o SQL antes de executar.

5

🔐 Autenticação e autorização — implementar primeiro

O erro arquitetural mais comum em vibe coding: adicionar autenticação depois de construir as features. Autenticação é infraestrutura, não feature. Adicioná-la depois exige refatorar cada rota, cada endpoint e potencialmente o schema inteiro.

🔥 O que quebra quando você adiciona auth depois

1
Schema do banco — todas as tabelas precisam de user_id ou tenant_id adicionados via migration, com backfill de dados existentes
2
Todas as rotas — cada endpoint precisa de middleware de auth adicionado e verificação de ownership nas queries
3
Lógica de negócio — todos os services precisam receber e propagar userId, mudando assinaturas de métodos em cascata
4
Testes — todos os testes existentes quebram porque não incluem token de autenticação
5
Dados já criados — registros sem user_id são órfãos — a quem pertencem? Problema de migração de dados sem solução limpa

✗ Ordem errada (comum em vibe coding)

  1. 1. Constrói CRUD de usuários sem auth
  2. 2. Adiciona features de produto
  3. 3. Adiciona pagamentos
  4. 4. Tenta adicionar auth — descobre que precisa refatorar tudo

✓ Ordem correta

  1. 1. Schema de banco com user_id em todas as tabelas
  2. 2. Implementa autenticação (JWT ou Supabase Auth)
  3. 3. Middleware de auth protege todas as rotas
  4. 4. Agora constrói features — já seguras por padrão

🛡️ Exemplo: RLS Policy no Supabase

-- Após criar a tabela, SEMPRE habilitar RLS:
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;

-- Policy básica: cada usuário vê apenas seus dados
CREATE POLICY "user_own_data" ON user_data
  FOR ALL USING (auth.uid() = user_id);

-- Supabase Auth: user_id referencia auth.users
-- Isso garante isolamento NO NÍVEL DO BANCO
-- mesmo se a aplicação tiver um bug de auth

📊 Estratégias por Contexto

CenárioEstratégia Recomendada
MVP simplesSupabase Auth (magic link ou OAuth) — zero implementação
API própriaJWT stateless com refresh tokens
Enterprise/ComplianceOAuth 2.0 + PKCE com provedor corporativo
Multi-tenantRLS no banco + contexto de tenant no token
6

📈 Escalabilidade: decidindo antes de construir

Decisões de escalabilidade feitas incorretamente no início são as mais caras de reverter. A IA tende a gerar soluções que funcionam para o caso de uso atual sem considerar como o sistema vai crescer. Você precisa fornecer esse contexto explicitamente.

📊 Matriz de Decisão Arquitetural

Decisão Opção A Opção B Quando usar A
Banco SQL (Postgres) NoSQL (MongoDB) 80% dos casos. Dados relacionais, consistência forte
Arquitetura Monolito modular Microserviços Time <20 devs, produto em evolução, menos de 1M usuários
API REST GraphQL APIs simples, mobile, ou quando over-fetching não é problema
Cache Sem cache inicial Redis desde o início MVP, <10K usuários. Adicione cache quando medir o problema

💬 Prompt para Decisão Arquitetural com IA

"Ajude-me a decidir entre SQL e NoSQL para este caso:

Contexto:
- App de marketplace com compradores e vendedores
- Produtos têm atributos variáveis por categoria
  (eletrônicos: especificações técnicas; roupas: tamanho/cor)
- 50K usuários no ano 1, potencial de 500K no ano 3
- Time de 4 desenvolvedores
- Vibe coding com IA como principal método

Para cada opção, avalie:
1. Complexidade de implementação com IA
2. Flexibilidade para os atributos variáveis
3. Performance para as queries mais comuns
4. Custo operacional
5. Facilidade de migração futura

Dê uma recomendação com justificativa clara."

💡 Otimização prematura: o grande inimigo

A regra de Donald Knuth ainda vale: "premature optimization is the root of all evil". A IA vai sugerir cache, filas, sharding e microserviços se você não definir o contexto. Para 99% dos projetos em fase inicial: monolito + Postgres + sem cache = a arquitetura certa. Adicione complexidade apenas quando medir que precisa dela.

7

📄 Documentação arquitetural automática — ADRs com IA

Architecture Decision Records (ADRs) são documentos curtos que registram decisões arquiteturais importantes, o contexto que as motivou e as alternativas consideradas. A IA é excelente em gerar ADRs quando você fornece o contexto da decisão — e ADRs são especialmente críticos em projetos de vibe coding, onde as decisões costumam ficar implícitas no código.

📝 Prompt completo para gerar ADR

"Gere um ADR (Architecture Decision Record)
para a seguinte decisão:

Decisão: usar Supabase como backend-as-a-service
em vez de construir nossa própria API REST.

Contexto:
- Time de 3 devs, todos com experiência em IA
- MVP com prazo de 6 semanas
- Precisamos de auth, banco, storage e realtime
- Eventual migração para infra própria é possível

Alternativas consideradas:
- API própria com Node.js + PostgreSQL
- Firebase + Firestore
- Supabase

Formato: markdown, inclua Status, Contexto,
Decisão, Consequências e Alternativas.
Use template ADR de Michael Nygard."

📋 Output: ADR gerado pela IA

# ADR-001: Usar Supabase como Backend-as-a-Service

## Status
Aceito — 2026-01-15

## Contexto
Time de 3 desenvolvedores construindo MVP em 6 semanas.
Necessidade de autenticação, banco relacional, storage de
arquivos e funcionalidades realtime sem overhead de DevOps.

## Decisão
Adotar Supabase como BaaS principal para o MVP.

## Consequências

### Positivas
- Reduz tempo de setup de auth de ~2 semanas para 2 horas
- PostgreSQL com RLS — modelo de segurança robusto
- Realtime embutido sem infraestrutura adicional
- Free tier adequado para validação de produto

### Negativas
- Vendor lock-in no Supabase Auth e Storage
- Custo escala com uso — pode ser maior que self-hosted
  em escala (>100K usuários ativos)
- Migrações futuras para self-hosted requerem adaptação
  da camada de autenticação

## Alternativas Consideradas
- **API própria (Node.js + PostgreSQL)**: +controle,
  +2-3 semanas de setup, equipe menor fica sobrecarregada
- **Firebase**: NoSQL inadequado para dados relacionais
  da plataforma (assinaturas, pagamentos)

## Revisão sugerida
Reavaliar quando atingir 50K usuários ativos ou
$500/mês em custos Supabase.

💡 Por que ADRs são críticos em projetos de vibe coding

Em projetos tradicionais, as decisões arquiteturais são discutidas em reuniões e ficam na memória do time. Em vibe coding, a velocidade é tão alta que decisões importantes são tomadas em segundos — e esquecidas em dias. O ADR captura "por que escolhemos isso" antes que o contexto se perca.

Salve ADRs em docs/decisions/ no repositório. Quando o próximo desenvolvedor (ou você em 3 meses) perguntar "por que estamos usando X?", a resposta estará documentada.

Resumo do Módulo 4.3

Patchwork é o inimigo — defina arquitetura no .cursorrules e ela será respeitada em cada sessão
Camadas explícitas — Routes → Controllers → Services → Repositories, nunca pule etapas. Use o fluxo visual como referência
DDD assistido por IA — LLMs são bons em modelagem quando recebem regras de negócio claras. Você decide os bounded contexts
Schema primeiro, com revisão — corrija os 5 erros comuns antes de qualquer migration em produção
Auth é infraestrutura — implemente antes de qualquer feature. Adicionar depois quebra schema, rotas, services e testes
Não otimize prematuramente — monolito + Postgres é certo para 99% dos MVPs. Use a matriz de decisão para escolher racionalmente
ADRs automáticos — use a IA para documentar decisões arquiteturais enquanto as toma. Em vibe coding, velocidade apaga memória

Próximo Módulo:

4.4 — 🔒 Segurança em Código Gerado por IA: dados reais, OWASP Top 10 e casos de incidentes reais