Pular para o conteúdo principal

ADR-002: Prisma + MySQL vs. Alternativas

CampoValor
Status✅ Accepted
Data2024-Q1
DecisoresTime de desenvolvimento
ImpactoAlto — 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:

  1. 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.
  2. Natureza dos dados — um e-commerce possui dados inerentemente relacionais: Produto tem muitas Variações, Pedido tem muitos Itens, cada Item referencia um Produto e uma Variação. Um modelo relacional é a representação mais natural.
  3. 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.
  4. 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çãoprisma/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 shadowDatabaseUrl no schema.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 generate após modificar o schema. Isso está documentado no CONTRIBUTING.md e é executado automaticamente via script postinstall no package.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