Pontos Principais
-
Mundialmente, há um apetite crescente por dados fornecidos em tempo real. Como produtores e consumidores estão cada vez mais interessados em experiências mais rápidas e transações de dados instantâneas, estamos testemunhando o surgimento da API de tempo real;
-
Quando se trata de APIs baseadas em eventos, os engenheiros podem escolher entre vários protocolos diferentes. As opções incluem, webhook simples, o WebSub mais recente, os protocolos abertos mais utilizados, como webSockets, MQTT ou SSE e, protocolos de streaming, como o Kafka. Além de escolher o protocolo, também precisam pensar em modelos de assinatura, ou seja, se serão iniciados no servidor, baseados no push, ou iniciados no cliente,baseados no pull;
-
Os modelos iniciados pelo cliente são a melhor escolha no último estágio da entrega de dados aos dispositivos do usuário final. Esses dispositivos só precisam de acesso aos dados quando estão conectados e não se importam com o que acontece quando estão desconectados. Devido a este fato, a complexidade do produtor é reduzida, pois o lado do servidor não precisa armazenar qualquer tipo de estado;
-
No caso do streaming de dados escalar, os engenheiros devem adotar um modelo iniciado pelo servidor. A responsabilidade de fragmentar os dados em várias conexões e as gerenciar é do produtor e, exceto pelo uso potencial de um balanceador de carga no cliente, as coisas são simples para o lado do consumidor;
-
Para realmente se beneficiar do poder dos dados em tempo real, toda a pilha de tecnologia precisa ser orientada a eventos. Talvez precisamos falar mais sobre as arquiteturas orientadas a eventos e menos sobre as APIs orientadas a eventos.
Mundialmente, há um apetite crescente por dados fornecidos em tempo real. Como tanto os produtores quanto os consumidores estão cada vez mais interessados em experiências rápidas e transações de dados instantâneas, estamos testemunhando o surgimento das APIs de tempo real.
Esse novo tipo de API orientada a eventos é adequado para uma ampla variedade de casos de uso. Pode ser usado para potencializar funcionalidades e tecnologias em tempo real, como bate-papo, alertas, notificações ou até mesmo dispositivos da IoT. As APIs de tempo real podem inclusive ser usadas para transmitir grandes volumes de dados entre diferentes empresas ou diferentes componentes de um sistema..
Este artigo começa explorando as diferenças fundamentais entre o modelo REST e as APIs de tempo real. A seguir, falaremos de alguns dos muitos desafios e considerações de engenharia envolvidos na construção de um ecossistema orientado a eventos que seja confiável e escalável, como a escolha do protocolo de comunicação e o modelo de assinatura corretos, gerenciamento de clientes e a complexidade do lado do servidor ou o dimensionamento para oferecer suporte ao fluxo de dados de alto volume.
O que exatamente é uma API de tempo real?
Normalmente, quando falamos sobre dados sendo entregues em tempo real, pensamos na velocidade. Por essa lógica, pode-se supor que o simples fato de melhorar as APIs REST, para serem mais responsivas e capazes de executar operações em tempo real, ou o mais próximo disso possível, as torna APIs de tempo real. No entanto, isso é apenas uma melhoria de uma condição existente, não uma mudança fundamental. Só porque uma API REST tradicional pode fornecer dados em tempo real, isso não a torna uma API de tempo real.
A premissa básica em torno das APIs em tempo real é que elas são orientadas por eventos. De acordo com o padrão de projeto orientado a eventos, um sistema deve reagir ou responder aos eventos conforme acontecem. Vários tipos de APIs podem ser considerados orientados a eventos, conforme ilustrado abaixo.
Família das API de tempo real. Fonte: Ably
Streaming, Pub/Sub e Push são padrões que podem implementar com sucesso uma arquitetura orientada a eventos, o que faz com que todos fiquem debaixo do guarda-chuva das APIs orientadas a eventos.
Ao contrário do popular modelo de request-response das APIs REST, as APIs baseadas em eventos seguem um modelo de comunicação assíncrona. Uma arquitetura orientada a eventos consiste nos seguintes componentes principais:
- Produtores de eventos - Enviam dados aos canais, sempre que um evento ocorre;
- Canais - Enviam os dados recebidos dos produtores de evento para os consumidores do evento;
- Consumidores de eventos - Assinam os canais e consomem os dados.
Vejamos um exemplo fictício e simples para entender melhor como os componentes interagem. Digamos que temos um aplicativo de futebol que usa um fluxo de dados para fornecer atualizações em tempo real para os usuários finais sempre que algo relevante acontece no campo. Se um gol é marcado, o evento é enviado a um canal. Quando um consumidor usa o aplicativo, ele se conecta ao respectivo canal, que então envia o evento para o dispositivo do cliente.
Observe que, em uma arquitetura orientada a eventos, produtores e consumidores estão separados. Os componentes desempenham as tarefas de forma independente e não têm conhecimento uns dos outros. Essa separação de interesses permite o dimensionamento da forma mais confiável de um sistema em tempo real, podendo evitar que possíveis problemas com um dos componentes afetem os demais.
Em comparação com o REST, as APIs orientadas a eventos invertem a complexidade e colocam mais responsabilidade sobre os ombros dos produtores do que dos consumidores.
REST vs orientado a eventos: Inversão de complexidade. Fonte: Ably
Essa inversão de complexidade está relacionada ao conceito do projeto das APIs baseadas em eventos. Enquanto em um paradigma REST, o consumidor é sempre responsável por manter o estado e sempre acionar as solicitações para obter atualizações, em um sistema orientado a eventos, o produtor é responsável por manter o estado e enviar atualizações para o consumidor.
Considerações sobre a arquitetura orientada a eventos.
Construir uma arquitetura orientada a eventos confiável não é uma tarefa fácil. Há uma gama de desafios de engenharia e decisões à serem tomadas. Entre eles, a fragmentação do protocolo e a escolha do modelo de assinatura correto, iniciado pelo cliente ou servidor, para o caso de uso específico são algumas das coisas mais urgentes a se considerar.
Embora todas as APIs REST tradicionais usam o HTTP como camada de transporte e protocolo, a situação é muito mais complexa quando se trata de APIs orientadas a eventos. Existem vários protocolos diferentes e as opções incluem um simples webhook, o WebSub mais recente, os protocolos abertos mais utilizados, como WebSockets, MQTT ou SSE, ou até mesmo protocolos de streaming, como o Kafka.
Essa diversidade pode ser uma faca de dois gumes, por um lado, não existe restrição a apenas um protocolo, por outro, é necessário selecionar o melhor para o cenário que estamos trabalhando, fato que adiciona uma complexidade a mais na engenharia.
Além de escolher um protocolo, é necessário pensar nos modelos de assinatura: Iniciados no servidor,baseados em push, ou os iniciados no cliente, baseados em pull. Observe que alguns protocolos podem ser usados em ambos modelos, enquanto alguns protocolos suportam apenas uma das duas abordagens de assinatura. Claro, isso traz ainda mais complexidade de engenharia para a mesa de discussões do projeto.q
Em um modelo iniciado pelo cliente, o consumidor é responsável por conectar e assinar o stream de dados orientado a eventos. Este modelo é mais simples da perspectiva do produtor: Se nenhum consumidor estiver inscrito no stream, então não terá trabalho a ser feito e depende dos clientes para decidir quando se reconectar. Além disso, as complexidades em torno da manutenção do estado também são tratadas pelos consumidores. Extremamente popular e eficaz, mesmo quando grandes volumes de dados estão envolvidos, os WebSockets representam o exemplo mais comum de um protocolo iniciado pelo cliente..
Em contraste, com uma abordagem iniciada pelo servidor, o produtor é responsável por enviar os dados aos consumidores sempre que ocorrer um evento. Esse modelo geralmente é preferível para os consumidores, especialmente quando o volume de dados aumenta, pois não são responsáveis por manter nenhum estado, afinal, essa responsabilidade é do produtor. O webhook comum, que é ótimo para enviar atualizações pouco frequentes e de baixa latência, é o exemplo mais tradicional de um protocolo iniciado pelo servidor..
Agora vamos nos aprofundar em mais detalhes e explorar os pontos fortes, fracos e as complexidades de engenharia das assinaturas iniciadas pelo cliente e pelo servidor.
Modelos iniciados pelo cliente em comparação aos modelos iniciados pelo servidor. Desafios e casos de uso
Os modelos iniciados pelo cliente são a melhor escolha para a entrega dos dados aos dispositivos do usuário final. Esses dispositivos só precisam de acesso aos dados quando estão conectados e não se importam com o que acontece quando estão desconectados. Devido a isso, a complexidade do produtor é reduzida, pois o lado do servidor não precisa armazenar qualquer tipo de estado. A complexidade é mantida em baixo nível, mesmo no lado do consumidor, pois geralmente, tudo o que os dispositivos dos clientes precisa fazer é se conectar e se inscrever nos canais que desejam para a "ouvir" as mensagens.
Existem vários protocolos iniciados pelo cliente , os mais utilizados e eficientes são:
- WebSocket. Fornece um canal de comunicação full-duplex que atua sob uma única conexão TCP. Sobrecarga muito menor do que as alternativas half-duplex, como a pesquisa HTTP. Ótima opção para indicadores financeiros, aplicativos baseados em localização e soluções de chat;
- MQTT. O protocolo go-to para streaming de dados entre dispositivos com recursos limitados de CPU e/ou de bateria e redes com pouca banda ou custosa , estabilidade imprevisível ou alta latência. Ótimo para a IoT;
- SSE. Protocolo aberto, leve e somente de assinatura, para streaming de dados orientados a eventos. Ideal para assinar feeds de dados, como atualizações de esportes ao vivo.
Entre eles, o WebSocket é indiscutivelmente o protocolo mais usado. Existem até alguns protocolos proprietários e soluções abertas que são construídos em cima de WebSockets brutos, como o Socket.io. São geralmente leves e possuem suporte de várias plataformas de desenvolvimento e linguagens de programação. Isso os torna ideais para entrega de dados B2C.
Vejamos uma situação de aplicação desse protocolo na prática para demonstrar como as soluções baseadas no WebSocket podem ser usadas para alimentar um sistema orientado a eventos iniciado pelo cliente. A Tennis Australia, o órgão regulador do Tênis na Austrália, queria uma solução que lhes permitisse transmitir os eventos, atualizações e comentários de uma partida em tempo real para fãs deste esporte que navegassem no site do Australian Open. A organizadora não poderia prever quantos dispositivos poderiam se inscrever para receber as atualizações a qualquer momento, nem onde esses dispositivos poderiam estar localizados no mundo. Além disso, os dispositivos do cliente geralmente são imprevisíveis, podem se conectar e desconectar a qualquer momento.
Devido a essas restrições, um modelo iniciado pelo cliente onde um dispositivo abriria uma conexão sempre que quisesse se inscrever para receber atualizações era perfeito para este caso de uso. Porém, como milhões de dispositivos podem se conectar ao mesmo tempo, não seria escalonável ter um relacionamento 1:1 com cada dispositivo cliente. A Tennis Australia estava interessada em manter a complexidade de engenharia no mínimo possível, por isso, queriam publicar uma mensagem sempre que houvesse uma atualização e distribuí-la para todos os dispositivos conectados por meio de um message broker.
No final, ao invés de construir a própria solução proprietária, a Tennis Australia optou por usar o Ably como message broker. Isso possibilitou a organizadora manter as coisas muito simples do seu lado, apenas publicar uma mensagem para o Ably sempre que houvesse uma atualização de pontuação. A mensagem é semelhante a esta:
var ably = new Ably.Realtime('API_KEY');
var channel = ably.channels.get('tennis-score-updates');
// Publicando a mensagem para o canal tennis-score-updates
channel.publish('score', 'Game Point!');
O Ably então, distribui essa mensagem para todos os dispositivos clientes conectados usando WebSockets, com a abordagem pub/sub, enquanto lida com a maior parte da complexidade de engenharia do lado do produtor, como rotatividade de conexão, backpressure ou fan-out de mensagem.
As coisas também são simples para os consumidores. Tudo que um dispositivo cliente precisa fazer é abrir uma conexão WebSocket e se inscrever para receber atualizações:
// Inscrevendo para receber mensagem no canal
channel.subscribe('score', function(message) {
alert(message.data);
});
Tradicionalmente, os desenvolvedores usam o modelo iniciado pelo cliente para construir aplicativos para os usuários finais. É uma escolha sensata, pois protocolos como WebSockets, MQTT ou SSE podem ser usados com sucesso para transmitir atualizações frequentes para um grande número de usuários, conforme demonstrado pelo exemplo da Tennis Australia. No entanto, é imprudente pensar que os modelos iniciados pelo cliente escalam bem quando estamos tratando de um fluxos de dados de alto rendimento, como cenários onde as empresas trocam grandes volumes de dados ou quando uma empresa está transmitindo informações de um sistema para outro.
Nesses casos, não é prático transmitir todos os dados em uma única conexão iniciada pelo consumidor, onde um servidor é o produtor e outro é o consumidor. Frequentemente, para gerenciar a abundância de dados, o consumidor precisa fragmentá-los em vários nós. Mas, ao fazer isso, o consumidor também precisa descobrir como distribuir esses fluxos de dados menores em várias conexões e lidar com outros desafios técnicos igualmente complexos, como por exemplo, a tolerância a falhas.
Usaremos um exemplo para ilustrar melhor algumas das complexidades do lado do consumidor. Suponhamos que existam dois servidores (A e B) que estão consumindo dois streaming de dados. Vamos imaginar que o servidor A falhe. Como o servidor B sabe que precisa de trabalho adicional? Como ele sabe onde o servidor A parou? Este é apenas um exemplo básico, mas imagine como seria difícil gerenciar centenas de servidores consumindo centenas de streaming de dados. Como regra, quando existe muita complexidade, a responsabilidade de gerenciar isso é do produtor, a ingestão de dados deve ser tão simples quanto possível para o consumidor.
É por isso que, no caso de streaming de dados em escala, é preferível adotar um modelo iniciado pelo servidor. Dessa forma, a responsabilidade de fragmentar os dados em várias conexões e gerenciá-las fica com o produtor. Enquanto no lado do consumidor as implementações são mais simples, normalmente teriam que usar um balanceador de carga para distribuir os fluxos de dados de entrada para os nós disponíveis para consumo, mas isso é tão complexo quanto podemos imaginar.
Os webhooks são frequentemente usados em modelos iniciados pelos servidores. É um padrão muito utilizado porque é simples e eficaz. No lado do consumidor, existe um balanceador de carga que recebe solicitações do webhook e as distribui aos servidores para processamento. No entanto, os webhooks se tornam menos eficazes à medida que o volume de dados aumenta. Por serem baseados em HTTP, há uma sobrecarga com cada evento (mensagem) uma vez que cada um dispara uma nova solicitação. Além disso, os webhooks não oferecem garantias de integridade ou de ordem de mensagem.
É por isso que, para os streaming de dados em escala, normalmente devemos usar um protocolo de streaming como AMQP, Kafka ou ActiveMQ, para citar apenas alguns. Esses protocolos geralmente têm sobrecargas muito mais baixas por mensagem e fornecem garantias de ordem e integridade, podendo inclusive fornecer benefícios adicionais como idempotência. Por último, mas não menos importante, os protocolos de streaming permitem fragmentação de dados antes de efetivamente transmiti-los aos consumidores.
É hora de olhar para uma implementação real de um modelo iniciado pelo servidor. O HubSpot é uma conhecida do desenvolvedor de software de marketing, vendas e atendimento ao cliente. A ferramenta fornece um serviço de chat que permite a comunicação entre os usuários finais. A empresa também está interessada em transmitir todos os dados desse serviço para outros serviços da HubSpot para processamento contínuo e armazenamento persistente. Usar uma assinatura iniciada pelo cliente para transmitir com sucesso grandes volumes de dados para os barramentos de mensagens internos não é uma opção. Para que isso aconteça, a HubSpot precisaria saber quais canais estão ativos em qualquer ponto no tempo, para extrair os dados deles..
Para contornar os desafios complexos de engenharia, a HubSpot decidiu usar o Ably como um message broker para implementar a comunicação por chat entre os usuários finais. Além disso, o Ably usa um modelo iniciado por servidor para enviar dados de bate-papo para o Amazon Kinesis, que também é utilizado como o componente de processamento de dados do barramento de mensagens da HubSpot.
Representação em alto nível da arquitetura do chat da HubSpot. Font: Ably
A complexidade do consumidor é reduzida ao mínimo e o HubSpot precisa apenas expor um endpoint do Kinesis que o Ably se encarrega de transmitir os dados do bate-papo para quantas conexões forem necessárias.
Uma breve conclusão
Este artigo oferece uma amostra do que são as APIs de tempo real e tem como objetivo ajudar os leitores a navegar em algumas das muitas complexidades e desafios de construir uma arquitetura em tempo real eficaz. É ingênuo pensar que, para obter uma API de tempo real, basta melhorar uma API REST tradicional para ser mais rápida e responsiva. O conceito de tempo real nas APIs significa muito mais do que isso.
Por padrão, as APIs em tempo real são orientadas por eventos. Esta é uma mudança fundamental no padrão de request-response dos serviços RESTful. No paradigma orientado a eventos, a responsabilidade é invertida e o núcleo das complexidades de engenharia fica com o produtor de dados, com o objetivo de tornar a ingestão o mais fácil possível para o consumidor.
Mas ter uma API em tempo real não é suficiente, afinal, isso não é uma solução para um problema. Para realmente colher benefícios com os dados em tempo real, todo o arcabouço de tecnologia precisa ser orientada a eventos. Talvez devêssemos começar a falar mais sobre as arquiteturas orientadas a eventos ao invés de falar sobre as APIs orientadas a eventos. Afinal, enviar grandes volumes de dados para um endpoint (consulte o exemplo do HubSpot acima para obter detalhes) pode ser classificado como uma API?
Sobre o Autor
Matthew O'Riordan é co-fundador técnico da Ably, uma plataforma global de mensagens em tempo real baseada em nuvem e que fornece APIs usadas por milhares de desenvolvedores e empresas. É desenvolvedor há mais de 20 anos e começou a trabalhar em projetos comerciais de internet em meados dos anos 90, quando o Internet Explorer 3 e o Netscape ainda brigavam pelo mesmo marketshare. Embora goste de programar, os desafios que enfrenta como empresário ao iniciar e expandir as empresas é o que te motiva. Matthew já iniciou duas empresas de tecnologia anteriormente, saindo com sucesso de ambas.