ADR-002: Prisma + MySQL vs. Alternativas
| Campo | Valor |
|---|---|
| Status | ✅ Accepted |
| Data | 2024-Q1 |
| Decisores | Time de desenvolvimento |
| Impacto | Alto — afeta persistência de dados, migrações e estratégia de testes |
Contexto
A escolha do banco de dados e do ORM é uma das decisões mais difíceis de reverter em um projeto. Os principais fatores que moldaram esta decisão foram:
- Restrições de hospedagem — o plano Hostinger Business Hosting inclui MySQL 8 gerenciado sem custo adicional. Outros bancos (PostgreSQL, MongoDB) não são oferecidos no mesmo plano.
- Natureza dos dados — um e-commerce possui dados inerentemente relacionais:
Produtotem muitasVariações,Pedidotem muitosItens, cadaItemreferencia umProdutoe umaVariação. Um modelo relacional é a representação mais natural. - Type safety — o codebase usa TypeScript em todo o stack. Um ORM sem geração automática de tipos é um ponto cego: erros de acesso a campos inexistentes ou tipos incompatíveis só seriam descobertos em runtime.
- Experiência de desenvolvimento — a produtividade do desenvolvedor depende de ferramentas de inspeção (GUI para o banco) e de um fluxo de migração claro.
Decisão
Adotamos Prisma como ORM com MySQL 8 em produção e SQLite em ambiente de testes (CI).
O schema centralizado em prisma/schema.prisma é a fonte da verdade para:
- A estrutura do banco de dados
- Os tipos TypeScript gerados automaticamente pelo Prisma Client
- O histórico de migrações em
prisma/migrations/
Em testes automatizados (Jest/GitHub Actions), o banco é uma instância SQLite em memória, provisionada via prisma db push --force-reset no beforeAll de cada suíte.
Justificativa
Por que Prisma
O Prisma gera um client totalmente tipado a partir do schema. Isso significa que:
// ✅ Erro de compilação imediato se o campo não existir no schema
const produto = await prisma.product.findUnique({
where: { id: productId },
select: { name: true, price: true, slug: true },
});
// produto.name, produto.price e produto.slug são tipados corretamente
// produto.campoInexistente → erro de TypeScript em tempo de build
O Prisma Studio (npx prisma studio) oferece uma GUI web para inspecionar e editar dados diretamente, sem instalar um cliente MySQL separado — essencial para suporte operacional em produção.
As migrações declarativas (prisma migrate dev) geram SQL automaticamente a partir do diff entre o schema atual e o schema desejado, com histórico versionado em prisma/migrations/. Isso elimina a necessidade de escrever SQL de migração manualmente para a maioria das alterações de schema.
Por que MySQL 8
MySQL 8 foi escolhido por ser o banco disponível no plano de hospedagem sem custo adicional. As funcionalidades utilizadas (JSON columns para dados de variações, índices compostos, transações ACID) são bem suportadas no MySQL 8. A versão 8 também trouxe melhorias significativas em relação ao MySQL 5.7, incluindo suporte nativo a CTEs (Common Table Expressions) e funções de janela.
Por que SQLite em testes
Provisionar um servidor MySQL no GitHub Actions requer um services: no workflow, adiciona ~15-20 segundos de startup e cria dependência de infraestrutura. O SQLite em memória:
- Inicia instantaneamente — sem delay de conexão TCP
- É descartado automaticamente — sem limpeza de estado entre suítes
- Não requer configurações de rede — o CI pode rodar offline ou em ambientes restritos
- É suficiente para testes de domínio — queries CRUD, relações, transações básicas funcionam identicamente no SQLite
O trade-off é que features MySQL-specific (ex.: FULLTEXT search, JSON_CONTAINS()) não podem ser testadas com SQLite e precisam de testes de integração separados ou de um ambiente de staging com MySQL real.
Consequências
Positivas
- Queries type-safe end-to-end — o compilador TypeScript captura erros de acesso ao banco antes do deploy.
- Schema como documentação —
prisma/schema.prismaé a documentação mais atualizada do modelo de dados; qualquer desenvolvedor pode entender a estrutura do banco lendo esse arquivo. - Migrações versionadas — cada alteração de schema é rastreada em
prisma/migrations/com timestamp, facilitando rollback e auditoria. - Testes rápidos e sem infraestrutura — o CI não precisa de serviço externo de banco para rodar a suíte de testes unitários e de integração.
- Prisma Studio — interface gráfica para inspeção de dados em desenvolvimento e suporte.
Negativas / Ressalvas
- Shadow database necessária para migrações MySQL — o Prisma exige uma segunda database (shadow DB) para calcular diffs de migração em MySQL. Em desenvolvimento local com Docker, isso é configurado via
shadowDatabaseUrlnoschema.prisma. Em produção na Hostinger, foi necessário criar uma segunda database no painel. - Divergência SQLite/MySQL em testes — algumas queries específicas de MySQL não podem ser testadas via SQLite (conforme descrito acima).
- Geração de types após mudanças no schema — todo desenvolvedor precisa lembrar de rodar
npx prisma generateapós modificar o schema. Isso está documentado noCONTRIBUTING.mde é executado automaticamente via scriptpostinstallnopackage.json. - Cold start em serverless — o Prisma Client tem um overhead de inicialização que pode causar cold starts em ambientes serverless (Vercel, AWS Lambda). Este trade-off é irrelevante para a hospedagem atual (Node.js persistente na Hostinger), mas deve ser considerado se o deploy mudar.
Alternativas consideradas
Drizzle ORM (descartado)
Motivo: À época da decisão, o Drizzle tinha uma comunidade menor, documentação menos abrangente e menos exemplos de integração com Next.js App Router. Embora seja uma alternativa tecnicamente sólida (também type-safe, com DX similar), o risco de encontrar casos de borda sem suporte da comunidade era maior. Pode ser reavaliado em projetos futuros.
MongoDB (descartado)
Motivo: A Hostinger não oferece MongoDB em seus planos de hospedagem compartilhada. Além disso, o modelo de dados do e-commerce é relacional por natureza — forçar relações em documentos aumenta a complexidade das queries de agregação (ex.: relatório de pedidos por produto, cálculo de estoque por variação).
Sequelize (descartado)
Motivo: Sequelize não gera tipos TypeScript automaticamente a partir do schema — os tipos precisam ser mantidos manualmente ou gerados por ferramentas terceiras. Isso elimina o principal benefício de usar TypeScript na camada de dados. A API do Sequelize também é mais verbosa que a do Prisma para queries complexas com relações.
Raw SQL / mysql2 diretamente (descartado)
Motivo: Sem type safety, sem controle de migrações automático, sem GUI. A produtividade de desenvolvimento seria significativamente menor, especialmente para consultas com múltiplas relações.
Referências
- Prisma Documentation
- Prisma com MySQL — Shadow Database
- ADR-001 — App Router (como o Prisma se integra ao fetching de dados em RSC)
prisma/schema.prisma— schema atual do projetoprisma/migrations/— histórico de migrações