Criando um chat com Node.js e Socket.io

Node.js é uma tecnologia com menos de 10 anos, tendo sido lançada em 2009. No entanto, tem uns 5 anos que se tornou imensamente popular, desde que grandes nomes do mundo da tecnologia como Uber, Netflix e PayPal começaram a utilizá-la em produção.

Uma das principais características do Node.js, se não for sua principal, é o fato de trabalhar de maneira assíncrona, com requisições não bloqueantes e baseadas em eventos, o que o torna muito interessante para aplicações real-time. Aliado à isso temos e um ecossistema vibrante que cresce dia-a-dia com todo tipo de tecnologia auxiliar à plataforma, como o fantástico Socket.io.

Todo mundo que mexe com web já deve ter ouvido falar de websockets, Comet, etc. Esse tipo de tecnologia muito popularizado pelo Twitter nos idos de 2010-2012, promoveu uma revolução na forma de trabalhar comunicação cliente-servidor, cliente-cliente, real-time, etc. E Socket.io é justamente isso: uma tecnologia para comunicação real-time, bi-direcional, baseada em eventos.

Nesse post vamos explorar a criação de um aplicação de chat usando Node.js e Socket.io, visando dar uma introdução à tecnologia.

Veremos neste post:

  1. Introdução e Configuração
  2. Criando a aplicação web
  3. Programando o chat
  4. Indo além

Vamos lá!

#1 – Introdução e Configuração

Para este tutorial você precisa ter o Node.js instalado na sua máquina. Baixe e instale a versão mais recente para o seu sistema operacional. Usaremos sobre o Node.js o webframework Express (que eu já ensinei a usar antes), então abra o seu terminal de linha de comando e instale globalmente o módulo express-generator, que permite facilmente criar aplicações web com Express.

Muito provavelmente você terá de ter permissão de administrador para rodar o comando acima.

Agora, vamos criar um novo projeto Express usando o comando abaixo em que eu passo a view-engine (EJS, que usa HTML+JS), deixo o projeto preparado para o Git e passo o nome da pasta onde o projeto será armazenado.

Isso irá criar toda a estrutura mínima de pastas e arquivos para termos uma aplicação web funcionando com Express. Agora entre na pasta do projeto e rode um npm install para instalar as dependências:

Isso irá baixar todas as dependências necessárias para o básico funcionar. Mas ainda precisaremos de mais dependências!

Instale a dependência do módulo Socket.io usando o comando abaixo, que irá salvar esta dependência no seu arquivo package.json:

O Socket.io é dividido em duas partes: cliente em servidor. Essa dependência é a necessária para criar o servidor que rodará em nossa aplicação Node.js. Trabalharemos com o cliente mais tarde. Para deixar nosso servidor de chat já esperando conexões, abra o seu bin/www (que não possui extensão mas pode ser aberto por qualquer editor) e inclua o seguinte trecho de código que inicializa o servidor Socket.io e imprime uma mensagem no console toda vez que alguém “entra”no chat (que aqui basta acessar a página):

Esse código deve ficar depois do comando createServer.

Depois de instalar todas dependências e deixar pronto o rascunho do servidor de chat, apenas retorne à raiz do projeto e execute-o através do comando abaixo:

Se tudo deu certo, você deve abrir no seu navegador a URL localhost:3000 e ver a imagem abaixo:

Express funcionando
Express funcionando

Agora, para ver se o servidor Socket.io está funcionando realmente, vamos ter de implementar o lado do cliente.

#2 – Criando a aplicação web

Agora que temos todo nosso ambiente configurado, funcionando e sabemos o que vamos fazer, é hora de criar o front-end da nossa aplicação, que será bem “espartano”.

Na pasta views do seu projeto você encontrará arquivos .ejs que são as interfaces/layouts da sua aplicação web. Crie um novo arquivo chat.ejs (com qualquer editor de texto) e cole o seguinte conteúdo HTML dentro dele:

Para que essa view seja exibida no navegador, precisamos criar uma rota para ela. Fazer isso é bem fácil, entre na pasta routes e abra o arquivo index.js em seu editor de texto favorito. Dentro dele, adicione o seguinte bloco de código:

Isso quer dizer que, quando chegar uma requisição GET em /chat, renderizaremos a view chat.ejs. O resultado você confere abaixo, mandando rodar e acessando /chat no seu navegador:

Tela de Chat
Tela de Chat

A experiência do usuário com essa tela é bem simples: o usuário digita uma mensagem no campo e clica em Send para enviá-la. Obviamente nada disso funciona ainda.

O próximo passo é ajustar o lado do cliente para que ele se conecte em nosso servidor assim que entrar nessa tela. Para fazer isso, abra o nosso arquivo views/chat.ejs e inclua o seguinte trecho de código antes da tag </body>, lá pelo final do documento:

Esse código carrega a inicializa a biblioteca socket.io-client, que é um JS client-side que se conecta no server-side. Aqui eu não passei a URL do servidor para a função io(), sendo assim ele vai se conectar localhost mesmo.

Para ver se isto está funcionando, mande rodar novamente a sua aplicação e acesse a tela de chat, depois olhe no console. Acesse em várias abas diferentes e verá o comportamento abaixo no console:

Chat Server
Chat Server

Se quisermos saber também quando um usuário se desconectou do chat (ou seja, fechou a janela do navegador), podemos tratar isso com o evento disconnect. Lá em nosso bin/www, onde iniciamos o servidor de chat, altere o código para que seja impresso no console quando o usuário deixa a sala de chat:

Agora se iniciarmos novamente a aplicação e fizermos o mesmo procedimento de abrir e fechar abas, veremos um comportamento um pouco diferente:

Chat Server Funcionando
Chat Server Funcionando

E com isso agora temos tanto o client quanto o server da nossa aplicação de chat funcionando!

#3 – Programando o chat

A ideia principal por trás do Socket.io é que você pode enviar e receber quaisquer eventos que quiser, com os dados que quiser, usando objetos JSON ou binários mesmo.

Agora vamos programar o client-side da nossa aplicação para que quando o botão Send seja clicado, um evento ‘chat message’ seja disparado ao servidor com a mensagem escrita. Esse código deve ir no chat.ejs, no final do arquivo, substituindo o código anteriormente adicionado:

Agora temos de fazer com que nosso chat-server esteja preparado para lidar com esse evento. Sendo assim, abra o seu bin/www novamente e modifique o código do seu chat-server como abaixo:

Agora nosso chat-server já está recebendo mensagens. Se você executar a aplicação e escrever e enviar algumas mensagens, vai ver elas escritas no terminal do console.

Aqui a comunicação está sendo unidirecional: somente o server está recebendo as mensagens dos clientes. Nosso próximo passo é fazer com que as mensagens recebidas pelo server sejam propagadas para os clientes que estão na mesma sala de bate-papo. Para fazer isso, vamos mexer novamente no código do nosso chat-server em bin/www:

Para fazer a propagação da mensagem para todos os clientes, usamos o io.emit, passando o nome do evento que queremos propagar e os dados do evento, que aqui no caso é apenas a mensagem crua.

No entanto se você testar agora, verá que apesar do chat estar funcionando “por baixo dos panos”, não aparece nada na tela ainda. Isso porque ainda precisamos de uma última alteração no nosso front-end para tratar o recebimento deste evento e exibi-lo na tela do chat. Mude o bloco do client-side para o abaixo:

Agora sim, teste sua aplicação em mais de uma aba e verá que as mensagens enviadas por uma delas são repercutidas nas demais, como na imagem abaixo:

Chat Funcionando
Chat Funcionando

Legal não?!

#4 – Indo além

Esse tutorial termina aqui, mas existem diversas coisas que você pode estudar para aperfeiçoar esse chat. Dentre elas:

  • autenticação com passport;
  • persistência das conversas com MongoDB;
  • suporte a nicknames;
  • enviar um broadcast quando alguém novo entra na sala, ou quando alguém sai da mesma;
  • funcionalidade de “usuário está digitando…” através de Ajax com JQuery
  • mensagens privadas

O Socket.io é bem extensível com seu modelo aberto de eventos e dados, logo, é muito simples customizá-lo para atender todo tipo de demandas, criando seus eventos personalizados.

Espero que tenham gostado!

Fonte: este post foi claramente influenciado pela documentação oficial do Socket.io (em inglês).

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!

Tutorial Node.js com MS SQL Server

Hoje vou ensinar como que você pode usar Node.js com MS SQL Server. Não, essa não é uma dupla muito comum de ver no mercado, o mais normal é que Node.js seja utilizado com MongoDB, ou com MySQL. Eu cheguei a fazer umas pesquisas de tendências antes de escrever este post e MySQL é em média 10x mais utilizado com Node.js. No entanto, recentemente tive de fazer uma API que se comunicava com SQL Server e isso me fez descobrir que esse é um assunto pouco comentado na Internet, então resolvi deixar aqui este tutorial para quem precisar mais tarde (incluindo eu).

Veremos neste artigo (pule seções à vontade):

  1. Criando o banco de dados
  2. Criando e populando a tabela
  3. Criando a API
  4. Criando a listagem de clientes
  5. Criando a pesquisa de um cliente
  6. Excluindo um cliente
  7. Adicionando um cliente
  8. Atualizando um cliente
  9. Bônus 1: Executando muitas operações SQL
  10. Bônus 2: ORM

Então vamos lá!

Parte 1: Criando o banco de dados

Se você já possui um banco de dados SQL Server, ok, use ele e pule para a próxima parte.

Se você não possui um banco SQL Server, você tem duas opções:

  1. baixar e instalar o MS SQL Server Express (gratuito) na sua máquina;
  2. contratar um banco SQL Server na nuvem (pago, mas vou te ensinar um truque);

A primeira opção é um pouco mais trabalhosa, porém é free. Baixe, instale e crie um novo banco de dados para uso nesse tutorial. Durante a instalação, você irá precisar definir uma senha para o usuário ‘sa’, não esqueça dela.

A segunda opção é bem simples, mais vai te custar alguns pilas. Ou não. Na Umbler, empresa em que trabalho como evangelista tecnológico, você ganha créditos de graça para usar livremente conforme vai cumprindo algumas etapas dentro do painel. Crie um site com um domínio qualquer (mesmo que não tenha registrado) e depois vá na parte de banco de dados para criar um do tipo SQL Server. Isso te custará uns R$15 por mês, mas apenas se deixar o mês inteiro funcionando, você pode excluir tudo logo após esse tutorial, se quiser, e não será mais cobrado. Durante a criação do seu banco, você vai definir o nome dele, o usuário e a senha, anote tudo, bem como o endereço do servidor que a Umbler vai te fornecer.

Parte 2: Criando e populando a tabela

