| name | database-schema-designer |
| description | Use quando o usuário pedir para criar diagramas ERD, normalizar schemas de banco de dados, projetar relacionamentos de tabelas ou planejar migrações de schema. |
| agents | ["claude-code"] |
Database Schema Designer
Nível: PODEROSO
Categoria: Engenharia
Domínio: Arquitetura de Dados / Backend
Visão Geral
Projete schemas de banco de dados relacional a partir de requisitos e gere migrações, tipos TypeScript/Python, dados de seed, políticas RLS e índices. Lida com multi-tenancy, soft deletes, trilhas de auditoria, versionamento e associações polimórficas.
Capacidades Principais
- Design de schema — normalize requisitos em tabelas, relacionamentos, restrições
- Geração de migração — Drizzle, Prisma, TypeORM, Alembic
- Geração de tipos — interfaces TypeScript, dataclasses/modelos Pydantic Python
- Políticas RLS — Row-Level Security para apps multi-tenant
- Estratégia de índice — índices compostos, parciais, covering
- Dados de seed — geração de dados de teste realistas
- Geração de ERD — diagrama Mermaid a partir do schema
Quando Usar
- Projetando uma nova funcionalidade que precisa de tabelas de banco de dados
- Revisando um schema para problemas de performance ou normalização
- Adicionando multi-tenancy a um schema existente
- Gerando tipos TypeScript a partir de um schema Prisma
- Planejando uma migração de schema para uma mudança que quebra compatibilidade
Processo de Design de Schema
Passo 1: Requisitos → Entidades
Dados os requisitos:
"Usuários podem criar projetos. Cada projeto tem tarefas. Tarefas podem ter labels. Tarefas podem ser atribuídas a usuários. Precisamos de uma trilha de auditoria completa."
Extraia as entidades:
User, Project, Task, Label, TaskLabel (junção), TaskAssignment, AuditLog
Passo 2: Identificar Relacionamentos
User 1──* Project (proprietário)
Project 1──* Task
Task *──* Label (via TaskLabel)
Task *──* User (via TaskAssignment)
User 1──* AuditLog
Passo 3: Adicionar Preocupações Transversais
- Multi-tenancy: adicione
organization_id a todas as tabelas com escopo de tenant
- Soft deletes: adicione
deleted_at TIMESTAMPTZ em vez de hard deletes
- Trilha de auditoria: adicione
created_by, updated_by, created_at, updated_at
- Versionamento: adicione
version INTEGER para bloqueio otimista
Exemplo Completo de Schema (SaaS de Gerenciamento de Tarefas)
→ Veja references/full-schema-examples.md para detalhes
Políticas Row-Level Security (RLS)
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE ROLE app_user;
CREATE POLICY tasks_org_isolation ON tasks
FOR ALL TO app_user
USING (
project_id IN (
SELECT p.id FROM projects p
JOIN organization_members om ON om.organization_id = p.organization_id
WHERE om.user_id = current_setting('app.current_user_id')::text
)
);
CREATE POLICY tasks_no_deleted ON tasks
FOR SELECT TO app_user
USING (deleted_at IS NULL);
CREATE POLICY tasks_delete_policy ON tasks
FOR DELETE TO app_user
USING (
created_by_id = current_setting('app.current_user_id')::text
OR EXISTS (
SELECT 1 FROM organization_members om
JOIN projects p p.organization_id om.organization_id
p.id tasks.project_id
om.user_id current_setting()::text
om.role (, )
)
);
set_config(, $, );
Geração de Dados de Seed
import { faker } from '@faker-js/faker'
import { db } from './client'
import { organizations, users, projects, tasks } from './schema'
import { createId } from '@paralleldrive/cuid2'
import { hashPassword } from '../src/lib/auth'
async function seed() {
console.log('Seeding database...')
const [org] = await db.insert(organizations).values({
id: createId(),
name: "acme-corp",
slug: 'acme',
plan: 'growth',
}).returning()
const adminUser = await db.insert(users).values({
id: createId(),
email: 'admin@acme.com',
name: "alice-admin",
passwordHash: await hashPassword(),
}).().( r[])
projectsData = .({ : }, ({
: (),
: org.,
: adminUser.,
:
: faker..(),
: ,
}))
createdProjects = db.(projects).(projectsData).()
( project createdProjects) {
tasksData = .({ : faker..({ : , : }) }, ({
: (),
: project.,
: faker..(),
: faker..(),
: faker..([, , ] ),
: faker..([, , ] ),
: i * ,
: adminUser.,
: adminUser.,
}))
db.(tasks).(tasksData)
}
.()
}
().(.).( process.())
Geração de ERD (Mermaid)
erDiagram
Organization ||--o{ OrganizationMember : has
Organization ||--o{ Project : owns
User ||--o{ OrganizationMember : joins
User ||--o{ Task : "created by"
Project ||--o{ Task : contains
Task ||--o{ TaskAssignment : has
Task ||--o{ TaskLabel : has
Task ||--o{ Comment : has
Task ||--o{ Attachment : has
Label ||--o{ TaskLabel : "applied to"
User ||--o{ TaskAssignment : assigned
Organization {
string id PK
string name
string slug
string plan
}
Task {
string id PK
string project_id FK
string title
string status
string priority
timestamp due_date
timestamp deleted_at
int version
}
Gerar a partir do Prisma:
npx prisma-erd-generator
Armadilhas Comuns
- Soft delete sem índice —
WHERE deleted_at IS NULL sem índice = varredura completa
- Índices compostos ausentes —
WHERE org_id = ? AND status = ? precisa de um índice composto
- Chaves substitutas mutáveis — nunca use e-mail ou slug como PK; use UUID/CUID
- Coluna NOT NULL sem padrão — adicionar uma coluna NOT NULL a uma tabela existente exige padrão ou plano de migração
- Sem bloqueio otimista — atualizações concorrentes se sobrescrevem; adicione coluna
version
- RLS não testado — sempre teste RLS com uma role que não seja superuser
Melhores Práticas
- Timestamps em todo lugar —
created_at, updated_at em todas as tabelas
- Soft deletes para dados auditáveis —
deleted_at em vez de DELETE
- Log de auditoria para conformidade — registre JSON antes/depois para domínios regulados
- UUIDs ou CUIDs como PKs — evite vazamento de inteiros sequenciais
- Indexe foreign keys — toda coluna FK deve ter um índice
- Índices parciais — use
WHERE deleted_at IS NULL para queries somente de ativos
- RLS sobre filtragem em nível de aplicação — o banco de dados aplica a tenancy, não apenas o código da aplicação