← Todos os artigos
  • WebSocket
  • tempo real
  • grids de dados

Adeus, botão de atualizar: dados ao vivo com WebSockets

Quando várias pessoas trabalham nos mesmos dados, uma tela desatualizada custa dinheiro. Como o SorlaDB mantém todas as telas em dia por um único WebSocket, com rolagem infinita, reconexão e sem perder edições.

Sexta-feira, 16h15. Dois vendedores, em duas cidades diferentes, olham a mesma tela de estoque. Os dois veem uma unidade restante do produto que os clientes querem. Os dois fecham a venda. Na segunda-feira, um dos clientes recebe um pedido de desculpas no lugar da entrega.

Ninguém errou. Cada tela estava certa quando carregou. Só deixou de estar alguns minutos depois, e nada avisou.

As equipes costumam conviver com isso treinando as pessoas a apertar o botão de atualizar, ou fazendo a tela perguntar ao servidor "tem novidade?" a cada poucos segundos. A primeira opção depende da memória. A segunda custa servidor e mesmo assim chega atrasada.

O problema real: telas que envelhecem

Toda tela compartilhada por várias pessoas tem a mesma fraqueza: ela é uma fotografia dos dados no momento em que carregou. Pedidos mudam de status, preços são corrigidos, alguém edita o mesmo registro. Quanto mais tempo a tela fica aberta, mais a fotografia se afasta da realidade.

As soluções de sempre têm um preço:

  • O botão de atualizar depende de as pessoas lembrarem de apertar, e recarrega tudo para mudar uma linha.
  • Polling (perguntar a cada poucos segundos) multiplica as requisições pelo número de telas abertas, quase sempre para ouvir "nada mudou", e as mudanças ainda chegam segundos depois.
  • Travar registros evita conflitos fazendo as pessoas esperarem umas pelas outras.

A ideia: uma linha aberta nos dois sentidos

Um WebSocket é uma linha telefônica que fica aberta entre o navegador e o servidor. A tela pede o que precisa por essa linha, uma página de pedidos por exemplo. O servidor responde na mesma linha e também liga de volta quando algo muda, sem precisar que alguém pergunte.

O SorlaDB fala esse protocolo de fábrica. As linhas que chegam, pedidas pela tela ou enviadas pelo servidor, vão direto para uma coleção. Toda parte da interface que observa essa coleção se atualiza sozinha.

O que muda para o negócio

  • Decisões com dados atuais. Quando uma linha muda no servidor, ela muda em todas as telas abertas em instantes, não quando alguém lembra de atualizar.
  • Menos conflitos e retrabalho. As pessoas veem as mudanças umas das outras na hora, então promessas como a última unidade em estoque são feitas com base em fatos.
  • Menos desperdício que o polling. Uma conexão silenciosa substitui uma enxurrada de perguntas "tem novidade?", e o servidor só fala quando há notícia.
  • Aguenta redes de verdade. Se a conexão cai, o SorlaDB reconecta sozinho e detecta proxies que derrubam conexões paradas sem avisar. A pessoa vê "reconectando…", não uma página quebrada.

Como funciona

O navegador abre uma conexão, pede páginas conforme a pessoa rola e escuta as mudanças:

const app = new sorla();
app.useDb('sales');
app.db.createCollection('orders');

const remote = app.db.orders.connect('wss://api.example.com/ws/orders');

// Uma página para a rolagem infinita: { rows, hasMore, total }, também gravada na coleção
await remote.load({ offset: 0, limit: 200, query: { status: 'open' }, sort: { due: 1 } });

remote.on('push', ({ message }) => notify(`Pedidos alterados por outra pessoa (${message.type})`));
remote.on('close', () => (status.textContent = 'reconectando…'));

// A interface só observa a coleção; as páginas e os envios do servidor a atualizam
app.db.orders.watch({ status: 'open' }, { sort: { due: 1 } }).subscribe(render);

O protocolo é JSON simples com um type. O cliente envia { type: 'load', id, offset, limit, query, sort }, e o servidor responde { type: 'reply', id, rows, hasMore, total }. A qualquer momento o servidor pode enviar { type: 'rows', rows }, { type: 'delete', ids } ou uma lista de ops, e o SorlaDB aplica na coleção.

O servidor pode usar a mesmíssima linguagem de consulta para montar cada página. Aqui ele também usa o SorlaDB, e o MongoDB aceitaria a mesma query:

import { WebSocketServer } from 'ws';

const sockets = new WebSocketServer({ port: 3001, path: '/ws/orders' });

sockets.on('connection', (socket) => {
  socket.on('message', (raw) => {
    const message = JSON.parse(raw);
    if (message.type === 'ping') return socket.send(JSON.stringify({ type: 'pong' }));
    if (message.type === 'load') {
      const { offset, limit, query = {}, sort = {} } = message;
      const rows = db.orders.find(query).sort(sort).skip(offset).limit(limit).toArray();
      const total = db.orders.count(query);
      socket.send(JSON.stringify({ type: 'reply', id: message.id, rows, total, hasMore: offset + rows.length < total }));
    }
  });
});

// Sempre que um pedido muda, em qualquer lugar, todas as telas abertas ficam sabendo
export function broadcast(order) {
  const message = JSON.stringify({ type: 'rows', rows: [order] });
  for (const client of sockets.clients) client.send(message);
}

Os detalhes que deixam isso pronto para produção já vêm prontos:

  • Pedidos feitos enquanto a conexão abre ficam numa fila e são enviados quando ela abre.
  • Depois de uma queda, o SorlaDB reconecta com pausas crescentes: 500 ms, 1 s, 2 s… até 30 s.
  • Um heartbeat a cada 25 segundos detecta conexões que um proxy fechou sem avisar ninguém.
  • Respostas que nunca chegam expiram em 30 segundos, em vez de ficarem penduradas para sempre.

Num produto de verdade: Intelligent Grid

O Intelligent Grid, o nosso grid de dados no estilo Excel feito sobre o SorlaDB, transforma isso numa opção:

const socket = sorla.connect('wss://api.example.com/ws/orders');
const grid = new IntelligentGrid('#orders', { idField: 'id', columns, append: { pageSize: 100, socket } });

As páginas carregam conforme a pessoa rola, com a ordenação e os filtros enviados ao servidor. Quando outra pessoa altera uma linha, ela se atualiza no lugar. E se a pessoa alterou um campo daquela linha e ainda não salvou, o valor dela é mantido: um trabalho não salvo nunca é sobrescrito pela mudança de outra pessoa.

WebSocket ou Server-Sent Events?

Eles resolvem problemas diferentes, e muitos produtos usam os dois:

Você precisa de Use
uma página de dados de vez em quando um fetch simples
uma base grande de uma vez, com progresso e retomada Server-Sent Events
páginas sob demanda e mudanças ao vivo de outras pessoas WebSocket

A conclusão para quem lidera

Toda tela compartilhada é uma promessa de que o que as pessoas veem é verdade. Sem atualização ao vivo, essa promessa vence alguns minutos depois de a página carregar, e o custo aparece como conflitos, retrabalho e pedidos de desculpas a clientes. Uma conexão WebSocket mantém a promessa enquanto a tela estiver aberta, gastando menos servidor do que perguntar de novo e de novo.

Se a sua equipe tem uma tela em que as pessoas dizem "atualiza antes de confiar", é por ela que vale começar. O guia de sincronização com o servidor tem o protocolo completo, e o SorlaDB é open source no GitHub.

Continue lendo