Agora que você já tem o banco pronto, vamos criar uma tabela nele e colocar alguns dados de exemplo. Não pule esta etapa pois vamos fazer tudo isso usando Node.js!

Crie uma pasta para guardar os arquivos do seu projeto Node.js, você pode fazer isso pelo console se quiser, usaremos ele algumas vezes nesse tutorial. No exemplo abaixo, criei a pasta e depois entrei dentro dela.

Agora execute no console o comando “npm init” que o próprio NPM (gerenciador de pacotes do Node) vai te guiar para a construção do arquivo packages.json, que é o arquivo de configuração do projeto. Se ficar em dúvida ou com preguiça, segue o meu package.json abaixo:

Com o arquivo de configurações criado, vá no console novamente, na pasta do projeto e digite o seguinte comando para instalar a extensão mssql, que permite usar Node com SQL Server:

A flag “-S”diz que é pra salvar essa dependência no arquivo packages.json. Se você abrir o arquivo vai ver que tem coisa nova por lá.

Agora, crie um arquivo create-table.js dentro dessa pasta, que será o arquivo que vai criar e popular nossa tabela que usaremos neste exercícios. Também usaremos ele para entender o básico de comandos SQL, conexão com o banco, etc. Vamos começar nosso create-table.js definindo uma constante para a String de conexão com o banco e uma constante para o objeto que vai carregar a extensão mssql (e que mais tarde usaremos para conectar, executar SQL, etc). Se você já usou SQL Server com C#, a String é a mesma, caso contrário, preencha as lacunas conforme a String de exemplo abaixo:

Agora, usaremos esse objeto sql para fazer uma conexão e, em caso de sucesso, imprimir uma mensagem de sucesso. Caso contrário, se der erro, uma mensagem de falha. Note que existem diversas maneiras de programar este mesmo algoritmo em Node.js: você pode usar a forma clássica com callbacks, a forma intermediária com promises e a forma mais recente com async/await. Eu vou fazer com promises, que acho que é a que fica mais legível:

Se esse código lhe parece muito bizarro, calma, é fácil de entender. O objeto sql permite que façamos coisas no banco de dados, uma delas é a conexão (connect). No entanto, o Node.js trabalha de maneira assíncrona, o que quer dizer que ele não espera pela conexão ser estabelecida, ele retorna uma promessa (promise) de que vai executar algo (o meu ‘then’ ou o meu ‘catch’ se der erro) após a conexão for concluída. Sendo assim, no ‘then’ eu coloco uma função JS que será executada quando a conexão funcionar, e no ‘catch’ uma função JS que será executada em caso de erro.

Neste exemplo usei arrow-functions que está disponível desde o ES6 (versão 2016 do Javascript), que são formas mais simples de declarar funções pequenas. O que vem antes do ‘=>’ (arrow) são os parâmetros da função. O que vem depois são os comandos da mesma. Usarei novamente mais tarde e volto a explicar para você.

Para executar esse arquivo, abra o console (se estiver usando VS Code, apenas aperta F5 com este arquivo aberto no editor) e na pasta do projeto digite:

Agora que sabemos como conectar no SQL Server através de Node.js, é hora de executarmos o comando que vai criar a tabela e popular ela, ao invés de simplesmente imprimir “conectou”. Sendo assim, vamos criar uma função JS nesse arquivo pra fazer a criação da tabela:

Coloque a chamada desta função no ‘then’ após a conexão no banco, passando o objeto conn por parâmetro, como abaixo:

Mande rodar esse arquivo novamente e verá que ele criará o seu banco já com 3 linhas de dados. Se não quiser fazer isso dessa maneira, você pode fazer pela sua ferramenta de gerenciamento do SQL Server (como o Management Studio) ou pelo Visual Studio, se estiver usando ele para programar Node.js.

Parte 3: Criando a API

Agora que já temos nosso banco de dados SQL Server pronto, com dados de exemplo e aprendemos como fazer a conexão nele, vamos criar uma API básica usando Express para conseguir criar um CRUD com Node.js + MS SQL Server no próximo passo. Se já sabe como montar uma API básica com Node + Express, pule esta parte.

Vamos começar adicionando a dependência do Express (framework web) e do Body-Parser (parser para os POSTs futuros) no projeto via linha de comando na pasta do mesmo:

Na sequência, vamos criar um arquivo index.js na pasta do projeto onde vamos criar o nosso servidor da API para tratar as requisições que chegarão em breve. Vamos começar bem simples, apenas definindo as constantes locais que serão usadas mais pra frente:

Vamos usar apenas um pool de conexões global com o servidor MS SQL. Para isso adicione as seguintes linhas logo abaixo do bloco anterior, para conectar no banco de dados assim que a aplicação iniciar (podemos melhorar isso, mas por ora, vamos deixar assim mesmo):

Agora, logo abaixo, vamos configurar nossa aplicação (app) Express para usar o body parser que carregamos da biblioteca body-parser, permitindo que recebamos mais tarde POSTs nos formatos URLEncoded e JSON:

Na sequência, vamos criar um roteador e dentro dele definir uma regra inicial que apenas exibe uma mensagem de sucesso quando o usuário requisitar um GET na raiz da API (/) para ver se está funcionando.

Note que na última linha eu digo que requisições que chegarem na raiz devem ser mandadas para o router. Por fim, adicionamos as linhas abaixo no final do arquivo que dão o start no servidor da API:

Teste sua API executando via console o seu index.js com o comando ‘node index.js’. Você deve ver a mensagem de ‘API funcionando!’ no console, e se acessar no navegador localhost:3000 deve ver o JSON default que deixamos na rota raiz!

