MongoDB em AdvPL – Prova de Conceito

Introdução

Aproveitando o embalo do JSON em AdvPL — JSON – O que é e como usar em AdvPL e JSON – O que é e como usar em AdvPL – Parte 02 — vamos ver como podemos criar uma integração com o MongoDB usando AdvPL 😀

MongoDB

Em poucas palavras, trata-se de um banco de dados NoSQL, OpenSource e multiplataforma para documentos JSON. Possui versão “Community” e “Enterprise”, suporta indexação, Transações (ACID), replicação, cluster, balanceamento (sharding), agregação, e escala horizontalmente 😀

Montando um Client / Driver em AdvPL

O MongoDB possui uma interface TCP através da qual podemos enviar e receber requisições para o Banco de Dados. Existem drivers implementados em várias linguagens — C, C++, Java , C#, Python, PHP, Go, NodeJS, .NET, entre outras… Por baixo do capô, cada um dos drivers utilizam conexão TCP com o MongoDB.

A linguagem AdvPL oferece a classe tSocketClient, que permite abrir uma conexão TCP (Client-Side), podemos usá-la como base para implementar a troca de mensagens ( requisição / resposta ), no formato especificado e documentado em MongoDB Wire Protocol.

Vale lembrar que TCP é um protocolo de rede para troca de mensagens ou pacotes em blocos de bytes — simples assim. Cada especificação de protocolo de troca de dados na Internet — como HTTP, FTP, TELNET, etc — são especificações dos formatos dos pacotes de requisição e retorno, implementados sobre o TCP.

Especificação do Protocolo do MongoDB

As estruturas de requisição e retorno do protocolo são documentadas em uma notação parecida com uma “struct” em C, e as definições dos tipos utilizados estão documentadas na especificação BSON — Binary JSON (uma forma de representação  binária de um documento JSON). Não é necessário ser um programador em C para entender a especificação, mas um exemplo sempre ajuda a compreender melhor a definição 🙂

No final das contas, as estruturas servem de “guia” para explicar como devem ser criadas as sequências de bytes que fazem parte das requisições e retornos TCP que vamos enviar e receber do MongoDB. Como o tipo “C” caractere de variáveis do AdvPL suporta bytes de 0 a 255, montamos a sequência de bytes para uma requisição e enviamos ela através da conexão, e tratamos o retorno dessa requisição, que por sua vez também será recebida em uma variável caractere.

Um detalhe importante sobre a representação numérica no protocolo: Todos os números inteiros do protocolo devem ser Little-Endian — para mais detalhe sobre o que isso significa, leia o post O que é CODEPAGE e ENCODING – Parte 03.

Prova de Conceito

Procurar um exemplo sempre ajuda na compreensão do protocolo. No post “Building a Micro MongoDB Driver — Learning how clients communicate with MongoDB” têm um exemplo em C muito bem explicado e detalhado de “como fazer” e “como funciona” o mecanismo de envio e recebimento de requisições com o MongoDB usando um socket TCP. Agora, vamos ver como podemos fazer isso em AdvPL.

O protocolo de comunicação com o MongoDB inicialmente foi concebido para realizar operações baseadas em mensagens com estruturas comuns e um código de operação (OpCode) especificado como uma de suas propriedades, para realizar operações de inserção, update, delete e query. Posteriormente, foi criado um OpCode chamado OP_MSG — e através dele podemos executar não apenas as operações predefinidas no protocolo, mas qualquer comando do MongoDB. Desse modo, a partir do MongoDB 3.5 e superiores, temos uma forma unificada de comunicação com o banco usando apenas uma mensagem, e nela informar o comando que queremos executar. E, a resposta do MongoDB também vai ser no formato da mensagem OP_MSG.

A prova de conceito da conexão será criada inicialmente para executar a instrução buildinfo no MongoDB, gerando uma mensagem no formato esperado pelo MongoDB, enviando por uma conexão TCP, e lendo o resultado:

#include 'protheus.ch'
#include 'zlib.ch'

USER Function MongoTst()
Local oTCPConn
Local iStat
Local cSendCmd := ''
Local cSection := ''
Local nSend, nRecv , cRecvBuff := ''

// Cria o objeto client da conexão TCP
oTCPConn := tSocketClient():New()

// Abre uma conexão TCP com o MongoDB
iStat := oTCPConn:Connect( 27017 , "127.0.0.1", 1000 )

If iStat < 0 
  conout('Falha de conexão ( '+cValToChar(iStat)+' ) ')
  Return
