Como o Prisma analisa um esquema de um banco de dados MongoDB
Avalie esse Artigo
Prisma ORM (object relational-mapper) disponibilizou recentemente para o MongoDB. Isso representa a primeira vez que o Prisma suporta um relational database fora do mundo SQL. O Prisma é conhecido por oferecer suporte a muitos relational databases, mas como ele acabou sendo capaz de oferecer suporte ao MongoDB, totalmente diferente?
. . . . Trabalho como gerente de engenharia da equipe de Schema da Prisma. Somos responsáveis pelas partes de gerenciamento de esquema do Prisma, que incluem principalmente nossas migrações e ferramentas de introspecção, bem como a linguagem e o arquivo do esquema do Prisma (e nossa Incrível extensão Prisma VS Code!).
A outra grande equipe que trabalha no Prisma object relacional-mapper (ORM) é a equipe do cliente que constrói o Prisma Client e o Mecanismo de Query. Eles permitem que os usuários interajam com o banco de dados para ler e manipular dados.
Nesta publicação do blog, resumi como nossa equipe fez com que o recurso de introspecção de esquema do Prisma funcionasse para o novo connector do MongoDB e os desafios interessantes que resolvemos ao longo do caminho.
O Prisma é um ORM de banco de dados Node.js criado em torno da linguagem de esquema Prisma e do arquivo de esquema Prisma que contém uma representação abstrata do banco de dados de um usuário. Quando você tem tabelas com colunas de determinados tipos de dados em seu banco de dados, elas são representadas como modelos com campos de um tipo em seu esquema Prisma.
O Prisma usa essas informações para gerar um TypeScript/JavaScript Prisma Clienttotalmente seguro de tipo que facilita a interação com os dados em seu banco de dados (o que significa que ele apresentará reclamação se você tentar escrever um
String
em um Datetime
campo e certifique-se de, por exemplo, incluir informações para todas as colunas não anuláveis sem um padrão e semelhantes).O Prisma Migrate usa suas alterações no esquema Prisma para gerar automaticamente o SQL necessário para migrar seu banco de dados para refletir esse novo esquema. Você não precisa pensar nas mudanças necessárias. Você acabou de escrever o que deseja alcançar e o Prisma, em seguida, gera de forma inteligente o SQL DDL (Data definition language) para isso.
Para usuários que desejam começar a usar o Prisma com seu banco de dados existente, o Prisma tem um recurso chamado Introspecção. Você chama o comando CLI
prisma db pull
para "pull in " o esquema de banco de dados existente, e o Prisma pode criar o esquema Prisma para você automaticamente, para que seu banco de dados existente possa ser usado com o Prisma em segundos.Isso funciona da mesma forma para PostgreSQL, MySQL, MariaDB, SQL Server, CockroachDB e até mesmo SQLite e depende do fato de relational database serem bastante semelhantes, terem tabelas e colunas, entenderem algum dialeto de SQL, terem chaves estrangeiras e conceitos como integridade referencial.
Uma das funcionalidades mais solicitadas foi o suporte para Prisma com MongoDB. O problema de solicitação de recurso no GitHub para suporte do MongoDB de janeiro de 2020 foi por muito tempo de longe o mais popular, tendo ganhado mais de um total de 800 reações.
O MongoDB é conhecido por seu esquema flexível e pelo document model, onde você pode armazenar documentos semelhantes a JSON. O MongoDB usa um paradigma diferente dos bancos de dados relacionais ao modelar dados — não há tabelas, colunas, esquemas ou chaves estrangeiras para representar as relações entre tabelas. Os dados geralmente são armazenados agrupados no mesmo documento com dados relacionados ou “denormalized,”, que é diferente do que você veria em um banco de dados relacional.
Então, como esses mundos tão diferentes poderiam ser reunidos?
Para nossa equipe, isso significa descobrir:
- Como representar uma estrutura MongoDB e seus documentos em um esquema Prisma.
- Como migrar as mencionadas estruturas de dados.
- Como permitir que as pessoas introspeccionem seu MongoDB database existente para que possam começar a usar o Prisma com facilidade.
Felizmente, resolver 1 e 2 foi relativamente simples:
- Onde os bancos de dados relacionais têm tabelas, colunas e chaves estrangeiras mapeadas para os modelos do Prisma, com seus campos e relações, o MongoDB tem collections, campos e referênciasequivalentes que podem ser mapeados da mesma maneira. O Prisma Client pode usar essas informações para fornecer o mesmo tipo de segurança e funcionalidade no lado do cliente.
Banco de dados relacional | Prisma | MongoDB |
Tabela → | Modelo | ← Collection |
Coluna → | Campo | ← Campo |
Chave estrangeira → | Relação | ← Referência |
- Sem nenhum esquema do lado do MongoDB database para migrar, criar e atualizar índices e restrições era tudo o que era necessário para desenvolver um esquema de MongoDB database. Como não há SQL para modificar a estrutura do banco de dados (que não é escrita ou definida em nenhum lugar), o Prisma também não precisou criar arquivos de migração com declarações de Linguagem de Definição de Dados (DDL) e poderia apenas fazer o escopo para permitir 'prisma db push ` para levar o banco de dados diretamente ao estado final desejado.
Um desafio maior acabou sendo o recurso Introspecção.
Com relational database com um esquema, há sempre uma maneira de consultar o esquema. No PostgreSQL, por exemplo, você pode consultar várias exibições em um esquema
information_schema
para descobrir todos os detalhes sobre a estrutura do banco de dados - para, por exemplo, gerar o SQL DDL necessário para recriar um banco de dados ou abstraí-lo em um esquema Prisma.Como o MongoDB possui um esquema flexível (a menos que os esquemas sejam aplicados por meio do recurso de validação de esquema), não existe tal armazenamento de informações que possa ser facilmente consultado. Isso, é claro, coloca um problema sobre como implementar a introspecção para o MongoDB no Prisma.
Como qualquer boa equipe de engenharia faria, começamos por... Pesquisando um pouco no Google. Não há necessidade de reinventar a roda, se alguém já resolveu o problema no passado. Pesquisas por "MongoDB introspection, "MongoDB schema reverse engineering, " e (como aprenderam o termo nativo) "MongoDB infer schema " tiveram a sorte de trazer alguns resultados interessantes e válidos.
A GUI do banco de dados do MongoDB, Compass, tem uma aba “Schema” em uma collection que pode analisar uma collection como “provide an overview of the data type and shape of the fields in a particular collection.”
Ele funciona amostrando documentos 1000 de uma coleção que contém pelo menos 1000 documentos, analisando os campos individuais e, em seguida, apresentando-os ao usuário.
Outro recurso que achamos foi o repositório
mongodb-infer
de Lukas Hrábanvski de 2014. Mais tarde naquele ano, parece ter sido mesclado/substituído por mongodb-schema
, que está atualizado até hoje.É uma versão CLI e de biblioteca da mesma ideia - e, de fato, ao verificar o código-fonte do MongoDB Compass, você vê uma dependência para
mongodb-schema
que é usada nos bastidores.Normalmente, encontrar uma biblioteca de código aberto com um Apache 2. A licença 0 significa que você acabou de economizar muito tempo da equipe de engenharia e a equipe pode se tornar uma usuária da biblioteca. Mas, neste caso, queríamos implementar nossa introspecção no mesmo mecanismo de introspecção que também usamos para os bancos de dados SQL, e ele foi escrito no Rust. Como ainda não há
mongodb-schema
para Rust, tivemos que implementar isso nós mesmos. Por saber como o mongodb-schema
funciona, isso acabou sendo simples:Começamos simplesmente obtendo todas as coleções em um banco de dados. O driver MongoDB Rust fornece um útil
db.list_collection_names()
que podemos chamar para obter todas as coleções — e cada coleção é transformada em um modelo para o esquema Prisma. . . . . .Para preencher os campos com seu tipo, obtemos uma amostra de até 1000 registros aleatórios de cada coleção e percorremos eles. Para cada entrada, observamos quais campos existem e que tipo de dados eles têm. Mapeamos o tipo BSON para nossos tipos escalares Prisma (e tipo nativo, se necessário). O ideal é que todas as entradas tenham os mesmos campos com o mesmo tipo de dados, que é mapeável de forma fácil e limpa — e pronto!
Muitas vezes, nem todas as entradas em uma coleção são uniformes. Os campos ausentes, por exemplo, são esperados e equivalentes a valores
NULL
em um banco de dados relacional.Mas tipos diferentes (por exemplo,
String
e Datetime
) apresentam um problema: qual tipo devemos colocar no esquema Prisma?🎓 Aprendizagem 1: Apenas escolher o tipo de dados mais comum não é uma boa ideia.
Em uma iteração inicial da introspecção do MongoDB, padronizamos para o tipo mais comum e deixamos um comentário com a porcentagem de ocorrências no esquema Prisma. A ideia é que isso funcione na maior parte do tempo e ofereça ao desenvolvedor a melhor experiência de desenvolvimento – quanto melhores os tipos em seu esquema Prisma, mais o Prisma poderá ajudá-lo.
Mas rapidamente descobrimos, ao testar isso, que havia um pequeno (mas lógico) problema: sempre que o Prisma Client encontra um tipo que nãocorresponde ao que foi informado por meio do esquema Prisma, ele precisa lançar um erro e abortar a consulta. Caso contrário, ele retornaria dados que não aderem aos seus próprios tipos gerados para esses dados.
Embora estejamos cientes de que isso aconteceria, não era intuitivo para nós com que frequência isso causaria falha no Prisma Client. Avaliamos isso rapidamente ao usar esse esquema Prisma com tipos conflitantes no banco de dados subjacente com o Prisma Studio, a GUI do banco de dados integrada que vem com o Prisma CLI (basta executar
npx prisma studio
). Por padrão, ele carrega 100 entradas de um modelo que você visualiza — e quando havia ~5% de entradas com um tipo diferente em um banco de dados de 1000 entradas, era muito comum atingir isso já na primeira página . O Prisma Studio (e também um aplicativo usando esses esquemas) era essencialmente inutilizável para esses conjuntos de dados dessa maneira.Felizmente, tudo no MongoDB é um
Document
, que mapeia para um campo do tipoJson
no Prisma. Então, quando um campo tem tipos de dados diferentes, usamos Json
, geramos um aviso no Prisma CLI e colocamos um comentário acima do campo no esquema Prisma que renderizamos, que inclui informações sobre os tipos de dados que encontramos e como comuns eles eram.Usar
Json
em vez de um tipo de dados específico, é claro, reduz substancialmente o benefício que você obtém do Prisma e efetivamente permite que você escreva qualquer JSON no campo (tornando os dados ainda menos uniformes e mais difíceis de lidar com o tempo!) Mas, pelo menos, você pode ler todos os dados existentes no Prisma Studio ou em seu aplicativo e interagir com eles.A maneira preferida de corrigir tipos de dados conflitantes é ler e atualizá-los manualmente com um script e, em seguida, executar
prisma db pull
novamente. O novo esquema do Prisma deverá então mostrar apenas o tipo ainda presente na coleção.🎓 Aprendizagem 2: produz tipos Prisma no esquema Prisma, não tipos MongoDB.
Originalmente, produzimos as informações brutas de tipo que obtemos do driver MongoDB Rust, os BSON types, em nossos MongoDB CLI e comentários do esquema Prisma para ajudar nossos usuários a iterar seus dados e corrigir o tipo. Verificou-se que, embora isso fosse técnico correto e informasse ao usuário em que tipo os dados estavam, usar os nomes de tipo BSON era confuso em um contexto do Prisma. Mudamos para a saída dos nomes dos tipos de Prisma e isso agora parece muito mais natural para os usuários.
Embora o Prisma recomende a todos que limpem seus dados e minimizem a quantidade de tipos conflitantes que retornam a
Json
, essa também é, obviamente, uma escolha válida.Ao adicionar informações de relação ao seu esquema Prisma introspectado, você pode dizer ao Prisma que manipule uma coluna específica, como uma chave estrangeira, e crie uma relação com os dados nela contidos.
user User @relation(fields: [userId], references: [id])
cria uma relação com o modeloUser
por meio do campouserId
local. Portanto, se você estiver usando referências do MongoDB para modelar relações, adicione @relation
a elas para que o Prisma possa acessá-las no Prisma Client, emular ações referenciais e ajudar com a integridade referencial para manter os dados limpos.No momento, o Prisma não oferece uma maneira de detectar ou confirmar as possíveis relações entre diferentes collections. Queremos aprender primeiro como os usuários do MongoDB realmente usam as relações e, em seguida, ajudá-los da maneira ideal.
Implementar uma boa história de introspecção para o MongoDB foi um desafio legal para nossa equipe. No início, parecia que dois mundos muito diferentes estavam se chocando, mas, no final, foi fácil encontrar as vantagens e desvantagens corretas para obter o melhor resultado para nossos usuários. Estamos confiantes de que encontramos uma ótima combinação que combina o melhor do MongoDB com o que as pessoas esperam do Prisma.