API Funcionando
API Funcionando

Parte 4: Criando a listagem de clientes

Agora que temos uma API funcionando, vamos adicionar uma rota /clientes que listará todos os clientes do banco de dados. Para fazer isso, primeiro vamos criar uma função que executará consultas SQL no banco usando o pool de conexões global (conn), como abaixo:

Esta função nós usaremos para consultar todos os clientes e para consultar apenas um também. Agora, vamos criar a rota /clientes logo abaixo da rota / (raiz):

Agora, ao executarmos novamente nosso projeto e acessarmos a URL localhost:3000/clientes, veremos todos os clientes cadastrados no banco de dados (no passo 2, lembra?):

Todos clientes
Todos clientes

E com isso finalizamos a listagem de todos clientes na nossa API!

Parte 5: Criando a pesquisa de um cliente

Agora, se o usuário quiser ver apenas um cliente, ele deverá passar o ID do mesmo na URL, logo após o /clientes. Para fazer isso, vamos modificar nossa rota criada no passo anterior, /clientes, para aceitar um parâmetro opcional ID. Além disso, dentro do processamento da rota, se vier o ID, devemos fazer uma consulta diferente da anterior, como mostra o código abaixo, com os ajustes na mesma rota do passo anterior:

O parseInt que coloquei é apenas uma proteção contra SQL Injection uma vez que neste caso o ID deve ser um inteiro válido. Não é a melhor forma de resolver isso, mas vai nos atender por enquanto sem ter de entrar em conceitos mais avançados. Manda rodar e teste no navegador, verá que está funcionando perfeitamente!

Apenas um
Apenas um

E com isso terminamos a pesquisa por cliente.

Parte 6: Excluindo um cliente

Para excluir um cliente vamos fazer um processo parecido com o de pesquisar um cliente, no entanto, mudaremos o verbo HTTP de GET para DELETE, como manda o protocolo. Adicione a nova rota logo após as demais:

Note que desta vez o parâmetro id na URL não é opcional (não usei ? após :id). E dentro do processamento da requisição delete do router eu mando um SQL de DELETE passando o ID numérico.

Para testar essa rota você tem duas alternativas, ou usa o POSTMAN para forjar um DELETE, como abaixo:

DELETE com POSTMAN
DELETE com POSTMAN

Ou fazer via console usando cURL (se tiver ele instalado na sua máquina):

Em ambos os casos você deve obter uma resposta 200 OK (caso não tenha dado erro) e se mandar listar todos clientes novamente, verá que o número 1 sumiu.

Parte 7: Adicionando um cliente

Agora vamos adicionar um novo cliente com um POST na rota /clientes. Adicione esta nova rota logo abaixo das anteriores.

Nela, eu pego as variáveis que devem vir junto ao POST, faço algumas validações de tamanho e tipo de dado e depois junto elas a um comando de INSERT que vai ser executado no banco de dados.

Para testar esse POST, você usar o POSTMAN, como mostrado anteriormente:

POST no POSTMAN
POST no POSTMAN

Se quiser fazer via cURL:

Também podemos permitir outras maneiras de passar os dados com a requisição POST, como através de JSON ou através da URL, mas isso foge do escopo deste artigo que deve focar mais no CRUD com SQL Server + Node.

Se testar agora vai ver que é possível inserir novos registros no banco de dados através de requisições POST.

Parte 8: Atualizando um cliente

E para finalizar o CRUD, vamos ver como podemos atualizar um cliente no banco de dados SQL Server através da nossa API Node.js. Para fazer updates podemos usar os verbos PUT ou PATCH. O protocolo diz que devemos usar PUT se pretendemos passar todos os parâmetros da entidade que está sendo atualizada, mas não vamos alterar jamais o ID, então usaremos PATCH nesta API.

Crie uma rota PATCH em /clientes esperando o ID do cliente a ser alterado.

No código acima, pegamos o ID que veio na URL e as demais informações que vieram no corpo da requisição, fazendo as mesmas validações que já havia feito antes (podemos melhorar a segurança aqui). Depois monto o UPDATE com as variáveis locais e mando para nossa função de executar SQL.

Para testar uma requisição PATCH, você pode usar o POSTMAN:

PATCH no POSTMAN
PATCH no POSTMAN

Ou o cURL:

O resultado é o mesmo: o cliente cujo ID=4 vai ter o seu nome alterado para ‘fernando’. Note que se ele não existir, ocasionará um erro que será apresentado no corpo da resposta.

E com isso finalizamos o CRUD da nossa API Node.js que usa SQL Server como persistência de dados.

Bônus 1: Executando muitas operações SQL

Ao contrário dos bancos não-relacionais que estamos acostumados a usar com Node.js, o SQL Server leva mais a sério a escrita dos dados e o faz de maneira síncrona e um tanto lenta para uma plataforma veloz como o Node.js. Isso faz com que, se você for executar um loop de comandos no banco de dados, provavelmente vai dar erro pois o banco não conseguirá responder apropriadamente a uma enxurrada de requisições da sua aplicação Node.

Para resolver isso eu costumo encadear as execuções uma a uma, simulando uma execução assíncrona através de chamadas recursivas, como no exemplo abaixo:

Neste exemplo eu possuía um array de itens que eram endereços de email que eu queria excluir do banco de dados. O i passado por parâmetro inicialmente é 0, quando chamarmos esta função a primeira vez e depois vai sendo incrementado recursivamente. Já o objeto conn é a conexão do banco de dados, que você pode omitir se estiver usando ela global.