Endif

// Cria a chamada para o comando buildInfo usando um objeto JSON
oJsonCmd := JsonObject():new()

// Insere o comando a ser chamado 
// https://docs.mongodb.com/manual/reference/command/buildInfo/#dbcmd.buildInfo
oJsonCmd['buildInfo'] := 1

// Insere o $db -- necessário para executar os comandos
oJsonCmd['$db'] := 'db'

// Cria uma seção para uma requisição ( Section 0 ) 
// Usa um objeto JSON gerado e converte para BSON 
cSection := chr(0) + JTOBSON( oJsonCmd , {'buildInfo','$db'} )

// Cria a mensagem com a requisição para o MongoFB
cSendCmd += L2Bin( len(cSection) + (5*4) ) // Tamanho total da requisição, Header + Flags + Seção 0 
cSendCmd += L2Bin(0) // Request ID, pode ser zero 
cSendCmd += L2Bin(0) // REsponeId , usado apenas pelo Mongo na resposta 
cSendCmd += L2Bin(2013) // OpCode para envio de comando por mensagem 
cSendCmd += L2Bin(0) // Flags 
cSendCmd += cSection // Seção 0 com o comando a ser executado

// Manda pro MongoDB 
nSend := oTCPConn:Send(cSendCmd)

// Mostra no console os dados enviados
conout("Sent Bytes ["+cValToChar(nSend)+"]")
conout(STR2HexDmp(cSendCmd))

// Agora verifica o resultado 
// Espera até 2 segundo pelo resutado 
cRecvBuff := ''
nRecv := oTCPConn:Receive(@cRecvBuff,2000)

// Mostra um DUMP do que foi retornado pelo MongoDB
conout("Recv Bytes ["+cValToChar(nRecv)+"]")
conout(STR2HexDmp(cRecvBuff))

// Fecha / encerra a conexão 
oTCPConn:CloseConnection()

Return

// Funcão auxiliar JTOBSON
// Retorna uma string binária BSON representando
// o Objeto JsonObject usado como parâmetro
// Recebe a lista de propriedades a considerar

STATIC Function JTOBSON(oJson , aNames )
Local cBson := ''
Local nI , nT := len(aNames)
Local cName , xValue, cValType

For nI := 1 to nT
  cName := aNames[nI]
  xValue := oJson[aNames[nI]]
  cValType := valtype(xValue)
  If cValType == 'C'
    cBson += chr(02) // BSON UTF8 String
    cBson += ( cName + chr(0) )
    cBson += l2bin( len(xValue)+1 )
    cBson += xValue
    cBson += chr(0)
  ElseIf cValType == 'N'
    // por hora inteiro de 32 bits 
    cBson += chr(16) // BSON int32
    cBson += ( cName + chr(0) )
    cBson += l2bin( xValue )
  ElseIf cValType == 'U'
    cBson += chr(10) // BSON NULL Value 
    cBson += ( cName + chr(0) )
  Else
    USerException("JTOBSON - Value Type ["+cValType+"] not implemented")
  Endif
Next

// Monta a string BSON : 
// 4 bytes com o tamanho, mais o conteúdo
// finaliza com ASCII 0 
Return l2bin( len(cBson)+ 5 ) + cBson + chr(0)

As funções acima não tratam ainda o formato de retorno, apenas mostra o que foi recebido no Socket. E a função auxiliar de conversão de JSONObject para BSON não trata array, valores booleanos e objetos encadeados. A implementação “final” envolve passar a limpo a prova de conceito e implementar tudo o que falta. Vejamos abaixo o resultado obtido no log de console do Application Server:

Sent Bytes [53]
350000000000000000000000DD070000|5...........▌...
000000000020000000106275696C6449|.........buildI
6E666F00010000000224646200030000|nfo......$db....
0064620000                      |.db..

