MongoDB e SQL Server via REST API

0

Fala pessoal, tudo bem? Hoje vamos ver como fazer para pegar dados do MongoDB e levá-los para o SQL Server através de uma simples REST API em NodeJS.

Desde a versão 2016 o SQL Server tem suporte a JSON (JSON é vida!), isso ampliou suas formas de comunicação com aplicações e outros bancos de dados, como por exemplo o MongoDB. Mesmo sendo possível a comunicação com o MongoDB através de ODBC, uma API REST deixa essa comunicação mais fluida, porque uma vez publicada, o SQL Server se conecta a ela e os dados chegam de forma padronizada.

O intuito desse artigo é demonstrar de forma simples a construção de uma API que se conecta ao MongoDB e fazer com que o SQL Server faça requests a ela para obter os dados. Não vou abordar detalhes sobre a construção da API, o foco é a comunicação entre os dois bancos.

Este artigo é baseado num post da equipe do Studio 3T e pode ser encontrado nesse link.

Limitações e pré-requisitos

Durante os testes para fazer esse post, testei alguns cenários e encontrei algumas limitações:

  • Por requerer parâmetros de configurações, digamos mais administrativas, não é possível utilizar o Azure SQL Databases;
  • Bem como o SQL Server for Linux, por conta de algumas DLLs, que obviamente estão disponíveis somente para Windows;

Utilizei a versão SQL Server 2017 Developer com Windows Server 2016 na Azure.

Você também precisará do NodeJS e de alguns módulos que podem ser instalados através do NPM.

Do lado do MongoDB, criei uma instância M0 no MongoDB Atlas que é free também na Azure. Se você não conhece o MongoDB Atlas, recomendo a leitura deste artigo que escrevi sobre ele!

O Dataset

Como exemplo, baixei do Kaggle um dataset pequeno sobre carros (download aqui), utilizei apenas alguns campos e nosso documento no MongoDB ficou assim:

{
  _id: ObjectId(5c6ef4d6aaeaf27b7cdb76d4),
  brand: "alfa-romero",
  fuelType: "gas",
  numOfDoors: "two",
  bodyStyle: "convertible",
  engineLocation: "front",
  numberOfCylinders: "four",
  horsepower: 111
}

A API

Novamente, o foco não é uma API perfeita, logo, o código abaixo pode (e deve) ser muito melhorado, mas para efeitos de estudo ela nos atende bem:

const express       = require("express"),
    app             = express(),
    bodyParser      = require("body-parser"),
    cors            = require("cors"),
    MongoClient     = require('mongodb').MongoClient;


const port = 8000;
var mongodbURI = "mongodb+srv://user:pass@seu_servidor_mongodb/test?retryWrites=true"


app.use(cors())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))



MongoClient.connect(mongodbURI, {useNewUrlParser: true}, function(err, client){
    if(err) throw err;


    app.get('/', function(req, res){
        res.json({
            "message": "Welcome to Cars Sample API",
            "Find Brand Name": "curl -X GET http://server.com/brand/",
        })
    });


    app.get('/brand/:name', function(req, res){
        const collection = client.db('newCars').collection('cars');


        collection
            .find({
                brand: {
                    $regex: '\\N*' + req.params.name + '\\N*',
                    $options: "si"
                }
            })
            .project({
                _id: 0
            })
            .sort({
                brand: 1
            })
            .toArray(function(err, result){
                if(err) throw err;
                res.setHeader("Content-Type", "application/json");
                res.json({
                    result
                });
            })
    });
})


app.listen(port, () => {
    console.log(`listening on port ${port}`)
})

Note que temos dois pontos de entrada em nossa API:

  • O / apenas nos exibe uma mensagem de boas vindas
  • O /brand/:name recebe o nome da marca de automóveis que queremos listar, esse é o parâmetro de entrada para nossa consulta no MongoDB. Note também que utilizei uma busca utilizando o $regex para possibilitar uma busca mais abrangente, sem nos preocuparmos de que forma o nome da marca está sendo inputado. O retorno é um array de documentos:

Fazendo a mágica no SQL Server

Agora, que temos nossa API retornando os dados corretamente, vamos para o SQL Server e fazer os requests a partir dele. Vamos utilizar ‘Ole Automation Procedures’ que por default e para dificultar um ataque por SQL Injection está desabilitado. Para habilitar utilizei os seguintes comandos:

