Os clientes frequentemente nos dizem que estão se preparando para migrar do MongoDB para o Couchbase. Em parte, eles vêm porque estão cansados dos problemas que tiveram ao aprender a consultar o MongoDB. O Couchbase com N1QL oferece uma alternativa melhor, especialmente para o dimensionamento de aplicativos modernos.
Infelizmente, com o MongoDB, as opções de migração podem ser limitadas. Uma delas, sobre a qual escrevi recentemente, envolve a mudança de de MongoDB com Mongoose para Couchbase com Ottoman. O ponto central desse tutorial foi o uso de duas ferramentas ODM diferentes no Node.js que compartilhavam as mesmas APIs, tornando a transição quase perfeita. No entanto, e se você não estiver usando um ODM no MongoDB e não tiver interesse em usar um?
Desta vez, vamos dar uma olhada na transferência de um aplicativo Node.js que usa o MongoDB e a linguagem de consulta do MongoDB para o Couchbase com o N1QL. Em resumo, o N1QL é uma tecnologia que permite que você execute consultas SQL em dados JSON complexos. Isso a torna não apenas fácil de usar, mas também muito limpa na camada de aplicativos. Mais informações sobre o N1QL podem ser encontradas na seção Portal do desenvolvedor do Couchbase.
Vamos usar o mesmo problema de exemplo usado no Artigo anteriormas não há problema se você ainda não o viu. Tudo aqui será iniciado com uma lousa limpa.
Os requisitos
Há alguns requisitos que devem ser atendidos para garantir que você tenha sucesso com este guia. Eles podem ser vistos a seguir:
Como veremos os equivalentes do MongoDB e do Couchbase, você deve ter os dois bancos de dados disponíveis. Se já for um desenvolvedor do MongoDB, muitas coisas podem ser familiares para você, mas a decisão é sua.
Entendendo o modelo de dados NoSQL
Antes de desenvolver a API RESTful com qualquer uma das tecnologias, é uma boa ideia entender primeiro o modelo de dados que será usado.
Embora o MongoDB e o Couchbase sejam bancos de dados de documentos, eles não são totalmente iguais. O MongoDB armazena dados como BSON, enquanto o Couchbase armazena dados como JSON. Do ponto de vista da modelagem, isso realmente não importa para nós.
Considere o modelo de dados de uma escola em que você tem alunos e cursos. Para cada curso oferecido na escola, você pode ter um documento parecido com o seguinte:
1 2 3 4 5 6 7 8 9 10 |
{ "id": "course-1", "tipo": "curso", "name": "Ciência da Computação 101", "Termo": "F2017", "estudantes": [ "student-1", "student-2" ] } |
Cada curso manterá uma lista de todos os alunos que foram registrados. Nesse caso, a lista consistirá em valores de id que fazem referência a outros documentos. Estamos estabelecendo nossas próprias relações de documentos.
Para cada aluno da escola, eles podem ter um documento NoSQL parecido com o seguinte:
1 2 3 4 5 6 7 8 9 10 |
{ "id": "student-1", "type" (tipo): "estudante", "firstname": "Nic", "Lastname": "Raboy", "cursos": [ "curso-1", "curso-25" ] } |
Observe que o documento acima é semelhante à forma como modelamos nossos cursos. Cada aluno manterá uma lista de todos os cursos em que está matriculado. Esses cursos são IDs que fazem referência ao documento apropriado do curso.
Há centenas de maneiras diferentes de modelar nossos documentos, e este é apenas um exemplo. Para obter mais informações sobre modelagem de dados, dê uma olhada em documentação.
Agora que temos um modelo para nossos documentos, podemos nos concentrar na criação de uma API com cada tecnologia.
Desenvolvimento de uma API com a linguagem de consulta do MongoDB
Embora você possa ter seu próprio código de aplicativo MongoDB, vamos criar um do zero para que a migração permaneça muito fácil de entender.
Vamos começar criando um novo projeto em nosso prompt de comando ou terminal:
1 2 |
npm inicial --y npm instalar expresso corpo-analisador mongodb --salvar |
Os comandos acima criarão um novo package.json em seu diretório de trabalho atual e instale as dependências necessárias da estrutura e do banco de dados.
No final das contas, queremos que nosso projeto MongoDB tenha a seguinte estrutura:
1 2 3 4 5 6 7 8 9 |
app.js rotas/ cursos.js alunos.js modelos/ student.js curso.js package.json node_modules/ |
Toda a interação com o banco de dados será feita a partir de cada um dos modelos e todo o roteamento de API e a interação com o cliente serão feitos a partir do rotas arquivos. A inicialização do aplicativo e a conexão com o banco de dados serão feitas a partir do arquivo app.js arquivo.
Criação de um modelo de banco de dados MongoDB no aplicativo
Então, vamos dar uma olhada em um dos nossos modelos de banco de dados. Abra a seção models/course.js e inclua o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
var Banco de dados = exigir("../../app").banco de dados; var ObjectId = exigir("mongodb").ObjectId; função Modelo de curso() { }; Modelo de curso.salvar = função(dados, retorno de chamada) { Banco de dados.coleção("cursos").insertOne(dados, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); } Modelo de curso.updateStudents = função(id, estudantes, retorno de chamada) { Banco de dados.coleção("cursos").updateOne({ "_id": novo ObjectId(id) }, { $definir: { "estudantes": estudantes } }, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); } ); } Modelo de curso.getById = função(documentId, retorno de chamada) { var cursor = Banco de dados.coleção("cursos").agregado([ { "$match": { "_id": novo ObjectId(documentId) } }, { "$unwind": { "caminho": "$students", "preserveNullAndEmptyArrays": verdadeiro } }, { "$lookup": { "de": "estudantes", "localField": "estudantes", "foreignField": "_id", "como": "studentObjects" } }, { "$unwind": { "caminho": "$studentObjects", "preserveNullAndEmptyArrays": verdadeiro} }, { "$group": { "_id": { "_id": "$_id", "name" (nome): "$name" }, "estudantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "name" (nome): "$_id.name", "estudantes": "$students" } }, { "$limit": 1 } ]); cursor.toArray(retorno de chamada); }; Modelo de curso.getAll = função(retorno de chamada) { var cursor = Banco de dados.coleção("cursos").agregado([ { "$unwind": { "caminho": "$students", "preserveNullAndEmptyArrays": verdadeiro} }, { "$lookup": { "de": "estudantes", "localField": "estudantes", "foreignField": "_id", "como": "studentObjects" } }, { "$unwind": { "caminho": "$studentObjects", "preserveNullAndEmptyArrays": verdadeiro} }, { "$group": { "_id": { "_id": "$_id", "name" (nome): "$name" }, "estudantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "name" (nome): "$_id.name", "estudantes": "$students" } } ]); cursor.toArray(retorno de chamada); }; módulo.exportações = Modelo de curso; |
Há muita coisa acontecendo no modelo de banco de dados acima. Precisamos dividi-lo para facilitar a compreensão. Quanto mais entendermos, mais fácil será o processo de migração para o Couchbase.
Quando desejamos salvar um documento, temos a opção salvar
método:
1 2 3 4 5 6 7 8 |
Modelo de curso.salvar = função(dados, retorno de chamada) { Banco de dados.coleção("cursos").insertOne(dados, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); } |
Usando o banco de dados aberto, podemos inserir um único documento na coleção desejada usando o comando insertOne
function. Nessa função, passamos dados
que pode ser um objeto JavaScript de qualquer complexidade que desejamos salvar. Depois de salvo, o resultado será retornado ao método pai que o chamou.
E se quisermos atualizar um documento que já existe? Em particular, e se quisermos adicionar um aluno a um curso já existente?
Para este exemplo específico, podemos atualizar todo o estudantes
que existe no documento:
1 2 3 4 5 6 7 8 9 10 11 12 |
Modelo de curso.updateStudents = função(id, estudantes, retorno de chamada) { Banco de dados.coleção("cursos").updateOne({ "_id": novo ObjectId(id) }, { $definir: { "estudantes": estudantes } }, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); } ); } |
O código acima usará o updateOne
para pesquisar um determinado documento por id e substituir o método estudantes
com uma nova matriz que fornecemos por meio do updateStudents
função.
Nada muito difícil até agora e nenhum estresse real adicionado ao desenvolvedor.
É aqui que as coisas mudam. Quando salvamos os dados, estamos salvando uma matriz de valores de id. Esses não são dados que queremos ver nas operações de consulta. Em vez disso, queremos preencher ou expandir esses valores de id em seus equivalentes de documento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Modelo de curso.getAll = função(retorno de chamada) { var cursor = Banco de dados.coleção("cursos").agregado([ { "$unwind": { "caminho": "$students", "preserveNullAndEmptyArrays": verdadeiro} }, { "$lookup": { "de": "estudantes", "localField": "estudantes", "foreignField": "_id", "como": "studentObjects" } }, { "$unwind": { "caminho": "$studentObjects", "preserveNullAndEmptyArrays": verdadeiro} }, { "$group": { "_id": { "_id": "$_id", "name" (nome): "$name" }, "estudantes": { "$push": "$studentObjects" } }}, { "$project": { "_id": "$_id._id", "name" (nome): "$_id.name", "estudantes": "$students" } } ]); cursor.toArray(retorno de chamada); }; |
Para que isso seja possível, a matriz deve primeiro ser achatada por meio de um $unwind
e unidos por meio de um $lookup
operação. E não para por aí, pois queremos que nossos resultados sejam formatados da mesma forma, apenas substituindo os ids por objetos. Por isso, temos que fazer mais achatamentos, agrupamentos e manipulações.
Uma explicação completa sobre a união de dados no MongoDB versus a união de dados no Couchbase pode ser vista em um artigo completo que escrevi anteriormente sobre o assunto.
Resumindo, quanto mais complicados forem seus dados, mais complicada será sua consulta de agregação. Para um modelo de dados flexível, isso se torna pouco flexível para um desenvolvedor de aplicativos.
Vamos dar uma olhada rápida em nosso outro modelo, aquele que gerenciará os dados dos alunos. Abra a seção models/student.js e inclua o seguinte código JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
var Banco de dados = exigir("../../app").banco de dados; var ObjectId = exigir("mongodb").ObjectId; função StudentModel() { }; StudentModel.salvar = função(dados, retorno de chamada) { Banco de dados.coleção("estudantes").insertOne(dados, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); } StudentModel.updateCourses = função(id, cursos, retorno de chamada) { Banco de dados.coleção("estudantes").updateOne({ "_id": novo ObjectId(id) }, { $definir: { "cursos": cursos } }, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); } ); } StudentModel.getById = função(documentId, retorno de chamada) { var cursor = Banco de dados.coleção("estudantes").agregado([ { "$match": { "_id": novo ObjectId(documentId) } }, { "$unwind": { "caminho": "$courses", "preserveNullAndEmptyArrays": verdadeiro } }, { "$lookup": { "de": "cursos", "localField": "cursos", "foreignField": "_id", "como": "courseObjects" } }, { "$unwind": { "caminho": "$courseObjects", "preserveNullAndEmptyArrays": verdadeiro } }, { "$group": { "_id": { "_id": "$_id", "name" (nome): "$name" }, "cursos": { "$push": "$courseObjects" } }}, { "$project": { "_id": "$_id._id", "name" (nome): "$_id.name", "cursos": "$courses" } }, { "$limit": 1 } ]); cursor.toArray(retorno de chamada); }; StudentModel.getAll = função(retorno de chamada) { var cursor = Banco de dados.coleção("estudantes").agregado([ { "$unwind": { "caminho": "$courses", "preserveNullAndEmptyArrays": verdadeiro } }, { "$lookup": { "de": "cursos", "localField": "cursos", "foreignField": "_id", "como": "courseObjects" } }, { "$unwind": { "caminho": "$courseObjects", "preserveNullAndEmptyArrays": verdadeiro } }, { "$group": { "_id": { "_id": "$_id", "firstname": "$firstname", "lastname" (sobrenome): "$lastname", "endereço": "$address" }, "cursos": { "$push": "$courseObjects" } }}, { "$project": { "_id": "$_id._id", "firstname": "$_id.firstname", "lastname" (sobrenome): "$_id.lastname", "endereço": "$_id.address", "cursos": "$courses" } } ]); cursor.toArray(retorno de chamada); }; módulo.exportações = StudentModel; |
Praticamente as mesmas regras se aplicam ao modelo acima em comparação com o modelo que vimos representando os dados do curso. Isso se deve ao fato de que os dois modelos de documento eram muito semelhantes no início. Na maior parte, são planos, com uma matriz de valores de id.
Isso nos leva aos pontos de extremidade da API que fazem uso desses métodos de banco de dados.
Criação de rotas de API RESTful para o aplicativo
Essa é a parte fácil e, na verdade, a mais consistente entre as duas tecnologias de banco de dados, pois não depende do banco de dados.
Vamos dar uma olhada no projeto rotas/cursos.js file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
var Modelo de curso = exigir("../models/course"); var roteador = função(aplicativo) { aplicativo.obter("/courses", função(solicitação, resposta) { Modelo de curso.getAll(função(erro, resultado) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado); }); }); aplicativo.obter("/course/:id", função(solicitação, resposta) { Modelo de curso.getById(solicitação.parâmetros.id, função(erro, resultado) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado[0]); }); }); aplicativo.postagem("/courses", função(solicitação, resposta) { se(!solicitação.corpo.nome) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `nome`"}); } Modelo de curso.salvar(solicitação.corpo, função(erro, curso) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(curso); }); }); } módulo.exportações = roteador; |
No código acima, temos três rotas de API. A partir de um cliente externo, poderemos listar todos os cursos, localizar um curso específico ou salvar um novo curso.
Dependendo de qual endpoint for atingido, a função apropriada do modelo de banco de dados será executada. Não é feito muito trabalho pesado nas rotas, que, com base em seu nome, destinam-se apenas ao roteamento.
O outro arquivo de roteamento será um pouco diferente. Abra o arquivo routes/students.js e inclua o seguinte código JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
var StudentModel = exigir("../models/student"); var Modelo de curso = exigir("../models/course"); var roteador = função(aplicativo) { aplicativo.obter("/estudantes", função(solicitação, resposta) { StudentModel.getAll(função(erro, resultado) { se(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado); }); }); aplicativo.obter("/student/:id", função(solicitação, resposta) { StudentModel.getById(solicitação.parâmetros.id, função(erro, resultado) { se(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado); }); }); aplicativo.postagem("/estudantes", função(solicitação, resposta) { se(!solicitação.corpo.primeiro nome) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `firstname`"}); } mais se(!solicitação.corpo.sobrenome) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `sobrenome`"}); } mais se(!solicitação.corpo.endereço) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `endereço`"}); } StudentModel.salvar(solicitação.corpo, função(erro, estudante) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(estudante); }); }); aplicativo.postagem("/estudante/curso", função(solicitação, resposta) { se(!solicitação.corpo.ID do aluno) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `student_id`" }); } mais se(!solicitação.corpo.course_id) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "É necessário um `course_id`" }); } Modelo de curso.getById(solicitação.corpo.course_id, função(erro, curso) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } StudentModel.getById(solicitação.corpo.ID do aluno, função(erro, estudante) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } se(curso != nulo && estudante != nulo) { se(!estudante[0].cursos) { estudante[0].cursos = []; } se(!curso[0].estudantes) { curso[0].estudantes = []; } var cursos = []; var estudantes = []; para(var i = 0; i < estudante[0].cursos.comprimento; i++) { cursos.empurrar(estudante[0].cursos[i]._id); } para(var i = 0; i < curso[0].estudantes.comprimento; i++) { estudantes.empurrar(curso[0].estudantes[i]._id); } cursos.empurrar(curso[0]._id); estudantes.empurrar(estudante[0]._id); StudentModel.updateCourses(estudante[0]._id, cursos, função(erro, resultado) {}); Modelo de curso.updateStudents(curso[0]._id, estudantes, função(erro, resultado) {}); resposta.enviar(estudante[0]); } mais { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "O `student_id` ou `course_id` era inválido"}); } }); }); }); } módulo.exportações = roteador; |
Como na outra rota, há muita coisa acontecendo, mas a maior parte é praticamente idêntica. As diferenças estão na /estudante/curso
que é responsável por adicionar cursos a um aluno e alunos a um curso.
Ao acessar esse endpoint, primeiro obtemos as informações do aluno e do curso com base no valor de id passado. Ambos precisam existir, caso contrário, lançaremos um erro. Se ambos existirem, enviaremos o ID do aluno para a variável estudantes
para o documento do curso e o ID do curso na matriz cursos
do documento do aluno. Em seguida, chamaremos nosso método de atualização e retornaremos um resultado para o usuário final.
Reunindo tudo e inicializando o aplicativo
Neste ponto, a API está pronta para ser usada. Só precisamos inicializar o aplicativo Node.js e nos conectar ao banco de dados. Essa é a parte mais fácil.
Abra o arquivo app.js e inclua o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var MongoCliente = exigir("mongodb").MongoCliente; var Expresso = exigir("expresso"); var Analisador de corpo = exigir("body-parser"); var aplicativo = Expresso(); aplicativo.uso(Analisador de corpo.json()); MongoCliente.conectar("mongodb://localhost:27017/example", função(erro, banco de dados) { se(erro) { retorno console.registro("Não foi possível estabelecer uma conexão com o MongoDB"); } módulo.exportações.banco de dados = banco de dados; var studentRoutes = exigir("./mongodb/routes/students")(aplicativo); var courseRoutes = exigir("./mongodb/routes/courses")(aplicativo); var servidor = aplicativo.ouvir(3000, função() { console.registro("Conectado na porta 3000..."); }); }); |
No código acima, estamos importando cada uma das dependências que baixamos e inicializando o Express Framework. Antes de começarmos a servir a API, precisamos estabelecer uma conexão com o MongoDB. Depois que a conexão é estabelecida, as rotas são conectadas e o aplicativo começa a servir.
Nesse ponto, a API RESTful pode ser acessada em http://localhost:3000 e testada com ferramentas populares como Carteiro ou Fiddler.
Desenvolvimento de uma API com Couchbase e N1QL
Portanto, temos um exemplo com o qual trabalhar quando se trata de MongoDB e Node.js. Esse exemplo usou a linguagem de consulta do MongoDB ao se comunicar com o banco de dados.
Agora, vamos pegar esse aplicativo e movê-lo para Couchbase. Estamos fazendo isso porque, além de o Couchbase ser mais escalonável e ter melhor desempenho, a linguagem de consulta é muito mais simples e fácil de manter em um aplicativo.
Assim como no aplicativo MongoDB, vamos começar do zero, embora muito do que veremos seja idêntico. No prompt de comando ou no terminal, execute o seguinte:
1 2 |
npm init --y npm install express body-parser couchbase --save |
Os comandos acima devem parecer familiares. Estamos criando um arquivo package.json e instalando nossas dependências, mas, em vez do MongoDB, estamos usando o Couchbase.
O projeto terá a mesma estrutura vista no projeto anterior. Ele deve ter a seguinte aparência:
1 2 3 4 5 6 7 8 9 |
app.js rotas/ cursos.js alunos.js modelos/ curso.js student.js package.json node_modules/ |
A mesma lógica vista anteriormente terminará em cada um desses arquivos. A diferença é a sintaxe do Couchbase.
Criação de um modelo de banco de dados do Couchbase no aplicativo
Começando com a mesma ordem, vamos criar nossas funções de modelo de banco de dados. Conforme mencionado anteriormente, usaremos o N1QL, que é um destaque extremo do Couchbase, pois permite que você escreva consultas SQL. Essas consultas são executadas no banco de dados e não no aplicativo Node.js.
Abra o arquivo models/course.js e inclua o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
var Uuid = exigir("uuid"); var Balde = exigir("../../app").balde; var N1qlQuery = exigir("couchbase").N1qlQuery; função Modelo de curso() { }; Modelo de curso.salvar = função(dados, retorno de chamada) { dados.id = Uuid.v4(); dados.tipo = "curso"; dados.estudantes = []; var declaração = "INSERIR EM `" + Balde._nome + "` (KEY, VALUE) VALUES ($1, $2) RETURNING `" + Balde._nome + "`.*"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [dados.id, dados], função(erro, resultado) { se(erro) { retorno de chamada(erro, nulo); retorno; } retorno de chamada(nulo, resultado); }); } Modelo de curso.updateStudents = função(id, cursos, retorno de chamada) { var declaração = "UPDATE `" + Balde._nome + "` USE KEYS $1 SET students = $2 RETURNING `" + Balde._nome + "`.*"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [id, cursos], função(erro, resultado) { se(erro) { retorno de chamada(erro, nulo); retorno; } retorno de chamada(nulo, resultado); }); } Modelo de curso.getById = função(documentId, retorno de chamada) { var declaração = "SELECT s.id, s.type, s.name, " + "(SELECT t.* FROM `" + Balde._nome + "` AS t USE KEYS s.students) AS alunos " + "FROM `" + Balde._nome + "` AS s " + "WHERE s.type = 'course' AND s.id = $1"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [documentId], função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); }; Modelo de curso.getAll = função(retorno de chamada) { var declaração = "SELECT s.id, s.type, s.name, " + "(SELECT t.* FROM `" + Balde._nome + "` AS t USE KEYS s.students) AS alunos " + "FROM `" + Balde._nome + "` AS s " + "WHERE s.type = 'course'"; var consulta = N1qlQuery.fromString(declaração).consistência(N1qlQuery.Consistência.REQUEST_PLUS); Balde.consulta(consulta, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); }; módulo.exportações = Modelo de curso; |
No código acima, temos o mesmo conjunto de funções relacionadas ao banco de dados que vimos no exemplo do MongoDB. Dentro de cada uma dessas funções há consultas N1QL. Não apenas consultas N1QL, mas consultas N1QL parametrizadas para ajudar a combater ataques de injeção de SQL.
Dê uma olhada no getAll
que antes era muito complicado na versão do MongoDB:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Modelo de curso.getAll = função(retorno de chamada) { var declaração = "SELECT s.id, s.type, s.name, " + "(SELECT t.* FROM `" + Balde._nome + "` AS t USE KEYS s.students) AS alunos " + "FROM `" + Balde._nome + "` AS s " + "WHERE s.type = 'course'"; var consulta = N1qlQuery.fromString(declaração).consistência(N1qlQuery.Consistência.REQUEST_PLUS); Balde.consulta(consulta, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); }; |
Desta vez, temos uma consulta simples que inclui uma subconsulta. Se nossas necessidades de dados se tornarem mais complexas, a consulta poderá ser alterada sem aumentar significativamente o tamanho ou a complexidade.
Então, vamos dar uma olhada em nosso outro modelo de banco de dados. Abra a seção models/student.js e inclua o seguinte código JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
var Uuid = exigir("uuid"); var Balde = exigir("../../app").balde; var N1qlQuery = exigir("couchbase").N1qlQuery; função StudentModel() { }; StudentModel.salvar = função(dados, retorno de chamada) { dados.id = Uuid.v4(); dados.tipo = "estudante"; dados.cursos = []; var declaração = "INSERIR EM `" + Balde._nome + "` (KEY, VALUE) VALUES ($1, $2) RETURNING `" + Balde._nome + "`.*"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [dados.id, dados], função(erro, resultado) { se(erro) { retorno de chamada(erro, nulo); retorno; } retorno de chamada(nulo, resultado); }); } StudentModel.updateCourses = função(id, cursos, retorno de chamada) { var declaração = "UPDATE `" + Balde._nome + "` USE KEYS $1 SET courses = $2 RETURNING `" + Balde._nome + "`.*"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [id, cursos], função(erro, resultado) { se(erro) { retorno de chamada(erro, nulo); retorno; } retorno de chamada(nulo, resultado); }); } StudentModel.getById = função(documentId, retorno de chamada) { var declaração = "SELECT t.id, t.type, t.firstname, t.lastname, t.address, " + "(SELECT s.* FROM `" + Balde._nome + "` AS s USE KEYS t.courses) AS courses" + "FROM `" + Balde._nome + "` AS t " + "WHERE t.type = 'student' AND t.id = $1"; var consulta = N1qlQuery.fromString(declaração); Balde.consulta(consulta, [documentId], função(erro, resultado) { se(erro) { console.registro(erro); retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); }; StudentModel.getAll = função(retorno de chamada) { var declaração = "SELECT t.id, t.type, t.firstname, t.lastname, t.address, " + "(SELECT s.* FROM `" + Balde._nome + "` AS s USE KEYS t.courses) AS courses" + "FROM `" + Balde._nome + "` AS t " + "WHERE t.type = 'student'"; var consulta = N1qlQuery.fromString(declaração).consistência(N1qlQuery.Consistência.REQUEST_PLUS); Balde.consulta(consulta, função(erro, resultado) { se(erro) { retorno retorno de chamada(erro, nulo); } retorno de chamada(nulo, resultado); }); }; módulo.exportações = StudentModel; |
Parece familiar? Os dois modelos de banco de dados são semelhantes porque os dois modelos de documentos são semelhantes. Novamente, se a complexidade mudar, a camada do aplicativo ainda será fácil de gerenciar com consultas N1QL.
Como as rotas de API não têm nenhuma dependência do banco de dados, o código entre o aplicativo MongoDB e o aplicativo Couchbase pode ser compartilhado. Para ser claro, estou me referindo aos arquivos encontrados no diretório rotas diretório.
Inicialização do aplicativo e conexão com o Couchbase
Com os pontos de extremidade instalados e os modelos de banco de dados se comunicando com o Couchbase por meio do N1QL, podemos concluir o aplicativo.
Abra o arquivo app.js e inclua o seguinte código JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Couchbase = exigir("couchbase"); var Expresso = exigir("expresso"); var Analisador de corpo = exigir("body-parser"); var aplicativo = Expresso(); aplicativo.uso(Analisador de corpo.json()); módulo.exportações.balde = (novo Couchbase.Aglomerado("couchbase://localhost")).openBucket("exemplo"); var studentRoutes = exigir("./couchbase/routes/students")(aplicativo); var courseRoutes = exigir("./couchbase/routes/courses")(aplicativo); var servidor = aplicativo.ouvir(3000, função() { console.registro("Conectado na porta 3000..."); }); |
No código acima, estamos importando as dependências que instalamos e inicializando o Express Framework. Em seguida, estamos estabelecendo uma conexão com o banco de dados, reunindo nossas rotas e iniciando o servidor Node.js.
Como estamos usando o N1QL, não se esqueça de criar pelo menos um índice no seu Couchbase Bucket. Ele pode ser tão simples quanto o seguinte:
1 |
CREATE PRIMARY INDEX ON `example`; |
Neste ponto, você deve conseguir acessar seu aplicativo da mesma forma que o MongoDB. Visite http://localhost:3000 em seu navegador da Web ou com uma ferramenta como Postman ou Fiddler.
Conclusão
Você acabou de ver como pegar um aplicativo Node.js que usa o MongoDB e a linguagem de consulta do MongoDB e convertê-lo para o Couchbase com o N1QL. Essa é uma alternativa ao método ODM de mangusto para otomano sobre a qual escrevi anteriormente.
Então, por que você quer mudar do MongoDB para o Couchbase? Bem, o Couchbase é muito mais rápido e fácil de escalonar, mas o N1QL também é incrivelmente simples quando se trabalha com dados complexos. Você poderá reduzir drasticamente seu código e mantê-lo mais sustentável. Confira a consulta Couchbase vs. MongoDB tutorial Escrevi sobre a união de dados entre os dois bancos de dados.
Para obter mais informações sobre como usar o Couchbase em um aplicativo Node.js:
-
- Confira o Portal do desenvolvedor do Couchbase.
- Experimente um dos IDEs para desenvolvedores do Couchbase - JetBrains, VSCodepara os quais temos plug-ins.
[...] sobre a conversão de seus aplicativos Node.js com MongoDB para o Couchbase. Isso incluiu um tutorial da linguagem de consulta do MongoDB para N1QL, bem como um tutorial do Mongoose para Ottoman. Esses foram ótimos tutoriais de migração de um [...]