Como arquitetar um aplicativo da web baseado em websockets pesado em tempo real?

15

No processo de desenvolvimento de um aplicativo de página única em tempo real, adotei progressivamente websockets para capacitar meus usuários com dados atualizados. Durante essa fase, fiquei triste ao perceber que estava destruindo demais a estrutura do meu aplicativo e não consegui encontrar uma solução para esse fenômeno.

Antes de entrar em detalhes, apenas um pouco de contexto:

  • A webapp é um SPA em tempo real;
  • O Backend está em Ruby on Rails. Eventos em tempo real são empurrados pelo Ruby para uma chave Redis, em seguida, um servidor de micro nó puxa para trás e envia para Socket.Io;
  • O Frontend está no AngularJS e se conecta diretamente ao servidor socket.io no Node.

No lado do servidor, antes do tempo real, eu tinha uma clara separação baseada em controlador / modelo dos recursos, com o processamento anexado a cada um. Este projeto MVC clássico foi completamente destruído, ou pelo menos ignorado, logo quando comecei a enviar material via websockets para meus usuários. Agora tenho um single pipe, no qual todo o meu aplicativo flui para mais ou menos dados estruturados . E eu acho isso estressante.

No front-end, a principal preocupação é a duplicação da lógica de negócios. Quando o usuário carrega a página, eu tenho que carregar meus modelos através de chamadas AJAX clássicas. Mas eu também tenho que lidar com inundações de dados em tempo real, e eu me vejo duplicando muito da minha lógica de negócios do lado do cliente para manter a consistência dos meus modelos do lado do cliente.

Depois de algumas pesquisas, não consigo encontrar posts, artigos, livros ou qualquer coisa que possa dar conselhos sobre como se pode e deve projetar a arquitetura de uma webapp moderna com alguns tópicos específicos em mente:

  • Como estruturar os dados que são enviados do servidor para o usuário?
    • Devo enviar apenas eventos como "este recurso foi atualizado e você deve recarregá-lo por meio de uma chamada AJAX" ou enviar os dados atualizados e substituir os dados anteriores carregados por meio de chamadas AJAX iniciais?
    • Como definir um esqueleto coerente e escalável para os dados enviados? isto é uma mensagem de atualização do modelo ou "houve um erro com a mensagem blahblahblah"
  • Como não enviar dados sobre tudo de qualquer lugar no back-end?
  • Como reduzir a duplicação da lógica de negócios no servidor e no cliente?
por Philippe Durix 05.08.2016 / 11:17
fonte

2 respostas

9

How to structure the data that is sent from the server to the user?

Use o padrão de mensagens . Bem, você já está usando um protocolo de mensagens, mas quero dizer estruturar as alterações como mensagens ... especificamente eventos. Quando o lado do servidor é alterado, isso resulta em eventos de negócios. Em seu cenário, suas visualizações do cliente estão interessadas nesses eventos. Os eventos devem conter todos os dados relevantes para essa alteração (não necessariamente todos os dados de visualização). A página do cliente deve atualizar as partes de visão que está mantendo com os dados do evento.

Por exemplo, se você estivesse atualizando um ticker de ações e AAPL alterado, você não iria querer empurrar todos os preços das ações para baixo ou até mesmo todos os dados sobre AAPL (nome, descrição, etc). Você só enviaria o AAPL, o delta e o novo preço. No cliente, você atualizaria apenas o preço da ação na exibição.

Should I only send events like "this resource has been updated and you should reload it via an AJAX call" or push the updated data and replace previous data loaded via initial AJAX calls?

Eu diria que nem. Se você estiver enviando o evento, vá em frente e envie dados relevantes com ele (não os dados do objeto inteiro). Dê um nome para o tipo de evento que é. (A nomeação e os dados relevantes para esse evento estão além do escopo do funcionamento mecânico do sistema. Isso tem mais a ver com a modelagem da lógica de negócios.) Seus atualizadores de visualização precisam saber como converter cada evento específico em uma alteração precisa da visualização (ou seja, atualizar apenas o que mudou).

How to define a coherent and scalable skeleton to data sent? is this a model update message or "there was an error with blahblahblah" message

Eu diria que esta é uma pergunta grande e aberta que deve ser dividida em várias outras questões e postada separadamente.

Em geral, no entanto, seu sistema de back-end deve criar e enviar eventos para eventos importantes para sua empresa. Aqueles poderiam vir de feeds externos ou de atividades no próprio back-end.

How not to send data about everything from anywhere in the backend?

