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 muito 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:
- O webapp é um SPA em tempo real;
- O back-end está em Ruby on Rails. Os eventos em tempo real são enviados por Ruby a uma chave Redis, e um servidor de micro nós puxa isso para trás e o envia para Socket.Io;
- O Frontend está no AngularJS e se conecta diretamente ao servidor socket.io no Nó.
No lado do servidor, antes do tempo real, eu tinha uma clara separação de recursos baseada em controlador / modelo, com o processamento anexado a cada um. Esse design clássico do MVC foi completamente fragmentado, ou pelo menos ignorado, logo quando comecei a enviar coisas via websockets para meus usuários. Agora tenho um único canal no qual todo o meu aplicativo flui para dados mais ou menos estruturados . E acho 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, tenho que carregar meus modelos através de chamadas clássicas de AJAX. Mas também preciso lidar com a inundação de dados em tempo real e me pego duplicando grande parte da minha lógica de negócios do lado do cliente para manter a consistência dos meus modelos do lado do cliente.
Após algumas pesquisas, não consigo encontrar bons posts, artigos, livros ou o que quer que dê conselhos sobre como se pode e deve projetar a arquitetura de um webapp moderno 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 por push os dados atualizados e substituir os dados anteriores carregados pelas chamadas AJAX iniciais?
- Como definir um esqueleto coerente e escalável para os dados enviados? é uma mensagem de atualização de modelo ou "houve um erro com blahblahblah"
- Como não enviar dados sobre tudo de qualquer lugar do back-end?
- Como reduzir a duplicação da lógica de negócios no servidor e no cliente?
fonte
Respostas:
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 muda, isso resulta em eventos de negócios. No seu cenário, as visualizações do seu cliente estão interessadas nesses eventos. Os eventos devem conter todos os dados relevantes para essa alteração (nem sempre todos os dados da visualização). A página do cliente deve atualizar as partes da exibição que está mantendo com os dados do evento.
Por exemplo, se você estava atualizando um ticker de ações e a AAPL foi alterada, não desejaria reduzir todos os preços das ações ou mesmo todos os dados sobre a AAPL (nome, descrição, etc.). Você apenas enviaria AAPL, delta e novo preço. No cliente, você atualizaria apenas o preço das ações na exibição.
Eu também não diria. 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 quais dados são 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 exibição precisam saber como converter cada evento específico em uma alteração precisa da visualização (ou seja, atualize apenas o que mudou).
Eu diria que essa é uma pergunta grande e aberta que deve ser dividida em várias outras perguntas e postada separadamente.
Em geral, porém, seu sistema de back-end deve criar e despachar eventos para acontecimentos importantes para o seu negócio. Eles podem vir de feeds externos ou de atividades no próprio backend.
Use o padrão de publicação / assinatura . Quando o seu SPA carrega uma nova página que está interessada em receber atualizações em tempo real, a página deve se inscrever apenas nos eventos que pode usar e chamar a lógica de atualização de exibição à medida que esses eventos chegarem. Você provavelmente precisará de lógica pub / sub em o servidor para reduzir a carga da rede. Existem bibliotecas para Websocket pub / sub, mas não tenho certeza do que elas estão no ecossistema Rails.
Parece que você está tendo que atualizar os dados da visualização no cliente e no servidor. Meu palpite é que você precisa dos dados da exibição do servidor para ter 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 visualização precisará 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 exibição) não é lógica de negócios. Se você quer dizer validação de caso de uso, isso parece inevitável, pois as validações do cliente são necessárias para uma boa experiência do usuário, mas, no final das contas, o servidor não pode confiar nele.
Aqui está como eu vejo uma coisa assim estruturada bem.
Visualizações do cliente:
Comandos do cliente:
O lado do servidor pode realmente ser dividido em vários componentes com responsabilidades limitadas. Um que apenas processa as solicitações recebidas e cria eventos. Outro poderia gerenciar assinaturas de clientes, ouvir eventos (por exemplo, em processo) e encaminhar eventos apropriados aos assinantes. Você pode ter um terço que ouça eventos e atualize as visualizações do servidor - talvez isso ocorra antes que os assinantes recebam os eventos.
O que descrevi é uma forma de CQRS + Messaging e uma estratégia típica para resolver os tipos de problemas que você está enfrentando.
Não trouxe o Event Sourcing para essa descrição, pois não tenho certeza se é algo que você deseja assumir ou se precisa necessariamente. Mas é um padrão relacionado.
fonte
Depois de alguns meses de trabalho principalmente no back-end, pude usar alguns dos conselhos aqui para resolver os problemas que a plataforma estava enfrentando.
O principal objetivo ao repensar o back-end era manter o máximo possível 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 essa tem sido uma maneira muito difícil de pensar para aplicar com cuidado.
Depois que tudo foi organizado em recursos, eu pude anexar mensagens em tempo real aos modelos.
Na API Rest, todos os métodos de criação, atualização e exclusão geram uma resposta exclusiva, o código HTTP informando o sucesso ou a falha e os dados reais sendo enviados pelos soquetes da web.
No front-end, cada recurso é tratado por um componente específico que os carrega através do HTTP na inicialização, depois assina atualizações e mantém seu estado ao longo do tempo. As visualizações são vinculadas a esses componentes para exibir recursos e executar ações nesses recursos através dos mesmos componentes.
Achei as leituras do CQRS + Messaging e Event Sourcing muito interessantes, mas achei que era um pouco complicado demais para o meu problema e talvez seja mais adaptado a aplicativos intensivos em que o comprometimento de dados em um banco de dados centralizado é um luxo caro. Mas definitivamente vou ter em mente essa abordagem.
Nesse caso, o aplicativo terá poucos clientes simultâneos e assumi a responsabilidade de confiar muito no banco de dados. Os modelos mais alterados são armazenados no Redis, que eu confio para lidar com algumas centenas de atualizações por segundo.
fonte