O primeiro if do código garante a condição de parada da recursão e o catch na promise da query te contará se houver algum erro. Nada muito rebuscado, mas me foi bem útil!

Bônus 2: ORM

Se você não curte muito a ideia de ficar usando SQL no meio dos seus códigos JS, experimente usar alguma biblioteca ORM (Object-Relational Mapping) como o Sequelize. Nunca usei, mas muita gente recomenda por possuir suporte a MySQL, SQL Server, PostgreSQL e SQLite.

Até a próxima!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!

Persistência Poliglota: indo além do SQL!

O conteúdo deste post é de uma palestra que ministrei na Faccat este ano. Os slides estão no final do artigo, e o vídeo da palestra começa em 1h13 logo abaixo:

Há uns 10 anos,mais ou menos, eu estava fazendo as cadeiras de Banco de Dados 1 e Banco de Dados 2 na faculdade de Ciência da Computação. Eu via como modelar um banco de dados relacional, como criar consultas e executar comandos SQL, além de álgebra relacional e um pouco de administração de banco de dados Oracle. Isso tudo me permitiu passar a construir sistemas de verdade, com persistência de dados. A base em Oracle me permitiu aprender o simplíssimo MS Access rapidamente e, mais tarde, migrar facilmente para o concorrente, SQL Server. Posteriormente cheguei ainda a trabalhar com MySQL, SQL Server Compact, Firebird (apenas estudos) e SQLite (para apps Android).

Todos relacionais. Todos usando uma sintaxe SQL quase idêntica. Isso foi o bastante para mim durante alguns anos. Mas essa época já passou faz tempo. Hoje em dia, cada vez mais os projetos dos quais  participo têm exigido de mim conhecimentos cada vez mais poliglotas de persistência de dados, ou seja, diferentes linguagens e mecanismos para lidar com os dados das aplicações.

Antes de entrar em persistência poliglota, deixa eu perguntar uma coisa: considere uma caixa de ferramentas com alicate, martelo e chave de fenda, qual é a melhor ferramenta de todas?

Caixa de Ferramentas
Caixa de Ferramentas

A resposta mais coerente é: depende. Se você tem de apertar um parafuso, a chave de fenda. Quando tem de pregar um prego, o martelo. Para cada atividade, existe uma ferramenta adequada.

Avançando rumo à persistência poliglota, uma coisa que demorei a entender é que NoSQL não é melhor do que SQL. Lembra? Um martelo não é, necessariamente, melhor que uma chave de fenda.

Da mesma forma, embora muitas vezes a impressão que tenhamos é que bancos não-relacionais são sempre melhores do que os relacionais (tem muita empresa e curso que vende isso como verdade), não é assim que funciona. ACID tem o seu valor e muitos não-relacionais não se focam nisso, simplesmente porque não é esse o problema que querem resolver.

Existem diversos cenários em que você DEVE usar bancos SQL tradicionais, seja por questões legais (auditorias, certificações ou necessidades mais elementares), e cenários em que você PRECISA usar bancos não-relacionais. Jamais migre o SQL da sua empresa para um NOSQL pois ele parece mais cool ou porque todos estão falando de como ele é mais rápido. Isso é DOM – Desenvolvimento Orientado à Modinhas, e não é isso que estou propondo aqui.

O primeiro passo é entender o seu problema. O segundo passo é analisar as opções para resolvê-lo.

Passo 1: entenda o seu problema

Está com problema de performance no seu banco SQL? Já pensou em dar uma olhada nestas minhas dicas aqui de SQL Server e nestas aqui de SQLite? Já pensou em fazer um upgrade no hardware do seu servidor? Botar mais memória, um SSD de repente?

Garanto que isso é muito mais fácil, rápido e barato do que migrar todo o seu banco para um NOSQL que você nem conhece direito. Trocar de SQL para NOSQL sem pensar, na maioria dos casos, é trocar os problemas antigos que você conhecia por problemas novos que você não faz ideia de como resolver.

Passo 2: analise as opções

Agora, se o modelo entidade-relacionamento não está servindo, aí sim você começa a pensar em NOSQL. Daí sim entramos no passo de analisar as opções.

Uma opção é desenvolver o seu próprio mecanismo NOSQL, como eu fiz com o Busca Acelerada (e que aliás está no ar até hoje, enquanto não termino a nova versão com Node.js + MongoDB), como o Facebook fez com o Cassandra, como o Google fez com o Big Table e assim por diante. Mas, sinceramente, é meio difícil acreditar que alguma das dezenas de opções de NOSQL disponíveis atualmente não atendam a sua necessidade. É só uma questão de conhecer as opções e analisá-las com cuidado.

Faça o que eu digo, não faça o que eu fiz!

Pra  ajudar, vou  mostrar algumas excelentes opções a seguir. De nada! 😉

Para cada problema, uma solução

Existem dezenas de bancos NoSQL no mercado, não porque cada um inventa o seu, como nos fabricantes tradicionais de banco SQL (não existem diferenças tão gritantes assim entre um MariaDB e um MySQL atuais que justifique a existência dos dois, por exemplo). É apenas uma questão ideológica, para dizer o mínimo.