sp_configure 'show advanced options', 1;  
GO  
RECONFIGURE;  
GO  
sp_configure 'Ole Automation Procedures', 1;  
GO  
RECONFIGURE;  
GO

Agora, vamos criar uma procedure para executar serviços REST no SQL Server. Esse código é todo do artigo original e não precisei fazer nenhuma modificação:

GO
  IF Object_Id('dbo.GetWebService','P') IS NOT NULL 
    DROP procedure dbo.GetWebService
  GO
  CREATE PROCEDURE dbo.GetWebService
    @TheURL VARCHAR(255),-- the url of the web service
    @TheResponse NVARCHAR(4000) OUTPUT --the resulting JSON
  AS
    BEGIN
      DECLARE @obj INT, @hr INT, @status INT, @message VARCHAR(255);
      /**
  Summary: >
    This is intended for using web services that 
    utilize JavaScript Object Notation (JSON). You pass it the link to
    a webservice and it returns the JSON string
  Note: >
    OLE Automation objects can be used within a Transact-SQL batch, but 
    SQL Server blocks access to OLE Automation stored procedures because
    this component is turned off as part of the security configuration.
   
  Author: PhilFactor
  Date: 26/10/2017
  Database: PhilFactor
  Examples:
     - >
     DECLARE @response NVARCHAR(MAX) 
     EXECUTE dbo.GetWebService 'http://headers.jsontest.com/', @response OUTPUT
     SELECT  @response 
  Returns: >
    nothing
  **/
      EXEC @hr = sp_OACreate 'MSXML2.ServerXMLHttp', @obj OUT;
      SET @message = 'sp_OAMethod Open failed';
      IF @hr = 0 EXEC @hr = sp_OAMethod @obj, 'open', NULL, 'GET', @TheURL, false;
      SET @message = 'sp_OAMethod setRequestHeader failed';
      IF @hr = 0
        EXEC @hr = sp_OAMethod @obj, 'setRequestHeader', NULL, 'Content-Type',
          'application/x-www-form-urlencoded';
      SET @message = 'sp_OAMethod Send failed';
      IF @hr = 0 EXEC @hr = sp_OAMethod @obj, send, NULL, '';
      SET @message = 'sp_OAMethod read status failed';
      IF @hr = 0 EXEC @hr = sp_OAGetProperty @obj, 'status', @status OUT;
      IF @status <> 200 BEGIN
                          SELECT @message = 'sp_OAMethod http status ' + Str(@status), @hr = -1;
        END;
      SET @message = 'sp_OAMethod read response failed';
      IF @hr = 0
        BEGIN
          EXEC @hr = sp_OAGetProperty @obj, 'responseText', @Theresponse OUT;
          END;
      EXEC sp_OADestroy @obj;
      IF @hr <> 0 RAISERROR(@message, 16, 1);
      END;

Criamos então uma procedure chamada GetWebService que recebe como parâmetro de entrada uma URL e como resultado o JSON retornado.

Executamos da seguinte maneira, somente alterando a URL para o seu servidor:

E temos como resultado os dados tabulados, prontos para serem persistidos no SQL Server:

Legal, né? O exemplo é simples, mas pode ser facilmente modificado e gerar uma variedade de possibilidades para a comunicação entre o MongoDB e o SQL Server. Um exemplo é que em determinados cenários podemos eliminar ETLs para inserir dados no SQL Server a partir do MongoDB. Por outro lado vemos também uma maior abertura por parte do SQL Server utilizando JSON e isso é muito bacana!

Bem, espero que tenham gostado. Deixe seu like e compartilhe à vontade!

Ah! Se você quiser saber mais sobre MongoDB, dê uma olhada nas próximas turmas de treinamento, serão online e ao vivo, presenciais em São Paulo e Brasília!

Até a próxima!

Compartilhe.

Sobre o autor

Formado em Engenharia da Computação, entusiasta de tecnologias open-source, bigdata e NoSQL. MongoDB Ambassador / Evangelist, Top 50 Certificado em Neo4j, utiliza MongoDB e SQL Server criando aplicações em NodeJS há 3 anos. CTO / Co-owner da Cluster Consultoria, uma empresa especializada em bancos de dados NoSQL