Use o padrão de publicação / assinatura . Quando seu SPA carrega uma nova página que está interessada em receber atualizações em tempo real, a página deve assinar apenas os eventos que pode usar e chamar a lógica de atualização de visualização à medida que esses eventos entram. o servidor para reduzir a carga da rede. Bibliotecas existem para o Websocket pub / sub, mas não tenho certeza do que elas estão no ecossistema do Rails.

How to reduce the business logic duplication both on the server and the client side?

Parece que você está precisando atualizar os dados da visualização no cliente e no servidor. Meu palpite é que você precisa dos dados de visualização do lado do servidor para que você tenha um instantâneo para iniciar o cliente em tempo real. Sendo que existem duas linguagens / plataformas envolvidas (Ruby e Javascript), a lógica de atualização da visão terá que ser escrita em ambas. Além de transpilar (que tem seus próprios problemas), não vejo uma maneira de contornar isso.

Ponto técnico: A manipulação de dados (atualização de visualizações) não é uma lógica de negócios. Se você quer dizer validação de caso de uso, isso parece inevitável, já que as validações do cliente são necessárias para uma boa experiência do usuário, mas não podem ser confiadas pelo servidor.

Veja como eu vejo uma coisa assim bem estruturada.

Exibições do cliente:

  • Solicita um instantâneo de visualização e o último número de evento visto
    • Isso pré-populará a visualização para que o cliente não precise criar do zero.
    • Poderia ser sobre HTTP GET para simplicidade
  • Faz uma conexão de websocket e inscreve-se em eventos específicos, começando pelo último número de evento da vista.
  • Recebe eventos por meio do websocket e atualiza sua visualização com base no tipo / dados do evento.

Comandos do cliente:

  • Solicitar alteração de dados (HTTP PUT / POST / DELETE)
    • A resposta é apenas sucesso ou falha + erro
    • (O (s) evento (s) gerado (s) pela alteração ocorrerá via websocket e acionará uma atualização de exibição.

O lado do servidor pode ser dividido em vários componentes com responsabilidades limitadas. Um que apenas processa as solicitações recebidas e cria eventos. Outro pode gerenciar assinaturas de clientes, ouvir eventos (por exemplo, em processo) e encaminhar eventos apropriados para os assinantes. Você poderia ter um terceiro que ouvisse eventos e atualizasse as exibições do lado do servidor - talvez isso aconteça até que os assinantes recebam os eventos.

O que eu descrevi é uma forma de CQRS + Mensagens e uma estratégia típica para resolver o tipo de problemas que você está enfrentando.

Eu não trouxe Sourcing de Eventos para esta descrição, pois não tenho certeza se é algo que você quer levar em ou se você precisar, necessariamente. Mas é um padrão relacionado.

    
por 05.08.2016 / 20:07
fonte
4

Depois de alguns meses de trabalho no back-end principalmente, eu pude usar alguns dos conselhos aqui para resolver os problemas que a plataforma estava enfrentando.

O principal objetivo ao repensar o backend era ficar o mais difícil possível com o CRUD. Todas as ações, mensagens e solicitações espalhadas por várias rotas foram reagrupadas em recursos criados, atualizados, lidos ou excluídos . Parece óbvio agora, mas esta foi uma maneira muito difícil de se pensar cuidadosamente.

Depois que tudo foi organizado em recursos, pude anexar mensagens em tempo real a modelos.

  • A criação aciona uma mensagem com novo recurso de furo;
  • A atualização aciona uma mensagem apenas com os atributos atualizados (mais o UUID);
  • A exclusão aciona uma mensagem de exclusão.

Na API Rest, todos os métodos create, update, delete geram uma resposta somente head, o código HTTP informando sobre o sucesso ou falha e os dados reais sendo enviados por websockets.

No front-end, cada recurso é manipulado por um componente específico que os carrega por meio de HTTP na inicialização, depois se inscreve para atualizações e mantém seu estado ao longo do tempo. As visualizações se ligam a esses componentes para exibir recursos e executar ações nesses recursos através dos mesmos componentes.

Eu achei o CQRS + Messaging e Event Sourcing muito interessante, mas senti que foi um pouco complicado demais para o meu problema e talvez seja mais adaptado para aplicações intensivas, onde o comprometimento de dados em um banco de dados centralizado é um luxo caro. Mas com certeza vou ter em mente essa abordagem.

Neste caso, o aplicativo terá poucos clientes simultâneos e eu levei a parte de confiar muito no banco de dados. Os modelos que mais mudam são armazenados no Redis, o qual confio para lidar com algumas centenas de atualizações por segundo.

    
por 18.01.2017 / 12:10
fonte