Existem dezenas de bancos NOSQL porque existem dezenas de problemas de persistência de dados que o SQL tradicional não resolve. Simples assim. Claro, também temos algumas classificações entre eles, pois resolvem problemas em comum, geralmente com abordagens diferentes. É provável que isso seja cada vez mais frequente conforme estas ferramentas se tornem mais populares (pouco se ensina nas faculdades ainda, por exemplo).

Não, eu não conheço todos. E sim, vou falar de algumas ferramentas de persistência aqui que nunca usei, mas que presenciei o uso em projetos ou conheço pessoas/empresas que usam. Novamente: não use nenhum deles porque acha que eu indiquei, mas sim porque resolvem o seu problema em questão.

Mas, voltando ao que interessa, é comum classificarmos os bancos NOSQL em algumas categorias:

  1. Armazenamento em Documentos

Estes são os mais comuns e mais proeminentes de todos, sendo o seu maior expoente o banco MongoDB, que eu já falei aqui no meu blog nos posts sobre Node.js. Basicamente aqui temos coleções de documentos, nas quais cada documento é autossuficiente, contém todos os dados que possa precisar, ao invés do conceito de não repetição + chaves estrangeiras do modelo relacional.

A ideia é que você não tenha de fazer JOINs, pois como já comentei em outro post, eles prejudicam muito a performance em suas queries (são um mal necessário no modelo relacional, infelizmente). Vai uma vez no banco e com apenas uma chave primária pega tudo que precisa.

Obviamente isto tem um custo: armazenamento em disco. Não é raro bancos MongoDB consumirem muitas vezes mais disco do que suas contrapartes relacionais.

Mas quando eu preciso de um MongoDB?

Quando o seu schema é variável, é livre. Os documentos BSON (semelhante ao JSON) do Mongo são schema-less e aceitam quase qualquer coisa que você quiser armazenar, sendo um mecanismo de persistência perfeito para uso com tecnologias que trabalham com JSON nativamente, como Javascript.

Documento JSON
Documento JSON

Quando eu não devo usar um MongoDB?

Quando relacionamentos entre diversas entidades são importantes para o seu sistema. Se for ter de usar chaves estrangeiras e JOINs, você está usando do jeito errado, ou, ao menos, não do jeito mais indicado. Aqui tem um post bem bacana falando disso, sensacionalista, mas bem completo.

Outro mecanismo bem interessante desta categoria é o RethinkDB, que foca em consultas-push real-time de dados do banco, ao invés de polling como geralmente se faz para atualizar a tela.

Para os amantes de .NET tem o RavenDB, que permite usa a sintaxe do LINQ e das expressões Lambda do C# direto na caixa, com curva de aprendizagem mínima.

Mais uma adição para seu conhecimento: Elasticsearch. Um mecanismo de pesquisa orientado a documentos poderosíssimo quando o assunto é pesquisa textual, foco da ferramenta. Ele é uma implementação do Apache Lucene, assim como Solr e Sphinx, mas muito superior à esses dois e bem mais popular atualmente também.

  1. Armazenamento Colunar

O maior expoente nesta categoria é o Cassandra, criado pelo Facebook. A maior rede social do mundo rapidamente percebeu que manter uma base de 1.5 bi de usuários criando milhares de registros todos os dias (likes, posts, fotos, amizades, etc) e depois ter de consultar tudo isso de maneira rápida e coerente, fazendo muitas agregações, não era uma tarefa que cabia aos bancos relacionais.

O armazenamento colunar, ao invés de guardar tabelas com linhas onde cada linha possui colunas, faz o inverso. Guardamos “tabelas” (não sou nenhuma autoridade no assunto) onde temos informações de apenas uma coluna de cada registro.

Base colunar vs tabular
Base colunar vs tabular

Mas quando eu preciso de um Cassandra?

Quando você faz muitas operações de agregação em suas colunas. Sabe o GROUP BY do SQL tradicional? Ela é outro mal necessário, assim como os JOINs, e tem uma performance ridiculamente baixa, principalmente associado com um WHERE complexo. Isso não é problema para uma base colunar, e um problema gigantesco para uma base orientada a documentos como o MongoDB, aliás.

Quando eu não devo usar um Cassandra?

Quando você precisa que suas consultas retornem dados completos ao invés de apenas informações colunares. Se isso for a regra, você deveria usar um relacional (meio-termo) ou um baseado em documento, como o Mongo (com registros “full-data” independentes).

Outra adição a esta seção é o HBase, escrito em Java e modelado a partir do BigTable do Google (este último proprietário e por isso não vale a pena citar aqui).

3 Armazenamento Chave-Valor

Estes bancos não-relacionais são o mais distante que você pode imaginar de um “banco de dados”, por isso que chamamos de mecanismos de persistência de dados, ao invés de “banco”. Vamos pegar como exemplo o Redis, o mais famoso mecanismo de chave-valor da atualidade.

Aqui temos uma arquitetura baseada em coleções de chaves. Cada registro possui uma única chave, até aqui tudo normal, mas cada chave está associada a um valor, que pode ser um valor literal, atômico, ou um objeto mais complexo, não importa. É um índice, geralmente sendo usado memory-only, mas que pode ser híbrido também (disco e memória).

Exemplos de Chave-Valor
Exemplos de Chave-Valor

Mas quando eu preciso de um Redis?

Quando você precisa subir um índice em memória que seja estupidamente rápido, que permita queries complexas baseadas na teoria dos conjuntos e que, após essa consulta no índice, você vá usar alguma outra base de dados com os dados completos do(s) registro(s) que está buscando.

Quando eu não devo usar um Redis?

