Como usar Prisma com múltiplos bancos de dados no mesmo projeto
1. Cenários e motivações para múltiplos bancos com Prisma
1.1. Separação de domínios: banco de leitura vs. banco de escrita (CQRS)
Em aplicações de alto desempenho, separar operações de leitura e escrita em bancos distintos é uma estratégia comum. O Prisma permite configurar um banco exclusivo para consultas (read replica) e outro para comandos (write master). Isso reduz contenção de recursos e melhora a escalabilidade.
1.2. Microserviços monolíticos: bancos isolados por módulo de negócio
Mesmo em um monólito, você pode querer isolar dados de módulos diferentes (ex: financeiro, estoque, usuários) em bancos separados. Cada módulo tem seu próprio schema Prisma, garantindo independência e facilitando futuras extrações para microserviços.
1.3. Migração incremental: convivência de banco legado e novo banco
Ao modernizar um sistema, é comum manter o banco legado funcionando enquanto migra dados para um novo banco. Com múltiplos schemas Prisma, ambos coexistem no mesmo projeto, permitindo migração gradual sem interromper operações.
2. Estrutura de projeto e configuração de múltiplos schemas
2.1. Organização de diretórios
A melhor prática é criar pastas separadas para cada banco:
meu-projeto/
├── prisma/
│ ├── db1/
│ │ └── schema.prisma
│ └── db2/
│ └── schema.prisma
├── src/
│ ├── db1-client.ts
│ ├── db2-client.ts
│ └── index.ts
├── package.json
└── .env
2.2. Arquivo schema.prisma dedicado para cada banco
prisma/db1/schema.prisma:
generator client {
provider = "prisma-client-js"
output = "../../node_modules/.prisma/client/db1"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL_DB1")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
authorId Int
author User @relation(fields: [authorId], references: [id])
}
prisma/db2/schema.prisma:
generator client {
provider = "prisma-client-js"
output = "../../node_modules/.prisma/client/db2"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL_DB2")
}
model Product {
id Int @id @default(autoincrement())
name String
price Float
stock Int
}
model Order {
id Int @id @default(autoincrement())
productId Int
quantity Int
total Float
product Product @relation(fields: [productId], references: [id])
}
2.3. Configuração de variáveis de ambiente
No arquivo .env:
DATABASE_URL_DB1="postgresql://user:pass@localhost:5432/db1"
DATABASE_URL_DB2="postgresql://user:pass@localhost:5432/db2"
3. Geração de clientes Prisma independentes
3.1. Comando com --schema
Para gerar o cliente para cada banco:
npx prisma generate --schema=prisma/db1/schema.prisma
npx prisma generate --schema=prisma/db2/schema.prisma
3.2. Scripts no package.json
Adicione scripts para facilitar:
"scripts": {
"generate:db1": "prisma generate --schema=prisma/db1/schema.prisma",
"generate:db2": "prisma generate --schema=prisma/db2/schema.prisma",
"generate:all": "npm run generate:db1 && npm run generate:db2"
}
3.3. Importação e instanciação de clientes separados
src/db1-client.ts:
import { PrismaClient } from '../node_modules/.prisma/client/db1/index.js'
const prismaDb1 = new PrismaClient()
export default prismaDb1
src/db2-client.ts:
import { PrismaClient } from '../node_modules/.prisma/client/db2/index.js'
const prismaDb2 = new PrismaClient()
export default prismaDb2
src/index.ts:
import prismaDb1 from './db1-client'
import prismaDb2 from './db2-client'
async function main() {
// Usando banco 1
const user = await prismaDb1.user.create({
data: { name: 'João', email: 'joao@email.com' }
})
// Usando banco 2
const product = await prismaDb2.product.create({
data: { name: 'Notebook', price: 4500.00, stock: 10 }
})
console.log({ user, product })
}
main()
4. Execução de migrações em bancos distintos
4.1. Comando migrate dev com --schema
npx prisma migrate dev --schema=prisma/db1/schema.prisma --name init
npx prisma migrate dev --schema=prisma/db2/schema.prisma --name init
4.2. Gerenciamento de ordem de migrações
Se houver dependência entre bancos (ex: db2 referencia dados do db1), execute migrações na ordem correta:
"scripts": {
"migrate:all": "npm run migrate:db1 && npm run migrate:db2",
"migrate:db1": "prisma migrate dev --schema=prisma/db1/schema.prisma",
"migrate:db2": "prisma migrate dev --schema=prisma/db2/schema.prisma"
}
4.3. Estratégias de rollback e versionamento
Para deploy, use prisma migrate deploy com o schema correto:
"scripts": {
"deploy:db1": "prisma migrate deploy --schema=prisma/db1/schema.prisma",
"deploy:db2": "prisma migrate deploy --schema=prisma/db2/schema.prisma"
}
Cada banco mantém sua própria tabela _prisma_migrations, permitindo rollback independente.
5. Operações transacionais entre bancos diferentes
5.1. Limitação do Prisma
O Prisma não suporta transações distribuídas. Cada PrismaClient gerencia transações apenas no seu banco:
// Isso funciona apenas para um banco
await prismaDb1.$transaction([
prismaDb1.user.create({ data: { name: 'A', email: 'a@a.com' } }),
prismaDb1.user.create({ data: { name: 'B', email: 'b@b.com' } })
])
5.2. Padrão Saga para consistência eventual
Implemente um saga manual para operações entre bancos:
async function createUserAndOrder(userData: any, orderData: any) {
let user
try {
// Passo 1: criar usuário no db1
user = await prismaDb1.user.create({ data: userData })
// Passo 2: criar pedido no db2
await prismaDb2.order.create({
data: { ...orderData, userId: user.id }
})
} catch (error) {
// Compensação: deletar usuário se pedido falhar
if (user) {
await prismaDb1.user.delete({ where: { id: user.id } })
}
throw error
}
}
5.3. Implementação manual com retry
Use bibliotecas como p-retry para tentativas:
import pRetry from 'p-retry'
async function createWithRetry(data: any) {
return pRetry(() => prismaDb1.user.create({ data }), {
retries: 3,
onFailedAttempt: error => {
console.log(`Tentativa ${error.attemptNumber} falhou`)
}
})
}
6. Consultas e relacionamentos entre bancos (cross-database)
6.1. Ausência de foreign keys
Como não há foreign keys entre bancos, modele relacionamentos via IDs manuais:
// No db2, Order armazena userId manualmente
model Order {
id Int @id @default(autoincrement())
userId Int // referência manual ao User do db1
productId Int
quantity Int
}
6.2. Joins em nível de aplicação
Faça duas queries e combine os resultados:
async function getUserWithOrders(userId: number) {
const user = await prismaDb1.user.findUnique({ where: { id: userId } })
const orders = await prismaDb2.order.findMany({ where: { userId } })
return { ...user, orders }
}
6.3. Cache de dados de referência
Para evitar múltiplas chamadas, use cache em memória:
const userCache = new Map<number, any>()
async function getCachedUser(userId: number) {
if (!userCache.has(userId)) {
const user = await prismaDb1.user.findUnique({ where: { id: userId } })
userCache.set(userId, user)
}
return userCache.get(userId)
}
7. Boas práticas, testes e deploy com múltiplos bancos
7.1. Singleton por banco
Evite criar múltiplas instâncias do mesmo cliente:
// db1-client.ts
let prismaDb1: PrismaClient
export function getDb1Client(): PrismaClient {
if (!prismaDb1) {
prismaDb1 = new PrismaClient()
}
return prismaDb1
}
7.2. Testes unitários com bancos isolados
Use bancos de teste separados:
// .env.test
DATABASE_URL_DB1="postgresql://user:pass@localhost:5432/db1_test"
DATABASE_URL_DB2="postgresql://user:pass@localhost:5432/db2_test"
Crie fixtures independentes para cada banco:
// test/fixtures/db1-fixtures.ts
export async function createTestUser() {
return prismaDb1.user.create({
data: { name: 'Test', email: 'test@test.com' }
})
}
7.3. Pipeline CI/CD
No pipeline, execute migrações em paralelo:
jobs:
migrate:
strategy:
matrix:
db: [db1, db2]
steps:
- run: npm run migrate:${{ matrix.db }}
Valide a integridade com scripts:
"scripts": {
"validate": "node scripts/check-consistency.js"
}
Referências
- Documentação oficial do Prisma: Múltiplos schemas — Guia oficial sobre configuração de múltiplos schemas Prisma no mesmo projeto.
- Prisma: Gerenciamento de múltiplos bancos de dados — Conceitos e práticas recomendadas para múltiplos bancos.
- Tutorial: Prisma com PostgreSQL e SQLite simultaneamente — Exemplo prático de uso de dois bancos diferentes no mesmo projeto.
- Prisma: Migrações com múltiplos schemas — Como gerenciar migrações quando há múltiplos arquivos schema.prisma.
- Artigo: Padrão Saga com Prisma e Node.js — Implementação de consistência eventual entre bancos usando o padrão Saga.
- Prisma: Transações e operações entre bancos — Limitações e alternativas para transações distribuídas.
- Boas práticas: Singleton PrismaClient — Como gerenciar conexões com singleton para evitar vazamentos.