Ao conversar com desenvolvedores de Node.js, é comum ouvir falar de NoSQL como o banco de dados preferido para desenvolvimento. JavaScript e JSON andam de mãos dadas porque, afinal, JSON significa JavaScript Object Notation. Esse é um formato mais comum em bancos de dados orientados a documentos que os desenvolvedores de Node.js tendem a usar.
Uma pilha muito popular de tecnologias de desenvolvimento é a pilha MongoDB, Express Framework, Angular e Node.js (MEAN), mas também existe a pilha Couchbase, Express Framework, Angular e Node.js (CEAN). Não me entenda mal, todas as tecnologias que listei são excelentes, mas quando seus aplicativos precisam ser dimensionados e manter o desempenho, talvez você tenha mais sorte com Couchbase devido à forma como ele funciona por design.
E se você já estiver usando o MongoDB em seu aplicativo Node.js?
É provável que você esteja usando Mongoose que é um modelo de documento de objeto (ODM) para interagir com o banco de dados. O Couchbase também tem um ODM, chamado Otomana. O melhor dessas duas tecnologias ODM é que elas compartilham praticamente o mesmo conjunto de APIs, o que torna qualquer transição incrivelmente fácil.
Veremos como tirar proveito da API REST do MongoDB para fazer a transição de um aplicativo Node.js usando o Mongoose e migrá-lo para o Couchbase, com o Ottoman.
Os requisitos
Este tutorial será um pouco diferente devido a todas as tecnologias envolvidas. Vamos criar tudo do zero para simplificar, portanto, os requisitos e as recomendações a seguir são:
- Servidor Couchbase 4.5+
- MongoDB 3.4+
- Node.js 6.0+
Vamos começar criando uma API REST do MongoDB no Node.js usando o Mongoose, daí a necessidade do Node.js e do MongoDB. Em seguida, pegaremos esse aplicativo e o migraremos para o Couchbase.
Para fins deste exemplo, não veremos como configurar o Node.js, o MongoDB ou o Couchbase Server.
Compreensão do nosso modelo de dados NoSQL
Tanto o MongoDB quanto o Couchbase são bancos de dados de documentos. Um armazena dados BSON e o outro armazena JSON, mas, do ponto de vista do desenvolvedor, eles são incrivelmente semelhantes. Dito isso, vamos criar alguns modelos baseados em alunos que frequentam cursos em uma escola. O primeiro modelo que criamos pode ser para cursos reais, em que um único curso pode se parecer 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" ] } |
No exemplo acima, observe que o curso tem um ID exclusivo e que o definimos como sendo um curso. O curso tem informações de nomenclatura, bem como uma lista de alunos que estão matriculados.
Agora, digamos que queremos definir nosso modelo para documentos de alunos:
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 modelo acima tem um formato semelhante ao dos cursos. O que estamos dizendo aqui é que ambos os documentos estão relacionados, mas ainda são semiestruturados. Estamos dizendo que cada curso mantém o controle de seus alunos e cada aluno mantém o controle de seus cursos. Isso é útil quando tentamos consultar os dados.
Há possibilidades ilimitadas quando se trata de modelar seus dados NoSQL. De fato, provavelmente há mais de cem maneiras de definir um modelo de "cursos e alunos", em comparação com o que eu havia decidido. Depende totalmente de você, e essa é a flexibilidade que o NoSQL oferece. Mais informações sobre modelagem de dados podem ser encontradas em aqui.
Com um modelo de dados em mente, podemos criar um conjunto simples de pontos de extremidade de API usando cada MongoDB com Mongoose e Couchbase com Ottoman.
Desenvolvimento de uma API com a estrutura Express e o MongoDB
Como estamos, em teoria, migrando do MongoDB para o Couchbase, faria sentido descobrir primeiro o que queremos em um aplicativo MongoDB.
Crie um novo diretório em algum lugar do seu computador para representar a primeira parte do nosso projeto. Nesse diretório, execute o seguinte:
1 2 |
npm inicial --y npm instalar expresso corpo-analisador mongodb mangusto --salvar |
Os comandos acima criarão um arquivo chamado package.json que manterá o controle de cada uma das quatro dependências do projeto. As expresso
é para a estrutura do Express e a dependência analisador de corpo
A dependência permite a existência de corpos de solicitação em solicitações POST, PUT e DELETE, que são comuns para a alteração de dados. Em seguida mongodb
e mangusto
são necessários para trabalhar com o banco de dados.
O projeto que criamos terá a seguinte estrutura:
1 2 3 4 5 6 7 8 |
app.js rotas/ cursos.js alunos.js modelos/ models.js package.json node_modules/ |
Vá em frente e crie esses diretórios e arquivos se eles ainda não existirem. Os app.js será o driver do aplicativo, enquanto o arquivo rotas conterá nossos pontos de extremidade da API e o modelos conterá as definições do banco de dados para o nosso aplicativo.
Definição dos esquemas do Mongoose
Então, vamos trabalhar de trás para frente, começando com o modelo Mongoose que se comunicará com o MongoDB. Abra a seção models/models.js e inclua o seguinte:
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 |
var Mongoose = exigir("mangusto"); var Esquema = Mongoose.Esquema; var ObjectId = Mongoose.Tipos de esquema.ObjectId; var Esquema do curso = novo Mongoose.Esquema({ nome: Cordas, prazo: Cordas, estudantes: [ { tipo: ObjectId, ref: Esquema do aluno } ] }); var Esquema do aluno = novo Mongoose.Esquema({ primeiro nome: Cordas, sobrenome: Cordas, cursos: [ { tipo: ObjectId, ref: Esquema do curso } ] }); módulo.exportações.Modelo de curso = Mongoose.modelo("Curso", Esquema do curso); módulo.exportações.StudentModel = Mongoose.modelo("Estudante", Esquema do aluno); |
No exemplo acima, estamos criando esquemas de documentos do MongoDB e, em seguida, criando modelos a partir deles. Observe como os esquemas são semelhantes aos modelos JSON que definimos anteriormente fora do aplicativo. Não estamos declarando um id
e tipo
porque o ODM cuida disso para nós. Em cada uma das matrizes, usamos uma referência a outro esquema. O que veremos ao salvar é um ID de documento, mas podemos aproveitar as tecnologias de consulta para carregar esse ID em dados reais.
Então, como usamos esses modelos?
Criação das rotas da API REST
Agora, queremos criar informações de roteamento ou, em outras palavras, pontos de extremidade de API. Por exemplo, vamos criar todos os pontos de extremidade CRUD para as informações do curso. Na seção rotas/cursos.js adicione o seguinte:
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 |
var Modelo de curso = exigir("../models/models").Modelo de curso; var roteador = função(aplicativo) { aplicativo.obter("/courses", função(solicitação, resposta) { Modelo de curso.encontrar({}).preencher("estudantes").então(função(resultado) { resposta.enviar(resultado); }, função(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); aplicativo.obter("/course/:id", função(solicitação, resposta) { Modelo de curso.findOne({"_id": solicitação.parâmetros.id}).preencher("estudantes").então(função(resultado) { resposta.enviar(resultado); }, função(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); aplicativo.postagem("/courses", função(solicitação, resposta) { var curso = novo Modelo de curso({ "name" (nome): solicitação.corpo.nome }); curso.salvar(função(erro, curso) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(curso); }); }); } módulo.exportações = roteador; |
No exemplo acima, temos três pontos de extremidade. Podemos visualizar todos os cursos disponíveis, visualizar os cursos por id e criar novos cursos. Cada ponto de extremidade é alimentado pelo Mongoose.
1 2 3 4 5 6 7 8 9 10 11 |
aplicativo.postagem("/courses", função(solicitação, resposta) { var curso = novo Modelo de curso({ "name" (nome): solicitação.corpo.nome }); curso.salvar(função(erro, curso) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(curso); }); }); |
Ao criar um documento, os dados POST da solicitação são adicionados a uma nova instanciação de modelo. Uma vez que salvar
é chamado, ele é salvo no MongoDB. Coisas semelhantes acontecem ao ler dados do banco de dados.
1 2 3 4 5 6 7 |
aplicativo.obter("/courses", função(solicitação, resposta) { Modelo de curso.encontrar({}).preencher("estudantes").então(função(resultado) { resposta.enviar(resultado); }, função(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); |
No caso dos itens acima, o encontrar
é chamada e os parâmetros são passados. Quando não há parâmetros, todos os documentos são retornados da função Curso
caso contrário, os dados são consultados pelas propriedades passadas. O preencher
permite que as referências de documentos sejam carregadas, portanto, em vez de retornar os valores de id, os documentos reais são retornados.
Agora vamos dar uma olhada na outra rota.
A segunda rota é responsável pela criação dos dados do aluno, mas há uma exceção aqui. Também estaremos gerenciando as relações de documentos aqui. Abra a seção routes/students.js e inclua o seguinte código-fonte:
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 Modelo de curso = exigir("../models/models").Modelo de curso; var StudentModel = exigir("../models/models").StudentModel; var roteador = função(aplicativo) { aplicativo.obter("/estudantes", função(solicitação, resposta) { StudentModel.encontrar({}).preencher("cursos").então(função(resultado) { resposta.enviar(resultado); }, função(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); aplicativo.obter("/student/:id", função(solicitação, resposta) { StudentModel.findOne({"_id": solicitação.parâmetros.id}).preencher("cursos").então(função(resultado) { resposta.enviar(resultado); }, função(erro) { resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); aplicativo.postagem("/estudantes", função(solicitação, resposta) { var estudante = novo StudentModel({ "firstname": solicitação.corpo.primeiro nome, "lastname" (sobrenome): solicitação.corpo.sobrenome }); estudante.salvar(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) { Modelo de curso.findOne({"_id": solicitação.corpo.course_id}).então(função(curso) { StudentModel.findOne({"_id": solicitação.corpo.ID do aluno}).então(função(estudante) { se(curso != nulo && estudante != nulo) { se(!estudante.cursos) { estudante.cursos = []; } se(!curso.estudantes) { curso.estudantes = []; } estudante.cursos.empurrar(curso._id); curso.estudantes.empurrar(estudante._id); estudante.salvar(); curso.salvar(); resposta.enviar(estudante); } mais { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": "O `student_id` ou `course_id` era inválido"}); } }, função(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }, função(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); }); }); } módulo.exportações = roteador; |
Os três primeiros endpoints da API devem parecer familiares. O novo endpoint aluno/curso
é responsável por adicionar alunos a um curso e cursos a um aluno.
A primeira coisa que acontece é que um curso é encontrado com base em um ID de solicitação. Em seguida, um aluno é encontrado com base em um ID de solicitação diferente. Se ambos os documentos forem encontrados, os ids serão adicionados a cada uma das matrizes apropriadas e os documentos serão salvos mais uma vez.
A etapa final aqui é criar nosso driver de aplicativo. Ele se conectará ao banco de dados e servirá o aplicativo para ser consumido pelos clientes.
Conectando-se ao MongoDB e atendendo ao aplicativo
Abra o arquivo app.js e adicione o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var Mongoose = exigir("mangusto"); var Expresso = exigir("expresso"); var Analisador de corpo = exigir("body-parser"); var aplicativo = Expresso(); aplicativo.uso(Analisador de corpo.json()); Mongoose.Promessa = Promessa; var studentRoutes = exigir("./routes/students" (rotas/alunos))(aplicativo); var courseRoutes = exigir("./routes/courses")(aplicativo); Mongoose.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"); } 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 instalamos anteriormente. Em seguida, estamos inicializando o Express e dizendo a ele para aceitar corpos JSON nas solicitações.
As rotas que foram criadas anteriormente precisam ser vinculadas ao Express, portanto, estamos importando-as e passando a instância do Express. Por fim, uma conexão com o MongoDB é feita com o Mongoose e o aplicativo começa a servir.
Não é particularmente difícil, certo?
Desenvolvimento de uma API com o Express Framework e o Couchbase
Vimos como criar uma API com o Node.js, o Mongoose e o MongoDB, então agora precisamos fazer a mesma coisa com o Node.js, o Ottoman e o Couchbase. Mais uma vez, isso serve para mostrar como é fácil fazer a transição do MongoDB para o Couchbase e obter todos os benefícios de um banco de dados NoSQL avançado e pronto para empresas.
Crie um novo diretório em algum lugar de seu computador e, dentro dele, execute o seguinte para criar um novo projeto:
1 2 |
npm inicial --y npm instalar expresso corpo-analisador couchbase otomano --salvar |
Os comandos acima são semelhantes aos que vimos anteriormente, com a exceção de que agora estamos usando o Couchbase e o Ottoman. O projeto que criamos terá exatamente a mesma estrutura e, para relembrar, ele se parece com o seguinte:
1 2 3 4 5 6 7 8 |
app.js rotas/ cursos.js alunos.js modelos/ models.js package.json node_modules/ |
Todos os modelos Ottoman existirão no modelos todos os pontos de extremidade da API e a lógica Ottoman existirão no diretório rotas e toda a lógica do driver existirá no diretório app.js arquivo.
Definição dos modelos otomanos
Vamos trabalhar na mesma direção que fizemos para o aplicativo MongoDB para mostrar a facilidade de transição. Isso significa começar com os modelos Ottoman que representarão nossos dados no Couchbase Server.
Abra o arquivo models/models.js e inclua o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var Otomana = exigir("ottoman"); var Modelo de curso = Otomana.modelo("Curso", { nome: { tipo: "string" }, prazo: { tipo: "string" }, estudantes: [ { ref: "Estudante" } ] }); var StudentModel = Otomana.modelo("Estudante", { primeiro nome: { tipo: "string" }, sobrenome: { tipo: "string" }, cursos: [ { ref: "Curso" } ] }); módulo.exportações.StudentModel = StudentModel; módulo.exportações.Modelo de curso = Modelo de curso; |
O que foi dito acima deve parecer familiar, mas você precisa perceber que esses são dois ODMs muito diferentes. Em vez de projetar esquemas do MongoDB por meio do Mongoose, podemos ir direto para o projeto de modelos JSON para o Couchbase com o Ottoman. Lembre-se de que não há esquemas nos Buckets do Couchbase.
Cada modelo Ottoman tem um conjunto de propriedades e uma matriz que faz referência a outros documentos. Embora a sintaxe seja um pouco diferente, ela realiza a mesma coisa.
Isso nos leva aos pontos de extremidade da API que usam esses modelos.
Criação dos pontos de extremidade da API REST
O primeiro conjunto de pontos de extremidade que queremos criar está relacionado ao gerenciamento de cursos. Abra a seção rotas/cursos.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 |
var Modelo de curso = exigir("../models/models").Modelo de curso; var roteador = função(aplicativo) { aplicativo.obter("/courses", função(solicitação, resposta) { Modelo de curso.encontrar({}, {carregar: ["estudantes"]}, 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, {carregar: ["estudantes"]}, função(erro, resultado) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado); }); }); aplicativo.postagem("/courses", função(solicitação, resposta) { var curso = novo Modelo de curso({ "name" (nome): solicitação.corpo.nome }); curso.salvar(função(erro, resultado) { 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 pontos de extremidade estruturados de forma quase idêntica ao que vimos com o MongoDB e o Mongoose. No entanto, há algumas pequenas diferenças. Por exemplo, em vez de usar promessas, estamos usando retornos de chamada.
Uma das diferenças mais visíveis é a forma como a consulta é feita. Não só temos acesso a um encontrar
como vimos no Mongoose, mas também temos acesso a uma função getById
função. Em ambos os cenários, podemos passar informações sobre como esperamos que uma consulta ocorra. Em vez de usar um preencher
podemos usar a função carregar
e fornecer os documentos de referência que desejamos carregar. Os conceitos entre o Mongoose e o Ottoman são praticamente os mesmos.
Isso nos leva ao nosso segundo conjunto de rotas. Abra o diretório 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 |
var StudentModel = exigir("../models/models").StudentModel; var Modelo de curso = exigir("../models/models").Modelo de curso; var roteador = função(aplicativo) { aplicativo.obter("/estudantes", função(solicitação, resposta) { StudentModel.encontrar({}, {carregar: ["cursos"]}, função(erro, resultado) { se(erro) { retorno 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, {carregar: ["cursos"]}, função(erro, resultado) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(resultado); }); }); aplicativo.postagem("/estudantes", função(solicitação, resposta) { var estudante = novo StudentModel({ "firstname": solicitação.corpo.primeiro nome, "lastname" (sobrenome): solicitação.corpo.sobrenome }); estudante.salvar(função(erro, resultado) { se(erro) { retorno resposta.status(401).enviar({ "sucesso": falso, "mensagem": erro}); } resposta.enviar(estudante); }); }); aplicativo.postagem("/estudante/curso", função(solicitação, resposta) { 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(!estudante.cursos) { estudante.cursos = []; } se(!curso.estudantes) { curso.estudantes = []; } estudante.cursos.empurrar(Modelo de curso.ref(curso._id)); curso.estudantes.empurrar(StudentModel.ref(estudante._id)); estudante.salvar(função(erro, resultado) {}); curso.salvar(função(erro, resultado) {}); resposta.enviar(estudante); }); }); }) } módulo.exportações = roteador; |
Já sabemos que os três primeiros endpoints terão o mesmo formato. Queremos prestar atenção ao último endpoint, que gerencia nossos relacionamentos.
Com esse ponto de extremidade, estamos obtendo um curso por seu valor de id e um aluno com base em seu valor de id. Desde que ambos retornem um documento, podemos adicionar uma referência de cada um a cada um de seus arrays e salvar novamente o documento. A mesma coisa e quase o mesmo código foram encontrados na versão do Mongoose.
Agora podemos examinar a lógica para começar a servir o aplicativo após a conexão com o banco de dados.
Conexão com o Couchbase e fornecimento do aplicativo
Abra o arquivo app.js e inclua o seguinte JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var Couchbase = exigir("couchbase"); var Otomana = exigir("ottoman"); var Expresso = exigir("expresso"); var Analisador de corpo = exigir("body-parser"); var aplicativo = Expresso(); aplicativo.uso(Analisador de corpo.json()); var balde = (novo Couchbase.Aglomerado("couchbase://localhost")).openBucket("exemplo"); Otomana.loja = novo Otomana.CbStoreAdapter(balde, Couchbase); var studentRoutes = exigir("./routes/students" (rotas/alunos))(aplicativo); var courseRoutes = exigir("./routes/courses")(aplicativo); var servidor = aplicativo.ouvir(3000, função() { console.registro("Conectado na porta 3000..."); }); |
A imagem acima lhe parece familiar? Deveria! Estamos apenas trocando as informações de conexão do Mongoose pelas informações de conexão do Couchbase. Após a conexão com o banco de dados, podemos começar a servir o aplicativo.
Conclusão
A migração do Mongoose e do MongoDB é mais fácil do que você imagina. De fato, vocêVocê acabou de ver como criar uma API REST com o Node.js, o Mongoose e o MongoDB e, em seguida, trazê-la para o Couchbase de uma maneira muito simples. Isso foi feito para provar que não é preciso ter medo do processo de migração, especialmente se você estiver usando o Node.js como sua tecnologia de back-end.
Com o Couchbase, você tem um banco de dados NoSQL distribuído e de alto desempenho que funciona em qualquer escala. A necessidade de usar o cache na frente do seu banco de dados é eliminada porque ele está integrado ao Couchbase. Para obter mais informações sobre o uso do Ottoman, você pode conferir um blog anterior que escrevi. Mais informações sobre o uso do Couchbase com o Node.js podem ser encontradas na seção Portal do desenvolvedor do Couchbase.
[...] há muito tempo, escrevi sobre a migração do MongoDB com o Mongoose para o Couchbase com o Ottoman. O ponto central desse tutorial foi o uso de duas ferramentas ODM diferentes no Node.js que compartilhavam o [...]
agradecimentos
nic raboy
por esse artigo tão bom. Só tenho uma pergunta a fazer: quando vou para a interface do usuário do couchbase na porta 8091 e vejo os documentos do meu bucket, há um campo ausente: "firstName", que é o primeiro campo do meu esquema. Ele está salvando todos os dados com êxito, mas o primeiro campo "firstName" não está presente. Há algo que possa estar relacionado ao couchbase ou ao ottoman.