Você não deve usar como único mecanismo de persistência na sua aplicação, pois se subir todos os seus dados para um Redis memória você não terá alguns benefícios de outros modelos e gastará muito, mas muito dinheiro, pagando por memória RAM (muito mais cara que SSDs, por exemplo). Use-o como cache de índices. Ponto.

Concorrentes válidos de citar são o memcached e MemcacheDB, popular mecanismo de cache in-memory usado na web e, mais recentemente, em conjunto com MySQL para proporcionar algumas vantagens do modelo não-relacional em adição às suas nativas.

4 Arquitetura em Grafos

O modelo entidade-relacionamento é muito bom quando o assunto é garantir que os relacionamentos entre as entidades seja seguido, mas fraquíssimo quando o assunto são relacionamentos complexos ou relacionamentos com propriedades.

Como assim? E as tabelas-meio?

Sim, elas são a solução para esse caso, uma solução bem ruim aliás, pois nos força a usar JOINs o tempo todo e bem, a essa altura você já deve saber que eu não curto muito JOINs…

O que acontece se a relação entre o registro do Huguinho e do Zezinho tiver o tempo que eles são amigos, o grau de parentesco entre ambos, ou pior, os filmes que eles gostam em comum ou os amigos que possuem em comum? Como fazer isso com um banco relacional?

Claro que dá, mas e como fica a performance das consultas depois? E os INSERTs? O mais famoso banco em grafo da atualidade é o Neo4J, e é ele que deveria ser aplicado nesse caso.

Grafo de Relacionamentos
Grafo de Relacionamentos

Quando eu preciso de um Neo4J?

Quando seus relacionamentos possuem características próprias e você terá de fazer consultas complexas baseadas nessas características e ordená-las pela proximidade de um registro do outro no mapa de relacionamentos (grafo) deles. Mais resumidamente: você desenha seu problema como um grafo? Pode ser uma boa considerar o Neo4J.

Quando eu não devo usar um Neo4J?

Você vê clara e facilmente o seu problema modelado em tabelas (ou outra estrutura de dados)? Então não use ele, use o modelo mais apropriado como ER ou document-based.

Não vejo nenhum concorrente especificamente orientado a grafos para falar junto do Neo4J, embora existam alguns multi-modelos com suporte à grafos bem proeminentes, que falarei mais tarde.

5 Time-series

Estes bancos são muito específicos para o problema de retornar séries de dados baseados em intervalos de tempo. Mas não estou falando de qualquer série de dados, mas um volume muito grande, como dados estatísticos e métricas analíticas que são capturadas e consultadas a todo instante. E quando falo a todo instante, quero dizer tempo-real.

O mais proeminente neste segmento, chamado de TSBD (time-series databases) é o InfluxDB. Curiosamente a sua sintaxe é muito semelhante ao SQL tradicional, especialmente as consultas.

INSERT InfluxDB
INSERT InfluxDB

Quando eu preciso de um InfluxDB?

Quando seus dados são associados a um período específico de tempo, são coletados dessa forma e precisam ser analisados dessa forma. Ações da bolsa, análise de tráfego web, estatísticas de consumo de recursos, esse tipo de problema de dados vs tempo.

Quando eu não preciso de um InfluxDB?

Nos demais cenários que não o bem específico acima.

Existem poucos mecanismos time-series, e nenhum tem alcançado tanta popularidade quanto o InfluxDB, por isso que não mencionarei nenhum nesta seção.

6 Multi-modelo

E se pudéssemos juntar o melhor dos dois mundos? Unir modelos relacionais e não-relacionais em um mesmo banco? Seria a solução perfeita, certo?

Essa é a proposta dos bancos multi-modelo. Nunca usei nenhum, para falar a verdade, e confesso que sou bem cético, afinal, quem quer fazer tudo não se torna realmente bom em nada, não é mesmo? Pelo menos é nisso que eu acredito e até que eu conheça algum case que realmente me impressione, continuarei achando isso.

No entanto, é fato que os bancos multi-modelo estão cada vez mais poderosos e famosos. Desde add-ons de implementações tradicionais como MySQL + Memcached ou Oracle 12c + Oracle NOSQL até bancos NOSQL inovadores como o OrientDB (documento + grafos), ArangoDB (document, grafo e chave-valor) e CouchBase (relacional + document + chave-valor).

Quando eu preciso de um banco multi-modelo?

Não sei informar razões comprovadas, mas posso supor que seja quando você quer juntar o melhor de diversas abordagens em um banco só.

Quando eu não devo usar um banco multi-modelo?

Quando apenas um modelo já supre as suas necessidades, ou quando a combinação de bancos mono-modelo diferentes produzirão um melhor resultado final (a tese que eu acredito).

Persistência poliglota na programação

“Ok, tudo isso é muito bonito, falar de usar a ferramenta certa para cada tarefa e tudo mais, super apoio, mas e como é que você programa com tantas fontes de dados diferentes?”

Essa é uma pergunta muito frequente. Quando falo em sala de aula que na empresa em que trabalho durante o dia usamos uma meia-dúzia de mecanismos de persistência diferentes, que aplico persistência poliglota no dia-a-dia, os alunos ficam meio confusos. E isso é normal.

Aprendemos na cadeira de banco de dados como modelar todo o domínio do nosso problema em um único banco de dados. Jamais somos ensinados como distribuir os dados (e depois consultar) usando bancos diferentes, quiçá mecanismos de persistência completamente diferentes!