RecvBytes [1138]
720400000400000000000000DD070000|r...........▌...
00000000005D0400000276657273696F|.....]....versio
6E0006000000342E322E310002676974|n.....4.2.1..git
56657273696F6E002900000065646636|Version.)...edf6
64343538353163306239656531353534|d45851c0b9ee1554
38663066383437646631343137363461|8f0f847df141764a
3331376500027461726765744D696E4F|317e..targetMinO
53002100000057696E646F777320372F|S.!...Windows7/
57696E646F7773205365727665722032|WindowsServer2
30303820523200046D6F64756C657300|008R2..modules.
050000000002616C6C6F6361746F7200|......allocator.
0900000074636D616C6C6F6300026A61|....tcmalloc..ja
7661736372697074456E67696E650006|vascriptEngine..
0000006D6F7A6A730002737973496E66|...mozjs..sysInf
6F000B00000064657072656361746564|o.....deprecated
000476657273696F6E41727261790021|..versionArray.!
00000010300004000000103100020000|....0......1....
00103200010000001033000000000000|..2......3......
036F70656E73736C0023000000027275|.openssl.#....ru
6E6E696E67001100000057696E646F77|nning.....Window
7320534368616E6E656C000003627569|sSChannel...bui
6C64456E7669726F6E6D656E74008202|ldEnvironment.é.
000002646973746D6F64000900000032|...distmod.....2
303132706C7573000264697374617263|012plus..distarc
6800070000007838365F363400026363|h.....x86_64..cc
0048000000636C3A204D6963726F736F|.H...cl:Microso
66742028522920432F432B2B204F7074|ft(R)C/C++Opt
696D697A696E6720436F6D70696C6572|imizingCompiler
2056657273696F6E2031392E31362E32|Version19.16.2
3730333420666F722078363400026363|7034forx64..cc
666C61677300190100002F6E6F6C6F67|flags...../nolog
6F202F45487363202F5733202F776434|o/EHsc/W3/wd4
303638202F776434323434202F776434|068/wd4244/wd4
323637202F776434323930202F776434|267/wd4290/wd4
333531202F776434333535202F776434|351/wd4355/wd4
333733202F776434383030202F776435|373/wd4800/wd5
303431202F776434323931202F776534|041/wd4291/we4
303133202F776534303939202F776534|013/we4099/we4
393330202F5758202F6572726F725265|930/WX/errorRe
706F72743A6E6F6E65202F4D44202F4F|port:none/MD/O
32202F4F792D202F6269676F626A202F|2/Oy-/bigobj/
7574662D38202F7065726D6973736976|utf-8/permissiv
652D202F5A633A5F5F63706C7573706C|e-/Zc:__cpluspl
7573202F5A633A73697A65644465616C|us/Zc:sizedDeal
6C6F63202F766F6C6174696C653A6973|loc/volatile:is
6F202F646961676E6F73746963733A63|o/diagnostics:c
61726574202F7374643A632B2B313720|aret/std:c++17
2F4777202F4779202F5A633A696E6C69|/Gw/Gy/Zc:inli
6E6500026378780048000000636C3A20|ne..cxx.H...cl:
4D6963726F736F66742028522920432F|Microsoft(R)C/
432B2B204F7074696D697A696E672043|C++OptimizingC
6F6D70696C65722056657273696F6E20|ompilerVersion
31392E31362E323730333420666F7220|19.16.27034for
7836340002637878666C616773000400|x64..cxxflags...
00002F545000026C696E6B666C616773|../TP..linkflags
003B0000002F6E6F6C6F676F202F4445|.;.../nologo/DE
425547202F494E4352454D454E54414C|BUG/INCREMENTAL
3A4E4F202F4C41524745414444524553|:NO/LARGEADDRES
534157415245202F4F50543A52454600|SAWARE/OPT:REF.
027461726765745F6172636800070000|.target_arch....
007838365F363400027461726765745F|.x86_64..target_
6F73000800000077696E646F77730000|os.....windows..
10626974730040000000086465627567|.bits.@....debug
0000106D617842736F6E4F626A656374|...maxBsonObject
53697A6500000000010473746F726167|Size......storag
65456E67696E6573004C000000023000|eEngines.L....0.
07000000626967676965000231000800|....biggie..1...
00006465766E756C6C00023200110000|..devnull..2....
00657068656D6572616C466F72546573|.ephemeralForTes
74000233000B00000077697265645469|t..3.....wiredTi
6765720000016F6B00000000000000F0|ger...ok.......≡
3F00                            |?.

Os bytes recebidos também estão no formato de uma mensagem OP_MSG, com header e seção de dados com um objeto BSON. A prova de conceito não cobre o tratamento destes dados, apenas por hora mostrar o retorno recebido.

Conclusão

Por hora, conclui-se apenas que é possível e viável criar um driver em AdvPL para conectar com um MondoDB 😀 Aguardem as cenas dos próximos capítulos, têm mais coisas no forno !!!

Agradeço desde já a audiência, e lhes desejo TERABYTES DE SUCESSO !!! 

Referências

 

3 comentários sobre “MongoDB em AdvPL – Prova de Conceito

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s