Existem diversas abordagens possíveis e todas elas exigirão de você um conhecimento intermediário para avançado em arquitetura de software. Não dá pra fazer apenas com o que vemos na faculdade, infelizmente. Conforme aumenta o número de mecanismo que você precisar “plugar” na sua aplicação, mas conhecimento avançado você irá precisar.

Entenda que o primeiro e mais importante conceito é a separação de camadas. Você irá precisar isolar completamente sua camada de dados e abstrair completamente os diversos mecanismos sob uma única biblioteca de classes de entidades, para que as camadas superiores à de dados não tenham de lidar com complexidade alguma de dados. Agora, na camada de dados, aí que irão morar todos os seus problemas com persistência poliglota…

Facilmente eu poderia escrever um artigo inteiro apenas sobre implementação de persistência poliglota, mas como esse já passou das 3 mil palavras, vou deixar isto para uma próxima oportunidade. Por ora, estude as seguintes abordagens que vão te ajudar a entender como pode trabalhar com o problema:

E muita informação pode ser encontrada no livro Padrões de Arquitetura de Aplicações Corporativas, de Martin Fowler, que já citei nesse post dos livros mais recomendados de programação.

Casos de Uso

Os casos de uso mais notórios e frequentes de persistência poliglota são dos gigantes do e-commerce. Ok, provavelmente as redes sociais são casos de uso ainda mais incríveis, mas como temos muito mais e-commerces de grande porte do que redes sociais, vou falar delas aqui.

Soube através de fontes confiáveis que os e-commerces do grupo B2W, o maior grupo do Brasil neste segmento, com sites como Shoptime, Submarino e Americanas.com usam persistência poliglota em sua arquitetura. Eles usam:

  • entidade-relacional: dados dos clientes, informações de pagamento, transações, etc, toda a parte mais crítica e sensível dos dados, até por uma questão legal para poderem ter certificados de segurança e confiabilidade e poderem oferecer bandeiras como VISA e Master aos clientes.
  • document-based: catálogo de produtos. Como cada categoria de produto possuem características completamente diferentes das outras categorias (tente montar uma tabela ER onde você possa salvar vestuário e eletrônicos juntos…), o modelo de documentos JSON schemaless é perfeito pra isso. Também é muito comum usarem mecanismos focados em busca como Elasticsearch para que a pesquisa do site seja rápida e eficiente.
  • key-value: carrinho de compras. Até que o cliente da compra seja identificado e a compra finalizada, essa informação não precisa estar no banco de dados, pois é temporária. Sendo assim, manter esses dados em memória é uma opção muito mais rápida do que ir no disco cada vez que o cliente indeciso mudar de opinião.
  • grafo: produtos relacionados, recomendações, upsell, cross-sell, etc. Cada produto se relaciona a outros produtos de diversas maneiras: seja por características semelhantes, seja porque são complementares, seja porque são comprados frequentemente em conjunto, etc. Guardar e consultar todas essas variáveis é dificílimo em um ER e importantíssimo para aumentar o ticket de venda, fidelizar clientes, maximizar o ROI, etc.

Faz mais sentido pra você agora?

Um outro caso de uso que posso falar com mais propriedade é de duas startups em que tive a oportunidade de trabalhar mais diretamente com persistência poliglota: o Route e a Umbler.

No Route usávamos (a startup está em vias de fechar, já não trabalho mais lá) MongoDB para armazenamento-base, uma vez que o volume de dados que armazenávamos era absurdamente alto e sem esquema definido. O Mongo nos permitia escalonamento vertical e horizontal facilmente, bem como documentos JSON fáceis de trabalhar via APIs REST. Para apoiar o Mongo, que no nosso caso foi otimizado para escrita, usávamos ainda:

  • Elasticsearch para as pesquisas internas da aplicação;
  • Redis para cache de consultas comuns de dados de interface do usuário, evitando ter de ir no MongoDB a cada requisição e
  • RabbitMQ, um mecanismo de fila de tarefas in-memory que garante que todas requisições sejam atendidas de maneira assíncrona, sem bloquear a aplicação principal.

Já na Umbler, startup em que estou atualmente, temos um caso ainda mais complicado pois nossa base de dados principal é SQL Server, onde mantemos o core da aplicação, herança de uso dessa tecnologia por parte dos fundadores da empresa. Aliado ao SQL Server temos:

  • Redis para os dados do gráfico de consumo de RAM e CPU que exibimos aos nosso clientes em tempo real;  
  • InfluxDB para o monitoramento de estado dos servidores e notificações de consumo dos clientes que estão excedendo seus recursos contratados (para que possam fazer upgrade);
  • Elasticsearch para a infraestrutura que controla os logs de envio de emails dos nossos clientes, visando controlar o spam (usando um stack chamado ELK) e, para finalizar e
  • usamos RabbitMQ aqui também, para a mesma finalidade que tinha no Route.

Simples, não?! XD

Conclusão

Acho que consegui passar um pouco do sei sobre esse fantástico assunto que são os mecanismos de persistência variados que possuímos no mercado hoje. Ao contrário de muitos que acham que é ruim termos tantas opções, uma vez que dificulta o estudo, acho o contrário. É ótimo ter tantas opções de ferramentas, afinal, não podemos ser mestres no martelo se os projetos atuais precisam de furadeiras, certo? 🙂

Esse post foi usado como base de estudo para a minha palestra de Persistência Poliglota durante o Faccat Tech Party 2017, cujos slides seguem abaixo, para referência futura.

Até a próxima!