Full text
Universidade do Minho Escola de Engenharia Departamento de Informática João Paulo Oliveira de Andrade Marques Middleware para integração com Sistemas de Queues dezembro de 2021
Universidade do Minho Escola de Engenharia Departamento de Informática João Paulo Oliveira de Andrade Marques Middleware para integração com Sistemas de Queues Dissertação de Mestrado Mestrado Integrado em Engenharia Informática Trabalho efetuado sobre a orientação do Professor Doutor António Manuel Nestor Ribeiro dezembro de 2021
DIREITOS DE AUTOR E CONDIÇÕES DE UTILIZAÇÃO DO TRABALHO POR TERCEIROS Este é um trabalho académico que pode ser utilizado por terceiros desde que respeitadas as regras e boas práticas internacionalmente aceites, no que concerne aos direitos de autor e direitos conexos. Assim, o presente trabalho pode ser utilizado nos termos previstos na licença abaixo indicada. Caso o utilizador necessite de permissão para poder fazer uso do trabalho em condições não previstas no licenciamento indicado, deverá contactar o autor, através do RepositoriUM da Universidade do Minho. Licença concedida aos utilizadores deste trabalho Creative Commons Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ ii
Agradecimentos Antes de mais quero agradecer a toda a minha família por me terem apoiado sempre, mas com especial atenção à minha namorada, aos meus pais e irmã, e aos pais da minha namorada por me terem sempre incentivado e apoiado em todas as minhas decisões, tanto na escolha do curso, como na escolha do tema da dissertação. A eles, um muitíssimo obrigado por tudo o que me proporcionaram ao longo desta caminhada que, sem todos eles, talvez não teria sido possível. A todos os meus professores, um enorme obrigado por todo o conhecimento que partilharam comigo, por tudo o que me ensinaram, mas em especial ao meu orientador, Professor António Manuel Nestor Ribeiro, que me apresentou este tema e que em todas as reuniões partilhou comigo as suas ideias e opiniões de forma a melhorar as funcionalidades do Middleware desenvolvido, bem como pelos momentos de conversa sobre vários outros assuntos, que de certa forma facilitaram bastante todo o trabalho. Deixo também o agradecimento a todos os meus colegas que me acompanharam ao longo deste percurso e com os quais partilhei muitos projetos, principalmente ao José Pereira e ao Ricardo Petronilho, com os quais discuti algumas tecnologias a utilizar durante a realização desta dissertação, por toda a entreajuda e momentos de convívio. Assim me despeço desta que foi a minha segunda casa durante os últimos 5 anos. A todos um grande obrigado por me terem feito chegar até aqui. iii
DECLARAÇÃO DE INTEGRIDADE Declaro ter atuado com integridade na elaboração do presente trabalho académico e confirmo que não recorri à prática de plágio nem a qualquer forma de utilização indevida ou falsificação de informações ou resultados em nenhuma das etapas conducente à sua elaboração. Mais declaro que conheço e que respeitei o Código de Conduta Ética da Universidade do Minho. , (Localização) (Data) (João Paulo Oliveira de Andrade Marques) iv
Middleware para integração com Sistemas de Queues Resumo Com o crescente número de componentes para gestão de sistemas de queues e o crescente número de aplicações cliente a fazer uso desses mesmos componentes é necessário a criação de um Middleware para desacoplar as aplicações cliente dos sistemas de queues. Os sistemas de queues são também conhecidos com Message-Oriented Middleware (MOM). O acoplamento das aplicações cliente a esses componentes torna-as muito dependentes destes, pelo que a introdução de um Middleware faz com que a aplicação cliente fique isolada das particularidades e das tarefas de manutenção. Posto isto, fica o Middleware dependente dessas particularidades e das tarefas de manutenção. O RabbitMQ, o ActiveMQ e o Kafka são exemplos de sistemas de queues onde existe um sistema intermediário externo entre as aplicações que estão a comunicar, e o ZeroMQ que é um sistema de queues onde a própria aplicação fica como um nodo do sistema de queues, isto é, o ZeroMQ é um sistema intermediário interno. Todos estes são implementados de diferentes formas, pelo que a troca de um sistema para outro leva a uma reestruturação das aplicações que o estejam a usar, por isso estes serão estudados durante esta dissertação de forma a avaliar as suas caraterísticas, vantagens e desvantagens para realizar a sua integração no Middleware a desenvolver. OMiddleware desenvolvido desacopla as aplicações dos sistemas de queues, permitindo assim a troca de um sistema para outro sem ser necessária uma reestruturação da aplicação. Este integrou o RabbitMQ, o ActiveMQ e o Kafka por forma a ser possível realizar as operações básicas de envio e leitura de mensagens. Além destas operações é também possível reler mensagens quando seja necessário. Por forma a demonstrar e testar o Middleware ir-se-á recorrer a um caso de estudo. Palavras-chave: Comunicação Assíncrona, Middleware, Sistemas de Queues v
Middleware for integration with Queue Systems Abstract With the growing number of components for managing queue systems and the growing number of client applications making use of those same components, it is necessary to create Middleware to decouple client applications from queue systems. Queue systems are also known as Message-Oriented Middleware (MOM). The coupling of client applications to these components makes them very dependent on them, so the introduction of Middleware makes the client application isolated from particularities and maintenance tasks. That said, Middleware is dependent on these particularities and maintenance tasks. RabbitMQ, ActiveMQ and Kafka are examples of queue systems where there is an external intermediary service between the applications that are communicating, and ZeroMQ is a queue system where the application itself is a node of the queue system, that is, ZeroMQ is an internal intermediation service. All of these are implemented in different ways, so switching from one system to another leads to a restructuring of the applications that are using it, so these will be studied during this dissertation in order to evaluate its characteristics, advantages and disadvantages to perform its integration in the Middleware to be developed. The developed Middleware decouples the applications from the queue systems, thus allowing the exchange from one system to another without needing to restructure the application. This integrated RabbitMQ, ActiveMQ and Kafka in order to be able to carry out the basic operations of sending and reading messages. In addition to these operations, it is also possible to reread messages when necessary. In order to test the Middleware, a case study will be used. Keywords: Asynchronous Communication, Middleware, Queue Systems vi
Índice Lista de Figuras x Lista de Tabelas xiv Acrónimos xvi 1 Introdução 1 1.1 Objetivos........................................... 3 1.2 EstruturadoDocumento ................................... 3 2 Estado da Arte 5 2.1 ProtocoloseAPIs....................................... 5 2.1.1 Advanced Message Queue Protocol . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.1.2 JavaMessageService ................................ 7 2.1.3 Message Queue Telemetry Transport . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.1.4 Simple (Streaming) Text Orientated Messaging Protocol . . . . . . . . . . . . . . . . 10 2.1.5 Extensible Messaging and Presence Protocol . . . . . . . . . . . . . . . . . . . . . 12 2.2 Tecnologias.......................................... 13 2.2.1 RabbitMQ ...................................... 13 2.2.2 ActiveMQ ...................................... 15 2.2.3 Kafka ........................................ 17 2.2.4 ZeroMQ ....................................... 18 2.3 EstudosComparativos .................................... 21 2.3.1 RabbitMQvs.ActiveMQ................................ 21 2.3.2 RabbitMQvs.ZeroMQ ................................ 22 2.3.3 ActiveMQvs.OpenMQ................................ 24 2.3.4 Kafkavs.RabbitMQ ................................. 27 2.4 TrabalhosRelacionados.................................... 31 2.4.1 Multi-MOM...................................... 31 3 Descrição do Problema 33 4 Conceção 37 4.1 ModelodeDomínio...................................... 37 4.2 LevantamentodeRequisitos.................................. 38 4.2.1 Priorização...................................... 39 4.3 Modelação .......................................... 40 4.3.1 Diagrama de Casos de Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 vii
4.3.2 Diagrama de Componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.3.3 Decisões Arquiteturais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4.3.4 DiagramadeClasses................................. 47 5 Implementação 71 5.1 Tecnologias.......................................... 71 5.1.1 Desenvolvimento................................... 71 5.1.2 Teste ........................................ 73 5.1.3 Deployment ..................................... 74 5.2 Especificações ........................................ 75 5.2.1 Sistemas de Queues e Estratégias . . . . . . . . . . . . . . . . . . . . . . . . . . 75 5.2.2 APIRest....................................... 76 5.3 DecisõesdeImplementação.................................. 80 5.3.1 RabbitMQ ...................................... 80 5.3.2 Tempo de Persistência das Mensagens . . . . . . . . . . . . . . . . . . . . . . . . 80 5.3.3 Domain Name System (DNS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 6 Caso de Estudo 82 6.1 Demonstração ........................................ 83 7 Teste de Desempenho 90 8 Conclusão 95 8.1 TrabalhoFuturo........................................ 95 Bibliografia 97 Anexos 101 A Diagramas de Classe 101 A.1 Registration.......................................... 101 A.2 Sender............................................ 102 A.3 Receiver ........................................... 103 A.4 Discovery........................................... 104 B Especificações API 105 B.1 Registration.......................................... 105 B.1.1 Rotas ........................................ 105 B.1.2 Respostas ...................................... 107 B.1.3 Erros ........................................ 108 B.2 Middleware.......................................... 109 viii
Tabela 31: Combinação de campos semi obrigatórios para a rota 1, 5 e 7 a 9.. . . . . . . . . . . . . . . . . . . . 110 Tabela 32: Campos das rotas número 2 e 3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Tabela 33: Campos da rota número 4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Tabela 34: Campos da rota número 5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Tabela 35: Campos da rota número 6. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Tabela 36: Campos da rota número 7. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Tabela 37: Campos da rota número 8. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Tabela 38: Campos da rota número 9. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Tabela 39: Campos da rota número 10. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 Tabela 40: Combinação de campos semi obrigatórios para a rota 10 e 16 a 18. . . . . . . . . . . . . . . . . . . . 114 Tabela 41: Campos das rotas número 11 e 12. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Tabela 42: Campos da rota número 13. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Tabela 43: Campos da rota número 14. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Tabela 44: Campos da rota número 15. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Tabela 45: Campos da rota número 16. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Tabela 46: Campos da rota número 17. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Tabela 47: Campos da rota número 18. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Tabela 48: Campos da rota número 19. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Tabela 49: Campos das rotas número 20 e 21. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Tabela 50: Respostas aos pontos de entrada do serviço Middleware. . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Tabela 51: Códigos de erros do serviço Middleware. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Tabela 52: Erros por rota do serviço Middleware. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 xv
Acrónimos AMQP Advanced Message Queue Protocol API Application Program Interface ASF Apache Software Foundation BD Base de Dados CRUD Create - Read - Update - Delete DAO Data Access Object DI Dependency Injection DNS Domain Name System DTO Data Transfer Object EOL End-of-Line GCP Google Cloud Platform HTTP Hyper Text Transport Protocol HTTPS Hyper Text Transport Protocol Secure IETF Internet Engineering Task Force IM Instant Messaging IoT Internet of Things IP Internet Protocol IPSec Internet Protocol Security ISO International Organization Standardization JAAS Java Authentication and Authorization Service JID Jabber ID JMS Java Message Service JSON JavaScript Object Notation JWT JSON Web Token M2M Machine-to-Machine MOM Message-Oriented Middleware MQTT Message Queue Telemetry Transport MQTT-SN Message Queue Telemetry Transport for Sensor Network MVC Model - View - Controller MVP Minimum Viable Product OASIS Organization for the Advanced Structured Information Standards OpenPGM Open Pragmatic General Multicast P2P Peer-to-Peer xvi
PGM Pragmatic General Multicast POJO Pure Old Java Object PTP Point-to-Point Pub/Sub Publisher/Subscriber QoS Quality of Service Req/Res Request/Response RFC Request for Comment SASL Simple Authentication and Security Layer SSE Server Sent-Event SSL Secure Sockets Layer STOMP Simple (Streaming) Text Orientated Messaging Protocol TCP Transmission Control Protocol TLS Transport Layer Security TTL Time to Live UDP User Datagram Protocol UUID Universally Unique Identifier VM Virtual Machine XML Extensible Markup Language XMPP Extensible Messaging and Presence Protocol xvii
1 Introdução Com o atual crescimento do desenvolvimento de software foram aparecendo muitas aplicações, porém estas seguiam o modelo monolítico, em que uma aplicação é um componente único que faz tudo. Com o decorrer dos anos deparou-se com um problema de escalabilidade e de manutenção destas, pois sempre que era necessário realizar uma alteração era necessário reiniciar toda a aplicação. Por forma a resolver este problema foi introduzido o modelo microsserviços, sendo que este consiste em dividir um componente complexo, como uma aplicação monolítica, em vários componentes mais simples por forma a obter os mesmos resultados pretendidos. Neste modelo cada componente é responsável por realizar apenas um conjunto específico de tarefas e sempre que necessário comunicar com outro componente para a realização de uma tarefa mais complexa. Esta divisão de um componente complexo em vários componentes mais simples e a necessidade de comunicar entre eles fez surgir um novo problema. Este problema consiste na falha de comunicação entre dois destes componentes, muitas vezes devido a uma falha de rede ou então a uma falha do componente a ser contactado. Algumas destas falhas podem levar ao bloqueio de alguns componentes, porque no caso de a informação a ser enviada entre os dois componentes ser de elevada importância o componente produtor da informação vai querer garantir que consegue entregar esta e que não é perdida. Este bloqueio ocorre porque a comunicação entre os componentes é síncrona. Por forma a resolver estas falhas foram desenvolvidos sistemas de queues (filas). Estes resolvem os problemas relativos às falhas dos componentes recetores da informação, pois uma falha na rede pode sempre ocorrer, sendo que atualmente as falhas de rede são bastante reduzidas. Estes sistemas são comummente designados por sistemas intermediários ou Message-Oriented Middlewares (MOMs). Sendo os sistemas de queues um componente estes também podem falhar, porém estes sistemas foram desenvolvidos de forma a serem altamente disponíveis e resilientes. A introdução destes sistemas veio tornar a comunicação assíncrona entre os componentes que anteriormente necessitavam de comunicar, pois estes em vez comunicarem diretamente agora comunicam entre si através dos sistemas de queues. Devido à necessidade destes componentes para realizar a comunicação assíncrona foram desenvolvidos diversos. Uns com mais funcionalidades do que outros, uns seguiram protocolos, outros seguiram Application Program Interfaces (APIs) de comunicação já existentes e alguns criaram ainda o seu próprio protocolo de comunicação. Os sistemas intermediários dividem-se em dois grandes grupos, os intermediários externos ou broker-based, tal como o RabbitMQ [Dos14], ActiveMQ 5 [SBD11] e o Kafka [Gar15], e os intermediários internos ou brokerless, como o ZeroMQ [Akg13]. Os sistemas intermediários internos não possuem um componente externo para realizar a comunicação assíncrona entre os dois componentes que necessitam de comunicar, sendo os próprios componentes a comunicar diretamente, mas de forma assíncrona não levando ao bloqueio do componente produtor. 1
Figura 1: MOM broker-based. A Figura 1 representa a utilização de um sistema intermediário externo, tal como pode observar-se o componente Broker está externo às aplicações. Por outro lado na Figura 2 está representada a utilização de um sistema intermediário interno, isto é, cada componente que esteja a utilizar este sistema torna-se um nodo do mesmo, não existindo fisicamente um componente externo a estas. Figura 2: MOM brokerless. Cada um destes sistemas apresenta as suas vantagens e desvantagens, pelo que existem estudos comparativos entre estes por forma a agilizar o processo de escolha no momento de decisão sobre qual destes sistemas utilizar. São exemplos destes estudos: • N. Estrada e H. Astudillo em [EA15]: estudo de escalabilidade e desempenho entre o RabbitMQ e o ZeroMQ, um sistema intermediário externo e um sistema intermediário interno, respetivamente. • V. M. Ionescu em [Ion15]: estudo de desempenho entre o ActiveMQ e o RabbitMQ, dois sistemas intermediários externos. • A. F. Klein et al. [Kle+15]: estudo de escalabilidade, desempenho e persistência de mensagens entre o ActiveMQ e o OpenMQ1, dois sistemas intermediários externos que seguem a mesma API. • Philippe Dobblelaere e Kyumars Sheykh Esmaili em [DE17]: estudo qualitativo e quantitativo entre o RabbitMQ e o Kafka, dois sistemas intermediários externos. O objetivo desta dissertação passa pela conceção e implementação de um Middleware que integre vários destes sistemas de queues por forma a criar um nível de abstração entre os componentes que necessitam de comunicar e os sistemas de queues, acrescentado também características de valor acrescentado para uma maior experiência de utilização. Pois com a introdução dos sistemas de queues foi criado um novo problema que é a dependência que os componentes passam a ter sobre estes sistemas. Este aspeto tem sido alvo de estudo pela comunidade, tal como o trabalho de Yuri Bezerra [Bez10], em que é criada uma biblioteca para abstração das estratégias de comunicação síncrona e assíncrona para aplicações 1OpenMQ: https://javaee.github.io/openmq/Documentation.html 2
mobile. 1.1 Objetivos Nesta secção serão ilustrados os objetivos para a dissertação a realizar. De seguida encontram-se enumerados os principais objetivos delineados. 1. Efetuar um estudo sobre alguns protocolos e APIs existentes na área da comunicação e utilizados ou suportados pelos sistemas intermediários a estudar, demonstrando a sua arquitetura e principais caraterísticas. 2. Efetuar um estudo sobre os sistemas de queues já existentes, como os já acima referidos (RabbitMQ, ActiveMQ, Kafka e ZeroMQ), apontando as suas principais características, vantagens e desvantagens, demonstrando algumas comparações realizadas entre estes. 3. Fazer uma avaliação qualitativa dos sistemas de queues a utilizar. 4. Realizar o levantamento, análise e priorização de requisitos a ter em conta na conceção e implementação do Middleware. 5. Conceber e desenvolver um Middleware com caraterísticas de valor acrescentado para a integração dos vários sistemas de queues por forma a tornar as aplicações que necessitam destes menos dependentes. Este Middleware deve ainda ser escalável e permitir redundância. 6. Por forma a manter o Middleware sempre atualizado com novos sistemas de queues este deve permitir a sua extensibilidade, mantendo o Middleware atualizado sem grandes complicações. Isto sem afetar os sistemas já adicionados anteriormente e sem afetar as aplicações cliente que estejam dependentes deste Middleware. 7. Criação de um caso de estudo que faça uso deste Middleware por forma a poder demonstrar as capacidades do mesmo. 8. Realizar um teste de desempenho por forma a avaliar o desempenho do mesmo. 1.2 Estrutura do Documento A dissertação a realizar passará por um estudo sobre o estado da arte, englobando alguns protocolos de comunicação utilizados por MOMs, alguns dos sistemas anteriormente referidos, estudos comparativos sobre os mesmos e ainda trabalhados relacionados com este, sendo que este estudo servirá como base teórica para o Middleware a desenvolver. Na fase sobre o estado da arte serão abordados protocolos como Advanced Message Queue Protocol (AMQP), Message Queue Telemetry Transport (MQTT), Simple (Streaming) Text Orientated Messaging Protocol (STOMP) e Extensible Messaging and Presence Protocol (XMPP), e a API Java Message Service (JMS), de seguida serão apresentados os sistemas de queues já anteriormente referidos (RabbitMQ, ActiveMQ 5, Kafka e ZeroMQ). Após o estudo sobre o estado da arte será feita uma descrição do problema atual, sendo que neste serão 3
descritos os problemas que existem atualmente com a utilização em específico de um sistema de queues. De seguida será elaborada a conceção do sistema a desenvolver e posteriormente a sua implementação, sendo descritas as decisões tomadas e respetivas consequências das mesmas. Terminada a implementação será realizado um caso de estudo que demonstre as capacidades e funcionalidades do Middleware desenvolvido, sendo também realizados testes de desempenho sobre este. Por último serão apresentadas as conclusões obtidas durante o desenvolvimento e teste do Middleware desenvolvimento, bem como o respetivo trabalho futuro a realizar por forma a melhor o Middleware. 4
2 Estado da Arte No presente capítulo serão recolhidos e estudados vários trabalhos e projetos relevantes para esta dissertação, essencialmente trabalhos relacionados com MOMs. Estes são os sistemas intermediários já referidos anteriormente e são responsáveis pela entrega assíncrona de mensagens entre aplicações, daí o nome Message-Oriented Middleware, pois estes são orientados a mensagens. Os diversos trabalhos e projetos aqui detalhados serviram como base para a conceção e desenvolvimento do Middleware. Inicialmente serão abordados vários tipos de protocolos e APIs existentes na área de comunicação. De seguida serão apresentadas algumas tecnologias que serão utilizadas nesta dissertação. Após uma análise individual de cada sistema de queues serão apresentados alguns resultados de algumas comparações entre alguns destes sistemas na Secção 2.3, sendo que por último será descrito um trabalho relacionado com o Middleware a desenvolver. 2.1 Protocolos e APIs A presente secção aborda alguns protocolos e APIs que são utilizados para comunicação entre aplicações, serviços, dispositivos, entre outros. Serão abordados os protocolos AMQP, MQTT, STOMP e XMPP, e a API JMS. 2.1.1 Advanced Message Queue Protocol O AMQP surgiu para ser uma norma no que toca à conceção e implementação de sistemas de queues, permitindo diferentes plataformas em diferentes linguagens comunicar. Este foi desenvolvido pelas necessidades de um protocolo de alta fiabilidade e escalabilidade. Existem duas versões do AMQP que tiveram uma maior relevância, são estas a versão 0.9.1 (versão que o RabbitMQ implementa) e a versão 1.0.0 pertencente à Organization for the Advanced Structured Information Standards (OASIS)2. AMQP 0.9.1 apresenta uma arquitetura Client/Broker ou Client/Server permitindo assim uma abstração Publisher/Subscriber (Pub/Sub) ou Request/Response (Req/Res). O tamanho das mensagens enviadas sobre este protocolo tem um cabeçalho fixo de 8 bytes e um tamanho de payload indefinido ou negociável [Nai17]. Este protocolo apresenta três níveis de Quality of Service (QoS), QoS 0 em que não existe confirmação de receção da mensagem, QoS 1 em que é garantida a entrega da mensagem e o QoS 2 em que é garantida a entrega da mensagem sem duplicados. Como protocolo de transporte este utiliza Transmission Control Protocol (TCP)3. Apresenta também vários protocolos para segurança como Transport Layer Security (TLS)4/Secure Sockets Layer (SSL)5, Internet Protocol 2OASIS: https://www.oasis-open.org/ 3TCP: https://tools.ietf.org/html/rfc793 4TLS: https://tools.ietf.org/html/rfc8446 5SSL: https://tools.ietf.org/html/rfc6101 5
Security (IPSec)6e Simple Authentication and Security Layer (SASL)7[Nai17]. Dentro dos brokers AMQP existe pelo menos um Exchange e uma ou mais queues, tal como ilustrado na Figura 3, baseada em [Dos14]. As queues são responsáveis por armazenar as mensagens recebidas até serem entregues ao destinatário e oExchange é responsável por receber as mensagens enviadas e decidir para quais queues enviar a mensagem. A decisão sobre a qual queue entregar uma mensagem passa por analisar os vários campos da mesma, sendo que normalmente o campo que decide a rota que a mensagem deve seguir é o routing key que no caso de a comunicação ser Req/Res pode ser o nome da queue (caso este não tenha sido definido) e no caso de a comunicação ser Pub/Sub é o nome do tópico. Quando um consumidor liga a sua queue com um Exchange do tipo Pub/Sub este tem de especificar um tópico que será a sua subscrição, ou seja, sempre que o Exchange receber uma mensagem cujo o routing key coincida com a sua subscrição (tópico) a mensagem é envia para a queue. O tipo de comunicação Pub/Sub nem sempre necessita de um tópico, sendo que isto acontece porque existe um tipo de configuração para o Exchange em que este entrega uma mensagem a todas queues que estejam ligadas a ele e este tipo de Exchange designa-se por Fanout. Figura 3: Arquitetura AMQP 0.9.1. O AMQP 1.0.0 é definido como um protocolo de comunicação Peer-to-Peer (P2P), sendo que nesta versão cada entidade (Consumer,Producer, outros) está contida num Container e comunica através de Links unidirecionais, porém é possível criar dois Links com direções opostas de forma a gerar um Link bidirecional. Como protocolo de transporte este, tal como a versão anterior, utiliza TCP e apresenta dois protocolos para segurança, o TLS e o SASL [Wal19]. Na Figura 3 é possível verificar a arquitetura do protocolo na versão 0.9.1 e uma das diferenças na arquitetura da versão 1.0.0 é que o Exchange deixou de existir [Dos14]. Apesar das diferenças e de a versão 1.0.0 ser quase uma década mais recente, a versão 0.9.1 continua a ser a mais utilizada [Wal19], pois as diferenças são de tal ordem que a versão 1.0.0 é considerada um protocolo diferente da versão 0.9.1 [Dos14]. 6IPSec: https://tools.ietf.org/html/rfc6071 7SASL: https://tools.ietf.org/html/rfc4422 6
2.1.2 Java Message Service Ao contrário do AMQP o JMS é uma API e não um protocolo, isto é, qualquer MOM que seja implementado através deste terá de utilizar a mesma interface, pois tem de seguir a API fornecida pelo JMS. O JMS possui duas versões principais, a versão 1.1 [RMC09] e a versão 2.0 [Dea13]. As duas versões do JMS apresentam diferentes APIs, sendo estas ilustradas nas Figuras 4 e 5, correspondendo a versão 1.1 à Figura 4 e a versão 2.0 à Figura 5, baseadas em [Dev17]. Figura 4: API JMS 1.1. Figura 5: API JMS 2.0. As principais diferenças que se podem observar da versão 1.1 para a versão 2.0 são a renomeação das classes MessageConsumer eMessageProducer para JMSConsumer eJMSProducer, respetivamente, e a junção das classes Connection eSession numa só classe denominada por JMSContext. Estas diferenças não só simplificaram o código, já que este fica menos verboso, como permitem uma melhor interpretação das classes através da nova nomenclatura. Esta API funciona tanto para comunicação Point-to-Point (PTP) como Pub/Sub e as Figuras 4 e 5, baseadas em [Dev17], apenas ilustram as interfaces com que se interage diretamente. Nas Figuras 6 e 7 são ilustradas as classes que implementam as interfaces de forma a ser possível utilizar os dois tipos de comunicação suportados. As classes Topic,TopicConnectionFactory,TopicSession,TopicConnection,TopicPublisher eTopicSubscriber são utilizadas para o Pub/Sub na versão 1.1, sendo que na versão 2.0 com a junção das interfaces Session e Connection a classe TopicSession deixou de ser utilizada externamente. As classes Queue,QueueConnectionFactory,QueueSession,QueueReceiver,QueueSender eQueueConnection são utilizadas para o PTP na versão 1.1. Na versão 2.0 com a junção das interfaces Session eConnection a classe QueueSession deixou de ser utilizada externamente. 7
Apesar de este ter sido desenvolvido segundo o protocolo AMQP este apresenta suporte para protocolos como MQTT e STOMP referidos anteriormente [DE17;Rab20]. Este sistema foi desenvolvido na linguagem de programação Erlang e neste momento é também disponibilizado para Java e .NET Framework. Atualmente já existem outros programadores que disponibilizam este sistema noutras linguagens, como por exemplo o Pika31 para Python. Uma das principais caraterísticas/funcionalidades do RabbitMQ é a forma como é possível criar topologias com vários brokers. Na Figura 11, baseada em [Dos14], são ilustradas algumas topologias de brokers RabbitMQ. Figura 11: Topologias RabbitMQ. A criação de topologias complexas com vários brokers interligados permite balanceamento de carga e tolerância a faltas. Uma vantagem na utilização deste sistema é a persistência das mensagens, enquanto estas estão no sistema são persistidas pela base de dados embutida do Erlang, denominada de Mnesia32. Com a persistência destas mesmo que o sistema reinicie as mensagens já persistidas não se perdem. Existe a possibilidade de configurar o RabbitMQ de forma a não utilizar o disco para persistir as mensagens, ficando estas em memória [DE17]. Este sistema permite também a criação de um limite de mensagens por queue no caso de mensagens poderem ser apagadas de forma a não sobrecarregar o sistema se estas não estiverem a ser entregues. Outra forma de apagar mensagens das queues sem colocar um limite de mensagens é atribuir um Time to Live (TTL) a cada mensagem, sendo que se este TTL for atingido e a mensagem ainda estiver na queue esta será apagada [DE17]. No caso de o sistema estar a ser utilizado para Pub/Sub este mantém registo de todos os consumidores que consumiram uma mensagem e isto permite que uma mensagem seja apagada quando todos os consumidores a leram [DE17]. Um problema que este sistema apresenta é a pouca escalabilidade quando vários utilizadores utilizam o 31Pika: https://pypi.org/project/pika/ 32Mnesia: https://erlang.org/doc/man/mnesia.html 14
mesmo [Dos14]. Na Figura 12, retirada de [EA15], é apresentado um gráfico que ilustra o número de mensagens por segundo processadas pelo sistema RabbitMQ (linha azul do gráfico) em função do número de utilizadores. Apesar de este se encontrar em escala logarítmica é possível verificar que o número de mensagens por segundo processadas com 2 utilizadores é praticamente o dobro de quando utilizado apenas 1 utilizador, porém a diferença no número de mensagens processadas entre 2 utilizadores e 10 é praticamente constante. O número de utilizadores também é apresentado em escala logarítmica, pelo que o 0 no eixo horizontal corresponde a 1 utilizador e o 1 corresponde a 10 utilizadores. Figura 12: Número de mensagens por segundo (msg/s) processadas pelo sistema RabbitMQ em função do número de utilizadores. O RabbitMQ foi desenvolvido inicialmente pela Rabbit Technologies Ltd. em 2007, sendo a versão 1.7.2 a última versão lançada pela empresa antes de ser adquirida pela SpringSource em 2010. Atualmente a versão mais recente deste sistema é 3.9.9. Tal como referido o RabbitMQ implementa o AMQP 0.9.1, porém este pode suportar o AMQP 1.0.0 através de um plugin [DE17;Rab20]. 2.2.2 ActiveMQ O ActiveMQ é um sistema intermediário externo tal como o RabbitMQ e surgiu como uma necessidade para o Apache Geronimo33, pois este necessitava de um sistema de queues baseado na API JMS e os existentes no mercado estavam todos sob licenças pagas pelo que os programadores do Apache Geronimo começaram a desenvolver o ActiveMQ. Este sistema possui duas versões, sendo a principal diferença as versões da API JMS, o ActiveMQ 5 implementa o JMS 1.1, enquanto que o ActiveMQ Artemis implementa o JMS 2.0 e apresenta compatibilidade para o JMS 33Apache Geronimo: https://geronimo.apache.org/ 15
1.1. Parte do código do ActiveMQ Artemis pertencia ao HornetQ34 da Red Hat, pelo que esta tem controlo sobre parte deste projeto. Sendo este um sistema intermediário externo tem algumas funcionalidades idênticas ao RabbitMQ, tais como a persistência de mensagens (estas são persistidas pelo KahaDB35) e as topologias complexas de vários nodos do mesmo por forma a atingir uma maior escalabilidade [SBD11;Ion15]. Na Figura 13, baseada em [Ahu21], está representada a separação entre queues e topics realizada pelo ActiveMQ 5, sendo que uma vantagem desta separação é que permite a criação de topics e de queues com o mesmo nome sem gerar conflitos. Uma mensagem persistida numa queue é consumida apenas uma vez enquanto que mensagens persistidas em topics podem ser consumidas por vários consumidores. Figura 13: Arquitetura ActiveMQ Broker. O ActiveMQ foi desenvolvido em Java, mas apresenta também soluções para clientes em C/C++, .NET, Python, entre outros, sendo que o sistema (servidor) funciona sempre na Java VM36. Este foi desenvolvido segundo a API JMS, mas apresenta suporte para muitos outros protocolos tais como o AMQP 1.0.0 [Act19b], MQTT, XMPP, STOMP, entre outros [SBD11], sendo que no STOMP é apenas suportada a versão 1.0 e 1.1 para o JMS 1.1 [Act19c], o suporte para a versão 1.2 do STOMP apenas existe no JMS 2.0 [Act]. Uma funcionalidade deste sistema é o delivery delay (atraso na entrega) de mensagens, em que o produtor especifica o tempo que a mensagem tem de ficar armazenada antes de ser reencaminhada para o consumidor, sendo que este atraso (delay) pode ser especificado de diversas formas. Ora esta funcionalidade tem 4 propriedades, o atraso (delay), o período entre repetições (period), o número de repetições (repeat) e o cronómetro (cron) que consiste ficar indefinidamente a produzir o mesmo efeito com o tempo especificado, todas estas podem ser utilizadas em conjunto [Act19a]. O ActiveMQ tem incorporado autenticação e autorização através de ficheiros de configuração ou do Java Authentication and Authorization Service (JAAS)37 [SBD11]. 34HornetQ: https://docs.jboss.org/hornetq/2.4.0.Final/docs/quickstart-guide/html_single/index.html 35KahaDB: https://activemq.apache.org/kahadb 36Java VM: https://docs.oracle.com/javase/specs/jvms/se8/html/ 37JAAS: https://docs.oracle.com/javase/7/docs/technotes/guides/security/jaas/JAASRefGuide.html 16
O ActiveMQ a abordar nesta dissertação é o ActiveMQ 5, a versão 5.16.3 lançada em agosto de 2021 é a mais recente. Atualmente já existe o ActiveMQ Artemis que se passará a denominar por ActiveMQ 6 quando a paridade de funcionalidades entre as duas versões estiver terminada ou a um nível superior. 2.2.3 Kafka O Kafka ou Apache Kafka é um sistema intermediário externo para Pub/Sub altamente escalável. Tal como os outros sistemas este permite a existência de um único sistema ou então topologias complexas como clusters de um único nodo ou de múltiplos nodos. Estes encontram-se ilustrados nas Figuras 14 e 15, baseadas em [Gar15]. Figura 14: Topologia Kafka com apenas um nodo e múltiplos sistemas. Figura 15: Topologia Kafka com múltiplos nodos com e múltiplos sistemas. Nas Figuras 14 e 15, pode observar-se que tanto os nodos como os produtores e consumidores estão ligados ao ZooKeeper38. Este é o sistema de gestão de ficheiros que o Kafka utiliza para persistir as mensagens que recebe [Gar15]. O ZooKeeper inicialmente era um subprojeto do Hadoop39, mas atualmente é um projeto da Apache 38ZooKeeper: https://zookeeper.apache.org/ 39Hadoop: https://hadoop.apache.org/ 17
Software Foundation (ASF)40. As mensagens persistidas no ZooKeeper ficam armazenadas durante uma certa quantidade de tempo especificada na configuração dos tópicos, sendo que isto pode ser visto como uma vantagem ou desvantagem. Uma vez que o Kafka não guarda informação sobre os consumidores que já leram uma mensagem este não sabe se todos os consumidores a leram, pelo que tem de ser única e exclusivamente eliminada pelo ZooKeeper quando esta atinge o tempo especificado para a persistência no tópico, a desvantagem é a mensagem ter de ficar persistida o tempo especificado em vez de ser eliminada mais cedo, a vantagem é um consumidor poder ler uma mensagem novamente se necessitar [DE17]. Este sistema foi concebido para alto débito e para ser distribuído. O sistema foi desenvolvido em Java e para as aplicações cliente existe suporte para várias linguagens como Java, .NET, Python e outros. Uma característica deste sistema é o real time, isto é, a latência introduzida pelo sistema é tão baixa entre o consumir e produtor que é quase como se estivessem a comunicar diretamente, isto é uma funcionalidade crítica para os sistemas baseados em eventos [Gar15]. O Kafka utiliza um protocolo binário próprio sobre TCP e como protocolo de segurança utiliza o TLS. Em termos arquiteturais este sistema assemelha-se ao RabbitMQ e ao ActiveMQ, mas do ponto de vista do processamento de dados é comparado com o Scribe41 e o Flume42 [Gar15]. O Kafka foi inicialmente desenvolvido pela LinkedIn e disponibilizado em 2011. Em 2014 foi criada a empresa Confluent43 para continuar o seu desenvolvimento e apesar de esta desenvolver o Kafka este é fornecido pela ASF. A versão mais recente deste sistema é a 3.0.0 lançada em setembro de 2021, porém a versão a ser utilizada nesta dissertação é a versão 2.8.1. 2.2.4 ZeroMQ O ZeroMQ é o único sistema de queues apresentado nesta dissertação que é um sistema intermediário interno. Este sistema funciona como uma biblioteca de sistema e tal como já foi referido anteriormente é um sistema onde as aplicações/componentes cliente ficam como um nodo do sistema de queues. Uma vez que este sistema funciona como uma biblioteca permite criar outros sistemas de queues em cima deste. O sistema ZeroMQ suporta vários tipos de padrões para comunicação, como por exemplo o Req/Res denominado de Request/Reply (REQ/REP), o Pub/Sub, o Pipeline e o Exclusive Pair [Hin]. O padrão Exclusive Pair é igual ao Req/Res, mas com a restrição de que apenas podem estar dois nodos a comunicar, enquanto que no Req/Res podem estar dois ou mais nodos a produzir mensagens para um único nodo e vice-versa. O Pipeline consiste na utilização de nodos PUSH e PULL, sendo que este é utilizado para a distribuição de 40ASF: https://www.apache.org/ 41Scribe: https://engineering.fb.com/2019/10/07/data-infrastructure/scribe/ 42Flume: https://flume.apache.org/ 43Confluent: https://www.confluent.io/ 18
trabalho e recolha do mesmo. Na Figura 17, retirada de [Hin]44, é possível observar o padrão Pipeline juntamente com o padrão Pub/Sub. Nesta as setas de traço contínuo são relativas ao padrão Pipeline, sendo as setas de traço descontínuo relativas ao padrão Pub/Sub. Figura 16: Padrão Pipeline. Este é um sistema que implementa os seus próprios sockets sobre os sockets TCP, enquanto os sockets TCP são síncronos esta implementação dos sockets ZeroMQ faz com que sejam assíncronos, pelo que se uma aplicação tentar enviar uma mensagem a outra através dos sockets TCP só o consegue fazer se o destino estiver ativo. No caso da utilização dos sockets ZeroMQ não é necessária essa preocupação, pois no caso de o destino não estar disponível o socket ZeroMQ irá guardar a mensagem numa queue. Outra funcionalidade que os sockets ZeroMQ implementam na aplicação destino é caso esta não consiga processar uma nova mensagem quando esta é recebida o socket guarda-a numa queue até a aplicação estar disponível para a processar. Atualmente estas queues geradas pelos sockets ZeroMQ tem um limite de 1000 mensagens por omissão, pelo que se este valor for atingido as mensagens que vierem a seguir podem ser descartadas dependendo do tipo de padrão no qual o ZeroMQ está a ser utilizado. Os sockets ZeroMQ utilizam o Open Pragmatic General Multicast (OpenPGM)45, que é uma implementação do Pragmatic General Multicast (PGM)46, e este permite aos sockets funcionarem em multicast. Esta funcionalidade 44Sob licença CC BY-SA (https://creativecommons.org/licenses/by-sa/4.0/). 45OpenPMG: https://code.google.com/archive/p/openpgm/ 46PMG: https://tools.ietf.org/html/rfc3208 19
não existe nos sistemas de queues que utilizam o protocolo AMQP. Estes sockets permitem ainda ligações de M para N, ao contrário dos sockets TCP que apenas permitem ligações 1 para 1 [Akg13]. Além destes padrões existem o Extended Req/Res que utiliza o ROUTER e o DEALER, e o Extended Pub/Sub que utiliza o XPUB e XSUB. O ROUTER é uma adaptação do REP que torna a comunicação não bloqueante entre o REQ e o REP, além de reduzir o número de conexões do cliente REQ. O DEALER é idêntico ao REQ, sendo que este reencaminha as mensagens recebidas pelo ROUTER para os respetivos REP, reduzindo também o número de conexões dos REP, pois estes apenas recebem comunicações do DEALER. O XPUB e o XSUB tem um comportamento idêntico ao ROUTER e DEALER, pois a função destes é reduzir o número de conexões dos PUB e SUB, fazendo com que os PUB apenas necessitem de conhecer o XSUB e com que os SUB apenas necessitem de conhecer o XPUB. O ROUTER, DEALER, XPUB e XSUB são utilizados com a finalidade de criar um nodo que funcione como um sistema intermediário externo, em que todas as mensagens são enviadas para este nodo e depois este distribui-as pelos respetivos nodos consumidores. A Figura 16, retirada de [Hin], ilustra o padrão Extended Req/Res. Figura 17: Padrão Extended Req/Res. Apesar dos sockets ZeroMQ trazerem várias vantagens face aos sockets normais, o ZeroMQ tem pontos negativos também, como por exemplo a não persistência das mensagens por omissão, isto é, se um nodo (aplicação) se desligar ou reiniciar todas as mensagens armazenadas na queue desse nodo são perdidas [Akg13]. Uma particularidade deste sistema de queues em relação aos demais é a utilização de diferentes portas para comunicação TCP em vez da utilização explicita de queues, isto é, enquanto os sistemas de queues anteriores se especifica uma queue e é utilizada sempre a mesma porta de comunicação com o sistema de queues, no ZeroMQ com a falta de um sistema intermediário externo são utilizadas portas para realizar a comunicação ponto-a-ponto. 20
Este caso de utilização de diferentes portas é apenas para o Req/Res, pois no caso de Pub/Sub em que os consumidores se subscrevem num tópico já é possível reutilizar uma única porta para vários tópicos. O sistema apresenta suporte para inúmeras linguagens, tais como Java, C, C++, Erlang, Python, entre outras. O ZeroMQ apesar de ser um sistema intermediário interno permite a construção de um sistema intermediário externo, tal como o construído para o estudo realizado por N. Estrada e H. Astudillo em [EA15]. Este começou a ser desenvolvido em 2007 por Pieter Hintjens, quando este registou o domínio zeromq.org, juntamente com Martin Sustrik e a versão mais recente deste sistema é a 0.5.1. 2.3 Estudos Comparativos Tal como já referido anteriormente existem vários estudos comparativos sobre os sistemas de queues referidos, sendo que estes permitem uma escolha ponderada sobre que sistema(s) utilizar dependendo das necessidades do projeto que se pretenda desenvolver. De seguida serão ilustrados alguns desses estudos realizados e os respetivos resultados. 2.3.1 RabbitMQ vs. ActiveMQ V. M. Ionescu em [Ion15] realizou um estudo comparativo entre dois sistemas intermediários externos, o ActiveMQ baseado na API JMS 1.1 e o RabbitMQ baseado no protocolo AMQP 0.9.1, ambos os sistemas foram testados nas mesmas condições. Com este estudo foi concluído que o sistema ActiveMQ era mais rápido a processar as mensagens recebidas dos clientes do que o sistema RabbitMQ, tal como se pode observar na Tabela 1. No que toca a entrega de mensagens a clientes o sistema RabbitMQ teve um melhor desempenho, tal como é ilustrado na Tabela 2. Nas Tabelas 1 e 2, baseadas em [Ion15], são ilustrados os valores de envio e receção de mensagens dos dois sistemas, os valores de envio do sistema ActiveMQ para a aplicação cliente na Tabela 2 são aproximações obtidas através do gráfico ilustrado no estudo, todos os outros estão em formato tabular. Tamanho Ficheiro 58KB 148KB 450KB 1.44MB 3.97MB RabbitMQ 0.694991 1.706642 3.663944 8.802655 27.794664 ActiveMQ 0.246679 0.715804 1.323073 5.370515 18.150352 Tabela 1: Tempo em milissegundos do envio de mensagens da aplicação cliente para o sistema. 21
Tamanho Ficheiro 58KB 148KB 450KB 1.44MB 3.97MB RabbitMQ 0.003713 0.004573 0.00662 0.008245 0.012111 ActiveMQ ≈0.15 ≈0.17 ≈0.22 ≈0.39 ≈0.49 Tabela 2: Tempo em milissegundos do envio de mensagens do sistema para a aplicação cliente. Apesar do desempenho no envio de mensagens por parte do RabbitMQ para os clientes ser de uma ordem muito superior à do ActiveMQ, não é o suficiente para compensar o desempenho na entrega de mensagens ao broker, pelo que no geral o desempenho do ActiveMQ é superior. Neste mesmo estudo foi feita uma comparação da escalabilidade das duas tecnologias relativa ao número de clientes em simultâneo a fazer pedidos a estas. Neste foi possível verificar que para muitos clientes o RabbitMQ obtém melhores tempos de entrega de mensagens ao sistema. Estes resultados são ilustrados na Tabela 3, baseada em [Ion15], para o envio de uma mensagem de 148KB. Número de Clientes 1 3 10 20 RabbitMQ 2.24500 2.63496 5.03370 15.69899 ActiveMQ 1.00589 2.69662 11.23365 33.36614 Tabela 3: Tempo em milissegundos do envio de mensagens da aplicação cliente para o sistema com diferente número de clientes. Posto isto será necessária especial atenção aos dois sistemas durante o desenvolvimento do Middleware para não piorar o seu desempenho geral de nenhum dos dois. 2.3.2 RabbitMQ vs. ZeroMQ N. Estrada e H. Astudillo [EA15] realizaram um estudo de escalabilidade entre dois sistemas de queues que seguem duas abordagens diferentes, um deles o sistema RabbitMQ que é um sistema intermediário externo, o outro é o ZeroMQ, um sistema intermediário interno, sendo que nesta comparação foi criado um middleware para a utilização do ZeroMQ, fazendo assim com que a comparação passasse a ser entre dois sistemas intermediários externos. De modo a comparação ser genuína ambos os sistemas foram testados na mesma máquina. Na Figura 18, retirada de [EA15], é possível observar-se os sistemas anteriormente descritos, sendo utilizados para o teste os sistemas que se listam ao centro (ZeroMQ broker) e à direita (RabbitMQ). 22
Figura 18: Topologias do cenário de teste. Nesta comparação foram realizados os seguintes testes: 1. Determinar o número limite de operações que uma aplicação consumidora (mensagens do sistema para a aplicação) consegue fazer em disco, variando o número de mensagens por segundo. 2. Determinar o melhor rendimento para várias quantidades de clientes. 3. Determinar o melhor desempenho de cada sistema para um número variado de mensagens, usando a quantidade de clientes que obteve melhor resultado no ponto anterior. O Teste 1 é apenas realizado para obter qual o número de operações por segundo onde são obtidos os melhores resultados para operações sobre disco, ambos os sistemas têm registos muito idênticos, cerca de 10000 operações por segundo, mas ligeiramente superior para o ZeroMQ. Depois de determinado este valor passou-se ao Teste 2 onde se utilizou uma quantidade variada de clientes. Neste teste o ZeroMQ destacou-se do RabbitMQ obtendo uma maior quantidade de mensagens processadas por segundo para qualquer número de clientes entre 1 e 10. Sendo que o para o ZeroMQ não foram obtidas melhorias no número de mensagens processadas a partir de 5 clientes para cima, o mesmo ocorreu para o RabbitMQ, mas a partir de 3 clientes, Figura 12 ilustrada na Secção 2.2.1. Para o teste final (Teste 3) o número de clientes foi fixado em 1 produtor e 3 consumidores (3 corresponde ao valor para o qual o RabbitMQ atingiu o limite de mensagens por segundo) e o número de mensagens por segundo foi variando entre os seguintes valores 100 e 100000. No final do teste foi observado que que o ZeroMQ conseguia processar cerca de 11000 mensagens por segundo, enquanto que o RabbitMQ apenas conseguiu cerca de 4500. Os resultados descritos podem ser observados na Figura 19. 23
2.3.4.2 Análise Quantitativa Latência No teste de latência foram realizados dois testes para se poder observar qual o impacto de passar de um QoS de nível 0 para um QoS de nível 1 nos dois sistemas. Após os testes realizados notou-se que a alteração do nível do QoS praticamente não afetou a latência, tanto para o RabbitMQ como para o Kafka. De seguida na Tabela 10 e 11, baseadas em [DE17], são apresentados os resultados para os dois sistemas em condições normais. Média Máximo RabbitMQ 1-4 2-17 Kafka 1 15 Tabela 10: Latência em milissegundos com sistemas não replicados. Média Máximo RabbitMQ 1-4 2-17 Kafka 1 30 Tabela 11: Latência em milissegundos com sistemas replicados. Taxa de Transferência Tal como no teste de latência foram realizados dois testes para avaliar a taxa de transferência, baseando-se estas diferenças novamente ao nível do QoS (nível 0 e nível 1). Na Figura 25, retirada de [DE17], é possível observar os resultados obtidos para o teste do QoS nível 0, tanto para o RabbitMQ como para o Kafka, é ainda possível verificar que para pacotes da mesma dimensão o RabbitMQ obteve uma maior taxa de transferência dos mesmos. Esta comparação é baseada na linha a roxo do RabbitMQ (confirm=-1, no replication, Mbps) com a linha a azul do Kafka (bytes in Mbps). Esta comparação é possível de ser feita pois os gráficos estão ambos na mesma escala. É também possível observar na Figura 25 que o RabbitMQ sem replicação e sem confirmações (acks) obtém um maior número de pacotes transferidos por segundos, sendo que esta comparação é baseada na linha azul do RabbitMQ (confirm=-1, no replication, pps) com a linha vermelha do Kafka (packets in pps). Sobre os 50000 pacotes por segundo obtidos pelo Kafka quando o tamanho dos mesmos é muito reduzido nada se pode inferir uma vez que não existem resultados para esse tamanho de pacotes no RabbitMQ. 30
Figura 25: Resultados teste com QoS nível 0. Na Figura 26, retirada de [DE17], é possível observar o impacto no desempenho do Kafka quando o número de tópicos varia. Nesta é possível observar que o Kafka é escalável quando o número de tópicos aumenta, uma vez que quando estes aumentam o número de pacotes por segundo aumenta também. É também possível observar um decréscimo significativo quando é introduzida replicação, uma vez que é necessária a coordenação entre as duas réplicas. Figura 26: Impacto no desempenho consoante o número de tópicos no sistema Kafka. 2.4 Trabalhos Relacionados De seguida será apresentado um trabalho relacionado com o Middleware a desenvolver e este servirá como base teórica para funcionalidades e decisões a tomar. 2.4.1 Multi-MOM Multi-MOM é uma Dissertação de Mestrado apresentada e defendida por Yuri Morais Bezerra em [Bez10]. Nesta dissertação é abordado um problema semelhante ao da dissertação a desenvolver. Yuri Bezerra dissertou sobre os vários padrões de comunicação síncrona e assíncrona existentes para o desenvolvimento de aplicações mobile. Existindo uma panóplia de soluções este criou uma biblioteca de forma a 31
dar suporte aos principais paradigmas de comunicação síncrona e assíncrona, permitindo assim a um programador estar apenas dependente da sua camada e obter acesso a vários paradigmas de comunicação. Esta biblioteca foi desenvolvida de forma a ser extensível permitindo facilmente a adição e remoção de paradigmas de comunicação. Além disso a sua biblioteca é configurável, isto é, um programador que queira utilizar a sua biblioteca pode especificar quais os paradigmas de comunicação que pretende utilizar fazendo assim com que a biblioteca seja mais leve quando não são necessários todos os paradigmas de comunicação. A funcionalidade de configuração de quais paradigmas de comunicação utilizar era muito útil na altura em que o Multi-MOM foi lançado porque os smartphones eram muito mais limitados em termos de recursos do que na atualidade. Porém esta funcionalidade continua a ser útil atualmente porque quanto menos recursos se necessitar mais recursos restam para outras funcionalidades. Apesar de na altura existirem inúmeras soluções que implementavam vários paradigmas de comunicação, não era conhecida uma que implementasse tantos paradigmas como a solução proposta por Yuri Bezerra. Além disto nenhuma das soluções conhecidas eram facilmente extensíveis a novos paradigmas de comunicação, sendo que Yuri Bezerra teve este pormenor em atenção por forma a o tornar numa funcionalidade do Multi-MOM. Um problema desta solução é que sendo uma biblioteca esta apenas funciona para um conjunto de smartphones, sendo que neste caso smartphones Android, os restantes que utilizam sistemas operativos como iOS ou Windows Phone não poderiam utilizar esta biblioteca. A presente dissertação tem como objetivo a integração de diversos MOMs, pelo que a funcionalidade de extensibilidade é de elevada relevância uma vez que numa primeira fase serão apenas implementados alguns sistemas de queues e a funcionalidade de configuração é também extremamente importante uma vez que o cliente terá de escolher apenas os sistemas de queues que pretende utilizar para não sobrecarregar o Middleware com sistemas de queues que não lhe sejam relevantes. 32
3 Descrição do Problema Com a existência de diversos sistemas de queues e o constante aparecimento de novos sistemas torna a escolha cada vez mais difícil, mesmo existindo vários estudos comparativos para tentar agilizar este processo. Além do crescente número destes sistemas a inexistência de uma norma no que toca às suas APIs as aplicações ou componentes de uma aplicação que utilizem estes sistemas ficam bastante dependentes destes. No caso de surgir a necessidade de trocar de um sistema de queues para outro pode ser um processo muito moroso dependendo do grau de modularização da aplicação, pois a troca de um sistema para outro pode levar a uma reestruturação e modificação dos componentes da aplicação. Existem alguns motivos que podem levar uma aplicação a trocar de sistemas de queues, sendo que de seguida são apresentados alguns dos motivos pelos quais uma aplicação pode necessitar de o fazer. • Qualidade de serviço do sistema, isto é, as decisões que o sistema toma em cada situação de forma a evitar ao máximo o constrangimento da aplicação. • Sistema que está a ser utilizado foi descontinuado, isto é, o sistema deixou de ter manutenção ou novas atualizações, pelo que surgem novos sistemas que ultrapassam este. • A aplicação adicionou algumas funcionalidades para as quais precisa de um sistema de queues diferente do qual estava a utilizar. • Sistema que está a ser utilizado não segue boas práticas no lançamento de novas atualizações, ou seja, as novas atualizações podem criar conflitos na aplicação que estava a utilizar o sistema, levando esta a ter de utilizar versões anteriores do sistema ou a ser reformulada para suportar a nova versão. Um exemplo destas atualizações que não seguiram boas práticas foi o protocolo AMQP, o sistema RabbitMQ utiliza a versão 0.9.1 deste protocolo e não foi criada a versão para suportar a 1.0.0 do mesmo por serem muito distintas, as diferenças foram abordadas na Secção 2.1.1. A Figura 27 ilustra as Application1 e Application2 dependentes do Broker1, se estas agora necessitarem de trocar para o Broker2, por algum dos motivos anteriores ou outro não referido, tem de ser modificadas ou adaptadas por forma a conseguirem comunicar com este novo sistema de queues. Isto acontece porque as aplicações estão dependentes da API fornecida pela versão cliente dos sistemas de queues. Figura 27: Aplicação dependente do sistema de queues. Apesar de na Figura 27 as aplicações estarem dependentes de sistemas intermediários externos, sendo que 33
esta dependência também ocorre para os sistemas intermediários internos. Além da dependência ao nível do código existe um outro problema que consiste na conjugação da linguagem de programação com as linguagens suportadas para cada um dos sistemas de queues. Por exemplo existe a versão cliente do ActiveMQ para a linguagem de programação Golo47, mas para esta linguagem de programação não existe a versão cliente do RabbitMQ nem do ZeroMQ pelo que no caso de escolha desta linguagem de programação estes sistemas seriam logo excluídos. No caso de ser uma restrição da aplicação utilizar o RabbitMQ como sistema de queues então a linguagem de programação Golo já não poderia ser utilizada. Por forma a resolver estes problemas existem algumas soluções, como a criação de uma biblioteca a nível de código, tal como a solução apresentada por Yuri Bezerra em [Bez10]. Porém esta solução necessita que para cada linguagem de programação seja criada uma biblioteca. A Figura 28 representa a introdução desta biblioteca entre a aplicação e os sistemas de queues. Figura 28: Biblioteca entre as aplicações e os sistemas de queues. Em termos de desempenho esta solução tem um impacto quase nulo, uma vez que são apenas executados alguns métodos extra entre o momento em que a aplicação quer enviar a mensagem até ser efetivamente enviada para o sistema de queues. Outra solução passa pela criação de um componente intermediário entre a aplicação e o sistema de queues, sendo que esta resolveria a dependência a nível de código e também o problema de falta de suporte das versões clientes dos sistemas de queues para algumas linguagens de programação, porém esta solução força à configuração deste componente para este poder comunicar com os sistemas de queues. Na Figura 29 é possível observar como ocorreriam as comunicações entre a aplicação e o sistema de queues através do novo componente. Esta solução apresenta um maior impacto no desempenho em relação à solução anterior uma vez que é necessária a passagem por um componente extra, o Middleware, antes da mensagem ser entregue e lida do sistema de queues. 47Golo: https://golo-lang.org/ 34
Figura 29: Middleware interno ao projeto. Uma terceira solução é a criação de um serviço externo ao projeto. Com esta solução já não é necessária a configuração dos sistemas de queues, pois o serviço externo resolve ele próprio esse processo de configuração, removendo assim qualquer sistema de queues da arquitetura interna do projeto. Na Figura 30 é possível observar que o componente Middleware se encontra externo ao projeto e que é este que possui os sistemas de queues, abstraindo assim a sua configuração do projeto. Ao contrário da solução anterior esta terá um grande impacto no desempenho, uma vez que é um sistema externo ao projeto existirá uma maior latência na comunicação entre as aplicações e os sistemas de queues através do Middleware e além disto terá de existir uma camada de segurança para garantir que apenas as aplicações do projeto conseguem comunicar através do Middleware, todas as medidas de segurança terão também impacto no desempenho. Figura 30: Middleware externo ao projeto. Ponderadas as possíveis soluções para a resolução dos problemas referidos será adotada a terceira opção. Postos estes problemas e as respetivas possíveis soluções o objetivo passa agora pelo desenvolvimento de um Middleware que englobe as duas soluções referidas acima por forma a ser um serviço que suporte vários sistemas de queues existentes e que possa ser acedido através de qualquer linguagem de programação. De modo a que este Middleware não seja apenas um adaptador entre os vários sistemas de queues e as aplicações que necessitam deles para comunicar assincronamente este terá características de valor acrescentado 35
de forma a adicionar novas funcionalidades, permitindo assim às aplicações tirarem um maior partido na utilização deste sistema. 36
4 Conceção No presente capítulo é apresentada a conceção do Middleware a desenvolver. Inicialmente será ilustrado o domínio do problema na Secção 4.1, de seguida serão elaborados os requisitos na Secção 4.2, após o levantamento, a análise e a priorização de requisitos será realizada a modelação necessária ao desenvolvimento do Middleware e apresentada na Secção 4.3, respeitando os requisitos e restrições levantados numa fase inicial. 4.1 Modelo de Domínio O modelo de domínio representa as entidades do problema em mãos e de como estas interagem entre si. Apesar deste ser um modelo de alto nível de abstração todas as decisões tomadas sobre ele tem impacto na elaboração de requisitos e subsequentes diagramas de modelação. Posto isto este é fulcral para o desenvolvimento de qualquer aplicação, uma vez que será a base para a elaboração dos requisitos e subsequentes diagramas de modelação de mais baixo nível, como diagramas de classes. Na Figura 31 é ilustrado o modelo de domínio elaborado. Figura 31: Modelo de Domínio. 37
As principais entidades do modelo de domínio elaborado são o serviço (Service), a conexão (Connection), o sistema de queues (QueueSystem) e a mensagem (Message). Tal como se pode observar a entidade serviço tem de realizar uma conexão, sendo que nesta é indicado qual o sistema de queues, a estratégia e a Queue que se pretende utilizar, podendo ainda especificar um tempo pelo qual as mensagens ficarão persistidas. No momento em que é realizada a conexão é criado um Token único de forma a identificar e validar esse serviço. O sistema de queues pode ser um sistema intermediário externo (BrokerBased) ou intermediário interno (BrokerLess), sendo que os intermediários externos são o RabbitMQ, ActiveMQ e Kafka, e o intermediário interno é o ZeroMQ. Para iniciar o envio ou receção de mensagens o serviço tem de realizar a conexão anteriormente referida por forma a obter o Token de identificação. A entidade mensagem tem de seguir um formato (Format) e o seu conteúdo (Payload) é texto (Text). Estas tem ainda de ser identificadas por um ID único e podem ou não ter uma estratégia associada, diferente daquela que o serviço indicou no momento da conexão. Além da estratégia pode também conter uma Queue permitindo assim o envio da mensagem para uma Queue diferente da indicada no momento da conexão. A estratégia pode ser do tipo REQ/RES, PUB/SUB, FANOUT ou HEADERS, sendo que estas representam as estratégias suportadas pelos sistemas de queues, ressalvando que algumas podem ser incompatíveis, como por exemplo a Strategy FANOUT com o QueueSystem Kafka. Os sistemas de queues são constituídos por Queues onde são armazenadas as mensagens que são enviadas por serviços. Estas são armazenadas nas Queues até um outro serviço as ler, dependendo da estratégia que esteja a ser utilizada. 4.2 Levantamento de Requisitos Nesta secção serão listados todos os requisitos levantados numa fase inicial a ter em conta no desenvolvimento do Middleware. Depois destes estarem elaborados será realizada a priorização dos requisitos, implementando assim os requisitos mais importantes para o correto funcionamento do Middleware. De seguida são apresentados na Tabela 12 os requisitos de utilizador. Estes servem como base para a criação do Middleware, pois este terá de ter em conta todos os requisitos especificados, mesmo que apenas sejam implementados parte destes. 38
# Requisito Descrição 1Os utilizadores tem de ter a capacidade de se registar no Middleware. 2No momento do registo tem de ser especificado o sistema de queues principal a utilizar. 3 No momento do registo tem de ser especificada a estratégia de comunicação principal a utilizar com o sistema de queues, bem como todos os componentes necessários para a utilização dessa estratégia. 4Os utilizadores tem de efetuar o login para ficarem ligados ao sistema. 5No momento de envio de uma mensagem o utilizador pode especificar uma queue ou estratégia diferente da principal. 6No momento de envio de uma mensagem pode especificar um atraso na mesma para esta ser entregue mais tarde. 7O utilizador pode ativar a persistência de mensagens, permitindo assim a releitura das mensagens. 8Uma mensagem pode ser obtida novamente pelo seu utilizador enquanto estiver persistida. 9O utilizador pode eliminar uma mensagem que esteja persistida. Tabela 12: Requisitos. 4.2.1 Priorização Terminada a especificação dos requisitos estes serão agora priorizados por forma a implementar os requisitos indispensáveis para o Middleware, ou seja, que ofereçam o Minimum Viable Product (MVP). O MVP é o produto mínimo a oferecer de forma a ser possível utilizar o sistema com funcionalidades básicas. Por forma a atingir este MVP será então dada prioridade aos requisitos de 1 a 4 presentes na Tabela 12 da Secção 4.2. Os requisitos 5 a 9 serão implementados assim que os requisitos prioritários estiverem finalizados ou praticamente finalizados. Apesar de estes serem apenas implementados posteriormente serão tidos em consideração na fase de modelação (Secção 4.3) por forma a não criar incompatibilidades que levassem a uma reestruturação da implementação dos requisitos prioritários. Os requisitos não prioritários que não sejam implementados durante o desenvolvimento serão adicionados ao trabalho futuro a efetuar sobre este Middleware. 39
A Figura 37, baseada em [Gura], ilustra o padrão Adapter. Nesta o objeto Client representa a lógica de negócio, enquanto que a interface ClientInterface representa a porta de saída e o objeto Adapter representa o adaptador de saída. Outra decisão tomada foi a utilização do padrão arquitetural Facade [Gurb] e Singleton [Gurc] na lógica de negócio. O padrão Facade foi utilizado para garantir que existia um ponto único de entrada para a lógica de negócio. O padrão Singleton será utilizado na mesma classe que implementa o padrão Facade garantindo assim que existe apenas uma instância do único ponto de entrada para a lógica de negócio. Na Figura 38, baseada em [Gurb], encontra-se ilustrado o padrão Facade, sendo que nesta o objeto Client são os adaptadores de entrada e o objeto Facade o ponto único de entrada para a lógica de negócio. Este por sua vez comunica com todos os subsistemas que necessitar. Figura 38: Padrão Facade. O padrão Singleton encontra-se ilustrado na Figura 39, baseada em [Gurc]. Nesta o objeto Client representa os adaptadores de entrada e o objeto Singleton representa o ponto de entrada único da lógica de negócio, garantindo que todos os objetos Clients (adapatores de entrada) utilizam a mesma instância. Figura 39: Padrão Singleton. Por último, uma das decisões tomadas foi a utilização de Data Transfer Objects (DTOs), Pure Old Java Objects (POJOs) e entidades, isto é, o mesmo objeto tem 3 representações diferentes. Na lógica de negócio é utilizado o POJO, nos adaptadores de saída para ligação a BD é utilizada uma entidade que represente o POJO e nos adaptadores de entrada quando é necessário retornar o POJO este é convertido para um DTO. Esta decisão foi 46
tomada uma vez que os dados a persistir ou a retornar podem não corresponder exatamente a todas as variáveis do POJO, garantindo assim um maior controlo sobre os dados que são persistidos ou retornados. 4.3.4 Diagrama de Classes Os diagramas de classes representam a estrutura interna de cada componente a desenvolver, bem como todos os métodos e parâmetros necessários para todas as funcionalidades serem concretizadas. Todos os diagramas de classes que irão ser representados a seguir seguem a mesma arquitetura interna, isto é, são maioritariamente compostos pelos mesmos packages. Por forma a seguir o padrão hexagonal, apresentado na Secção 4.3.3, todos os microsserviços a desenvolver são constituídos por três packages principais, o inboundadapters, o businesslogic e o outboundadapters. O package inboundadapters contêm as classes necessárias para as entradas do microsserviço, o outboundadapters contêm as classes necessárias para o envio de dados/informação para outros serviços (serviços externos ao microsserviço). O businesslogic divide-se em três packages, o inboundports, o business e o outboundports. No inboundports encontram-se apenas interfaces que são os pontos de entrada para o package business. Isto porque as classes do package business implementam as interfaces presentes no inboundports e as classes do package inboundadapters apenas conhecem as interfaces do inboundports. O package business contêm toda a lógica de negócio do microsserviço em questão, sendo que este encontrase dividido em mais packages, como o resources onde se encontram os POJOs, security onde ficam as classes para validação de tokens e o package exception onde estão colocadas todas as exceções personalizadas a cada microsserviço. Por último o package outboundports contêm os pontos de saída do business do microsserviço. Este é apenas constituído por interfaces tal como o package inboundports. As classes do package outboundadapters implementam as interfaces presentes no outboundports e as classes do package business apenas conhecem estas interfaces. Por forma a facilitar a compreensão de qual classe vai implementar a respetiva interface, foram criados vários packages dentro do outboundadapters e do outboundports exatamente com os mesmos nomes, como por exemplo existe o package data no outboundadapters, então foi criado o package data no outboundports, sendo que a classe que se encontra no package outboundadapters.data implementa a interface que se encontra no package outboundports.data. Como os diagramas de classes completos são de grandes dimensões estes serão apresentados em várias figuras em cada microsserviço por forma a ilustrar a estrutura interna de cada microsserviço e as classes mais importantes. Os diagramas completos encontram-se no anexo A. Primeiramente será apresentado na Secção 4.3.4.1 o diagrama de classes do componente Registry, sendo de seguida apresentado o componente Sender na Secção 4.3.4.2, depois o componente Receiver na Secção 4.3.4.3 e por último o componente Discovery e ZeroMQBroker nas Secções 4.3.4.4 e 4.3.4.5, respetivamente. 47
4.3.4.1 Componente Registry O componente Registry será o serviço responsável pela gestão de instâncias Middleware. Além disso este componente irá conter a informação sobre todos os sistemas de queues suportados para a gestão das instâncias. Inicialmente será ilustrada a arquitetura interna deste componente e de seguida serão ilustradas várias classes deste mesmo componente, começando pelas classes contidas no inboundadapters, passando pelas classes dos package businesslogic e por último as classes do package outboundadapters. As Figura 40 a 42 representam a arquitetura interna do componente Registry, sendo que nestas é possível observar a arquitetura hexagonal descrita na Secção 4.3.4. Os packages inboundports e outboundports encontramse repetidos propositadamente por forma a ser possível verificar as dependências que existem sobre as classes presentes nestes packages. Figura 40: Arquitetura e classes inbounds do componente Registry. Como é possível observar na junção da Figura 40 com a Figura 41 as classes do package inboundadapters apenas conhecem a interface presente no package inboundports. Tal como acontece na junção das Figuras 40 e 41, na junção das Figuras 41 e 42 é possível observar que as classes do package business apenas conhecem as interfaces do package outboundports, sendo depois estas implementadas pelas classes do package outboundadapters. 48
Figura 41: Arquitetura e classes business do componente Registry. Figura 42: Arquitetura e classes outbounds do componente Registry. A única forma de aceder a este serviço é através da sua API RESTful denominada de IRegistryController que é posteriormente implementada pela classe RegistryController. Este possui métodos para gestão de instâncias, que 49
são o registerProject, deleteProject, getProject e updateProject. O método getBrokers serve para informar todos os sistemas de queues suportados pelo Middleware. Por último os métodos registerBroker e updateBroker permitem a gestão dos sistemas de queues suportados pelo Middleware, permitindo assim a gestão desta informação sem a necessidade de parar o serviço. Todos estes métodos encontram-se representados na Figura 43. Figura 43: Classe RegistryController. Sempre que um método da Figura 43 é invocado pela receção de um pedido HTTP este invocará um método da interface IRegistryService, sendo que esta invocação ocorre do seguinte modo, o método registerProject da classe RegistryController vai invocar o método registerProject da interface IRegistryService. Esta interface não é aqui demonstrada em detalhe pois a Figura 44 representa a classe RegistryService que implementa a interface IRegistryService, pelo que os métodos públicos da classe correspondem aos métodos da interface. Figura 44: Classe RegistryService. A classe RegistryService possui ainda uma coleção de regiões, sendo que isto permite validar que a região passada no momento de um registo de um projeto é válida. Este componente tem a necessidade de persistir dois tipos de dados distintos, o Project e o Broker. Para isso foi criada uma interface para cada um no outboundports, denominadas de IProjectDAO e IBrokerDAO. Estas não estão representadas porque as classes ProjectDAO e BrokerDAO, representadas nas Figuras 45 e 47, respetivamente, contêm os mesmos métodos porque implementam estas interfaces. Por forma a manipular os dados persistidos a interface IProjectDAO, representada pela classe ProjectDAO 50
na Figura 45, foram criadas as operações Create - Read - Update - Delete (CRUD) e além destas duas procuras para verificar se um certo elemento existe, o método existsByName é utilizado para verificar se existe algum projeto com o mesmo nome no momento da criação de um novo e o método existsByUuid verifica se existe o projeto com o Universally Unique Identifier (UUID)48 fornecido. O UUID é o identificador único gerado no momento do registo do projeto. Figura 45: Classe ProjectDAO. Na Figura 46 é possível observar todos os campos necessários persistir sobre a entidade ProjectData, o name é o nome definido para o projeto, o UUID e o atributo location será o Domain Name System (DNS) gerado para o projeto e este será baseado no nome do mesmo. Region é um atributo que indica a preferência de região para o deployment do Middleware, permitindo assim uma menor latência na comunicação com o mesmo. O último atributo denominado de brokers é a lista de brokers suportados para a instância. Figura 46: Classe ProjectData. Para manipular os dados persistidos da classe Broker recorre-se à interface IBrokerDAO que tal como a anterior não é aqui representada, sendo ilustrada a classe BrokerDAO na Figura 47. Esta possui as operações CRUD sobre as instâncias de Broker e ainda a obtenção de todos os objetos e verificação de existência de um em específico. Figura 47: Classe BrokerDAO. 48UUID: https://tools.ietf.org/html/rfc4122 51
No que toca a dados a persistir relativamente à entidade BrokerData representada na Figura 48 são o name que é o nome do broker, o brokerVersion indica qual a versão suportada para este sistema de queues no Middleware e o brokerId é o identificador único gerado para o sistema de queues no momento do seu registo. Figura 48: Classe BrokerData. 4.3.4.2 Componente Sender O componente Sender será o serviço responsável pela gestão de produtores de mensagens, sendo este serviço também responsável por enviar as mensagens para os sistemas de queues. Inicialmente será ilustrada a arquitetura interna deste componente e de seguida serão ilustradas várias classes deste mesmo componente. O conjunto de Figuras 49 a 51 representam a arquitetura interna do componente Sender, sendo que nestas é possível observar a arquitetura descrita na Secção 4.3.4. As Figuras 49 e 50 tem ambas o package inboundports representado permitindo assim visualizar as ligações entre o inboundadapters com o inboundports e o inboundports com o business, respetivamente. Figura 49: Arquitetura e classes inbounds do componente Sender. 52
Figura 50: Arquitetura e classes business do componente Sender. O package outboundports tal como o inboundports encontra-se representado nas Figuras 50 e 51, permitindo assim visualizar as ligações entre o outboundports com o business e o outboundadapters. 53
Figura 51: Arquitetura e classes outbounds do componente Sender. Este serviço tal como o serviço Registry (Secção 4.3.4.1) apenas possui pontos de entrada através da sua API RESTful denominada de IProducerController. Esta interface não se encontra aqui representada em detalhe, mas a classe ProducerController como implementa a interface IProducerController apresenta os mesmos métodos. Por forma a ser possível gerir instâncias de Producer (produtor) existem os métodos registerProducer, getProducer, deleteProducer, setBroker, setStrategy e setQueue. O método registerProducer cria novos Producers, getProducer retorna um Producer e deleteProducer elimina permanentemente um Producer. Em termos de atualizações de dados setBroker, setStrategy e setQueue atualizam grande parte dos dados do Producer. Além destes métodos existem ainda o connect e o close, que conectam e desconectam os produtores do serviço, respetivamente, e o send que é o método utilizado para o envio de mensagens. 54
Figura 52: Classe ProducerController. Na classe ProducerController sempre que um dos seus métodos é invocado este irá invocar um método da interface IProducerService. Esta interface não está aqui representada, mas as assinaturas dos seus métodos correspondem aos métodos públicos da classe ProducerService ilustrada na Figura 53. Existem vários métodos para gerar instâncias do POJO Broker, sendo que este foi criado com o intuito de evitar que os métodos da interface IProducerMessaging possuíssem muitos parâmetros, assim caso se tenha de adicionar um novo parâmetro apenas se modifica o POJO Broker e as assinaturas dos métodos mantêm-se iguais. Esta opção foi também tomada pelo facto de nem todos os sistemas de queues necessitarem de todos os parâmetros existentes, por exemplo o Kafka não necessita do atributo headers que é apenas necessário para o RabbitMQ. Figura 53: Classe SenderService. A interface IProducerMessaging não está aqui representada pois as classes KafkaProducer (Figura 54), ActiveMQ5Producer (Figura 55), RabbitMQProducer (Figura 56) e ZeroMQProducer (Figura 57) implementam esta, pelo que os seus métodos públicos correspondem à assinatura da interface. Além dos métodos públicos as classes concretas KafkaProducer, ActiveMQ5Producer, RabbitMQProducer e 55
No que toca aos sistemas de queues cada um tem uma classe, sendo que neste caso a classe RabbitMQConsumer é relativa ao RabbitMQ, a classe ActiveMQ5Consumer ao ActiveMQ 5, a classe ZeroMQConsumer ao ZeroMQ e a classe KafkaConsumer ao Kafka. Este último necessita ainda de uma classe auxiliar KafkaListener para receber as mensagens enviadas pelo respetivo sistema de queues. A classe RabbitMQConsumer tal como a RabbitMQProducer presente na Secção 4.3.4.2 possui um mapeamento de Exchanges criados permitindo assim evitar pedidos desnecessários ao sistema de queues quando estes já existem com um tipo diferente. Além do mapeamento de Exchanges existe um mapeamento dos listeners que estão atualmente à escuta no sistema de queues. Este mapeamento é mantido para quando é feita uma atualização ao consumidor ao dados do consumidor, dados do consumidor relativos aos sistemas de queues, ser possível obter o listener atual para o terminar e criar um novo com os novos dados. Este mapeamento dos listeners também existe nas restantes classes dos consumers pelo mesmo motivo, para ser possível terminar os listeners atuais e criar novos quando os dados dos consumidores são atualizados. Figura 67: Classe RabbitMQConsumer. Figura 68: Classe ActiveMQ5Consumer. Tal como foi referido anteriormente para o KafkaConsumer é necessário a classe auxiliar KafkaListener, sendo que esta possui quatro variáveis de classe relativas a um consumidor. Isto porque será criada uma instância desta 62
classe por cada consumidor que seja criado. As variáveis mínimas para esta classe seriam o SseEmitter para enviar os dados ao consumidor e o consumerId para ser possível persistir a mensagem como relativa a este consumidor. Porém foram adicionadas mais duas, a queue e o persistenceTime. Estes permitem evitar acessos a BD para obter dados sobre o consumidor, porque o persistenceTime é necessário para saber se a mensagem tem de ser persistida ou não e a queue é necessária para saber se este consumidor já recebeu esta mensagem por essa mesma queue. Figura 69: Classe KafkaConsumer. A classe ZeroMQConsumer possui dois mapeamentos, um para os vários ZMQSockets que são necessários ter abertos por causa das conexões TCP que este utiliza e outro que mapeia os consumidores, a chave de ambos os mapeamentos são a port que está a ser utilizada, ou seja, para a port 6000 existe um ZMQSocket e uma lista de consumidores que o está a utilizar. Figura 70: Classe ZeroMQConsumer. O componente Receiver persiste dois tipos de dados os Consumers e as Messages, na Figura 71 é possível observar a classe MessageDAO responsável por persistir os dados relativos a mensagens. Neste caso a classe MessageData. A classe MessageDAO implementa a interface IMessageDAO presente no package outboundports e esta não é aqui ilustrada porque todos os métodos presentes na classe MessageDAO são as suas assinaturas. Esta permite fazer leituras de mensagens fornecendo uma queue ou um consumerId, permite persistir novas e apagar uma ou mais mensagens. O método deleteAllByConsumer será apenas utilizado no momento em que um consumidor é eliminado, sendo por isso eliminadas todas as suas mensagens, porque estas apenas existem no caso do consumidor existir. 63
Figura 71: Classe MessageDAO. Os dados relativos à MessageData que são necessários persistir são os presentes na Figura 72, o messageId é o identificador gerado pelo produtor no momento do envio de uma mensagem para os sistemas de queues, o consumerId identifica a qual consumidor a mensagem pertence, o atributo data contêm os dados da mensagem, a queue indica por qual queue a mensagem foi lida e o expireAt é um valor calculado no momento da receção da mensagem e baseado no persistenceTime fornecido pelo consumidor. O atributo expireAt permite que uma mensagem seja eliminada automaticamente quando o seu valor passar a ser inferior à data atual. Figura 72: Classe MessageData. Para persistir e gerir os dados relativos a consumidores a classe ConsumerDAO fornece todos os métodos necessários para o efeito, Figura 73, sendo que esta estende a classe IConsumerDAO que não é aqui ilustrada por a classe ConsumerDAO possuir apenas os métodos desta interface. Os métodos fornecidos são os básicos para as operações CRUD, sendo que para leitura existem dois métodos distintos, o getByUsername e o getByConsumerId que permitem obter um consumidor pelo seu username e id, respetivamente. Figura 73: Classe ConsumerDAO. Os dados necessários persistir relativamente ao consumidor são os presentes na Figura 74 que representa a classe ConsumerData. Os atributos username e password são as credenciais do consumidor e o consumerId é um 64
identificador único gerado para o consumidor no momento do seu registo. O persistenceTime é o tempo definido pelo consumidor para as suas mensagens ficarem persistidas, no caso deste valor ser 0 as mensagens deste consumidor não são persistidas. Os restantes atributos são os atributos mínimos para o correto funcionamento com qualquer sistema de queues atualmente suportado. Figura 74: Classe ConsumerData. Enquanto o ConsumerDAO e o MessageDAO fazem a ponte entre a BD e a lógica de negócio, a classe EmitterDAO representada na Figura 75 e que estende a interface IEmitterDAO, faz o mesmo trabalho com a nuance que os seus dados não são persistidos para uma BD. Os dados deste Data Access Object (DAO) não são persistidos para BD porque são relativos às conexões SSE que estão abertas e estas só existem se o componente estiver ativo, pelo que se o componente for reiniciado por alguma falha todas as conexões se fechariam automaticamente e não faria sentido iniciá-las novamente quando o componente voltasse a ficar ativo. Isto porque do lado dos recetores a conexão também seria fechada no momento em que o componente fosse reiniciado. Figura 75: Classe EmitterDAO. O último outbound a abordar sobre este componente é a classe Discovery, Figura 76, que estende a interface IDiscovery. Esta classe faz a ligação com o componente Discovery, que será apresentado na Secção 4.3.4.4, através de um RabbitMQ. Apenas é necessário um método que serve para registar no componente Discovery que um consumidor abriu uma conexão com uma réplica em específico. O objeto consumer do tipo ConsumerInstance, representado na Figura 77, é utilizado para passar todos os dados necessários para o componente Discovery. 65
Figura 76: Classe Discovery. O POJO ConsumerInstance é constituído apenas por três atributos, o consumerId que identifica qual o consumidor a registar, a replica que indica qual o Internet Protocol (IP) da réplica e o token que é o token de autenticação do utilizador. Figura 77: Classe ConsumerInstance. 4.3.4.4 Componente Discovery O componente Discovery será responsável por encaminhar alguns dos pedidos HTTP para as réplicas corretas do componente Receiver. Uma vez que o componente Receiver possui um pedido que retorna um SSE isso faz com que um consumidor fique ligado a uma réplica específica, pelo que alterações feitas aos dados deste consumidor tem de ser efetuados diretamente a essa réplica para surtir efeitos imediatos. As Figuras 78 a 80 representam a arquitetura interna do componente Discovery. Figura 78: Arquitetura e classes inbounds do componente Discovery. 66
Figura 79: Arquitetura e classes business do componente Discovery. Figura 80: Arquitetura e classes outbounds do componente Discovery. 67
Ao invés dos outros componentes que apenas possuíam pontos de entrada através da sua API RESTful, sendo que este componente tem dois pontos de entrada distintos, a sua API RESTful representada pela interface IReceiverController que não é aqui ilustrada em detalhe, mas sim a classe ReceiverController que implementa esta e se encontra representada na Figura 81. O outro ponto de entrada é através de um sistema de queues, devido à necessidade de este componente receber comunicação assíncrona tal como foi referido na Secção 4.3.2. Este segundo ponto de entrada encontra-se representado na Figura 82. Figura 81: Classe ReceiverController. Os pontos de entrada da API RESTful deste serviço são o closeReceiver, deleteReceiver, setBroker, setStrategy, setQueue e setPersistenceTime, todos estes pedidos tem de ser enviados à réplica específica de Receiver no caso de o consumidor possuir um SSE aberto. No caso de não existir SSE aberto o pedido é encaminhado para qualquer umas das réplicas Receiver que esteja ativa. Quando qualquer um destes métodos é invocado será invocado o respetivo método da interface IDiscoveryService aqui representada através da classe DiscoveryService na Figura 83. A classe ReceiverController possui este nome uma vez que todos os pontos nela presente são relativos ao Receiver, sendo que se no futuro for necessário reaproveitar este componente para reencaminhar pedidos para réplicas especificas de Sender será criada a interface ISenderController e a classe SenderController. É possível observar através das Figuras 81 e 83 que a classe DiscoveryService possui um método público extra em relação à classe DiscoveryController e isto deve-se a que esse método extra, createReceiver, ser invocado através da classe RabbitMQ, presente na Figura 82. Figura 82: Classe RabbitMQ. A classe DiscoveryService, Figura 83, será responsável por toda a lógica de negócio verificando se existem dados relativos ao consumidor que invocou o pedido e no caso de existirem encaminhar este pedido para a interface 68
IReceiverService com a rota para a réplica específica. Caso não exista informação sobre esse consumidor a rota será dirigida a qualquer umas das réplicas do componente Receiver. Figura 83: Classe DiscoveryService. Tal como nos outros componentes a classe DiscoveryService está envolvida no padrão arquitetural Adapter ao apenas conhecer as interfaces do package outboundports, sendo depois estas implementadas pelas classes do package outboundadapters que são os adaptadores neste padrão. A gestão dos dados relativos à localização dos consumidores é efetuada através da classe ReceiverDAO, representada na Figura 84, que implementa a interface IReceiverDAO. Esta não se encontra aqui ilustrada porque as assinaturas dos métodos correspondem aos métodos da classe ReceiverDAO. Em termos de gestão é possível efetuar as operações CRUD e existe ainda o método exists que permite verificar se o consumidor em questão existe neste componente. Figura 84: Classe ReceiverDAO. Os dados persistidos pelo componente Discovery são os apresentados na Figura 85. São apenas persistidos estes dois valores porque são os necessários para identificar um receiver, userId, e a réplica onde se encontra a sua conexão SSE aberta, replica. Figura 85: Classe ReceiverData. 69
Por forma a ser possível realizar a comunicação deste componente com o componente Receiver, a classe ReceiverService, representada na Figura 86, faz a comunicação síncrona através de pedidos HTTP. Para remoção de código duplicado existe o método privado makeRequest que realiza o pedido em si, sendo que os restantes métodos preparam o pedido HTTP para depois ser realizado no método makeRequest. Esta classe implementa a interface IReceiverService. Figura 86: Classe ReceiverService. 4.3.4.5 Componente ZeroMQBroker O diagrama de classes do componente ZeroMQBroker não está aqui representado pois este não será implementado, pelo que não chegou a ser realizado. Este não será implementado porque a complexidade de criar este componente não iria trazer qualquer valor ao Middleware a desenvolver, uma vez que este componente iria ser apenas mais um sistema de queues suportado pelo Middleware. Apesar do componente ZeroMQBroker não ser implementado este foi tido em conta nos diagramas de classes dos componentes Sender e Receiver. 70
5 Implementação No presente capítulo será ilustrada a implementação do Middleware, para tal primeiro serão abordadas as tecnologias utilizadas para a implementação, bem como as vantagens e desvantagens que cada uma fornece. 5.1 Tecnologias A presente secção encontra-se dividida em três subsecções, na primeira subsecção 5.1.1 são descritas as tecnologias utilizadas para o desenvolvimento dos componentes do Middleware a desenvolver, na segunda subsecção 5.1.2 são apresentadas as tecnologias utilizadas para testar os componentes durante o processo de desenvolvimento e na terceira subsecção 5.1.3 são descritas as tecnologias utilizadas para o deployment do projeto como um todo. As tecnologias utilizadas são iguais para o serviço Registration e para o serviço Middleware. 5.1.1 Desenvolvimento Durante o desenvolvimento do Middleware foram utilizadas várias tecnologias por forma a ser possível atingir o objetivo pretendido. Primeiramente escolheu-se qual a linguagem de programação a utilizar, sendo que esta escolha recaiu sobre Java, pelo maior à vontade nesta linguagem e pelo seu poder no que toca a abstrações, que permite a reutilização de código. Após a escolha da linguagem de programação recorreu-se ao Visual Paradigm49 para a construção dos diagramas apresentados no Capítulo 4, sendo que este foi crucial para as decisões arquiteturais do Middleware a desenvolver. Por forma a tirar o máximo partido da linguagem de programação escolhida foi utilizada a framework Spring Boot50. Esta framework tem já desenvolvido código para 3 dos sistemas de queues a implementar (RabbitMQ, ActiveMQ e Kafka) e tem também o Web MVC que permite expor um serviço HTTP seguindo o modelo Model - View - Controller (MVC). Além destes também traz já integração com várias BDs, tais como MongoDB, MySQL, PostgreSQL, entre outras, e fornece também uma biblioteca de validação tirando alguma complexidade do código. No Middleware desenvolvido os motores de BDs utilizados foram o PostgreSQL, sendo que este foi utilizado pelo à vontade e conhecimento já adquirido sobre o mesmo. A biblioteca de validação da framework denomina-se de spring-boot-starter-validation e permite a adição de anotações antes das variáveis da classe. Quando é invocado um método HTTP com Body a framework consegue construir um POJO diretamente através dos dados contidos no Body e no caso de algum dos parâmetros não corresponder às anotações colocadas será lançada uma exceção, sendo que isto faz com que durante o código não seja preciso verificar se os campos tem o formato correto porque a framework tratou disso automaticamente quando recebeu o pedido HTTP. 49Visual Paradimg: https://www.visual-paradigm.com/ 50Spring Boot: https://spring.io 71
Das rotas presentes na Tabela 17 a rota número 1 e 5 não necessitam de qualquer tipo de autenticação, podendo assim ser acedidas livremente. As rotas 2 a 4 necessitam de autenticação através de um JWT gerado na rota número 1. As restantes rotas, 6 e 7, são rotas de administração, as quais são autenticadas por um JWT com uma chave secreta incorporada. # Método Rota 1 POST /api/projects 2 GET /api/projects/{projectId} 3 DELETE /api/projects/{projectId} 4 PUT /api/projects/{projectId} 5 GET /api/brokers 6 POST /api/brokers 7 PUT /api/brokers/{brokerId} Tabela 17: API Rest do serviço Registration. As rotas 1 a 4 são utilizadas para a gestão dos projetos, a rota 1 é utilizada para a criação de novos projetos, depois de criados pode obter-se, apagar-se ou editar-se estes através das rotas 2 a 4, respetivamente. A edição atualmente permite adicionar ou remover sistemas de queues para o projeto em questão. Com a rota 5 é possível saber quais os sistemas de queues atualmente suportados pelo Middleware e qual a versão que está atualmente em vigor para esse sistema66. Por forma a não ser necessário parar este serviço as rotas 6 e 7 permitem a adição e edição de sistemas de queues, respetivamente. Assim, este componente está sempre atualizado em relação aos sistemas de queues suportados sem a necessidade de reiniciar o mesmo, sendo apenas necessário reiniciá-lo para a adição de novas funcionalidades ou correção de erros. 5.2.2.2 Middleware De seguida serão ilustrados os pontos de entrada do serviço Middleware, sendo que os dados necessários, respostas e erros de cada ponto de entrada se encontram nos Anexos B.2.1, B.2.2 e B.2.3, respetivamente. Na Tabela 18 é possível observar todos os pontos de entrada no serviço Registration através da sua API RESTful. Rotas cujo parte do caminho esteja entre chavetas ({}) indicam que essa mesma parte é variável. 66As versões atualmente disponíveis são a 3.9.9 para o RabbitMQ, a 2.8.1 para o Kafka e a 5.16.3 para o ActiveMQ5. 78
# Método Rota 1 POST /api/senders 2 GET /api/senders/{userId} 3 DELETE /api/senders/{userId} 4 POST /api/senders/connect 5 POST /api/senders/{userId}/send 6 POST /api/senders/close 7 PUT /api/senders/{userId}/queue 8 PUT /api/senders/{userId}/strategy 9 PUT /api/senders/{userId}/broker 10 POST /api/receivers 11 GET /api/receivers/{userId} 12 DELETE /api/receivers/{userId} 13 POST /api/receivers/connect 14 GET /api/receivers/{userId}/receive 15 POST /api/receivers/close 16 PUT /api/receivers/{userId}/queue 17 PUT /api/receivers/{userId}/strategy 18 PUT /api/receivers/{userId}/broker 19 PUT /api/receivers/{userId}/persistence 20 GET /api/receivers/{userId}/message/{messageId} 21 DELETE /api/receivers/{userId}/message/{messageId} Tabela 18: API Rest do serviço Middleware. As rotas 1 a 3 e 7 a 9 são as rotas para as operações CRUD sobre utilizadores do tipo produtor, correspondendo a criação à rota 1, leitura à rota 2, eliminação à rota 3 e atualização às rotas 7 a 9. As rotas 10 a 12 e 16 a 19 são as rotas para as operações CRUD sobre utilizadores do tipo consumidor, correspondendo a criação à rota 10, leitura à rota 11, eliminação à rota 12 e atualização às rotas 16 a 19. Para a gestão das mensagens recebidas a rota 20 permite que o utilizador obtenha novamente uma mensagem e a rota 21 permite a eliminação destas. 79
5.3 Decisões de Implementação Na presente secção serão ilustradas decisões e assunções realizadas ao nível do desenvolvimento do Middleware em função do conhecimento que foi adquirido. De seguida na Secção 5.3.1 são ilustradas as decisões tomadas em relação ao RabbitMQ sobre problemas já conhecidos sobre este, na Secção 5.3.2 é relatada a decisão efetuada sobre os tempos de persistência das mensagens e na Secção 5.3.3 é apresentada a assunção realizada sobre o DNS. 5.3.1 RabbitMQ O RabbitMQ é um sistema de queues bastante diferente dos demais, pois um produtor em vez de publicar uma mensagem para uma Queue publica esta para um Exchange, sendo depois responsabilidade do Exchange entregar a mensagem a uma ou mais Queues dependendo do tipo de Exchange (direct, topic, fanout ou headers). Atualmente existe um problema com os Exchanges já documentado na própria documentação do RabbitMQ (https://www.rabbitmq.com/ae.html), sendo que este problema consiste em que quando uma mensagem é entregue a um Exchange e este não consegue entregar a mensagem a nenhuma Queue. Esta acaba por ser descartada sem conhecimento do produtor que a produziu. A forma de resolver este problema é configurar um Alternate Exchange (AE) sendo este passado aos restantes Exchanges no momento da sua criação. Um outro problema com o RabbitMQ é relativo às Queues e este pode ocorrer devido a 3 ocasiões (https: //www.rabbitmq.com/dlx.html). As ocasiões são as seguintes: • Mensagem expirada por TTL. • Mensagem descartada pela Queue por esta ter atingido o seu limite de mensagens persistidas simultaneamente. • Consumidor confirmou negativamente a receção da mensagem, sendo que isto é diferente de ocorrer um erro na receção da mensagem. Este problema é resolvido de forma idêntica ao anterior configurando um Dead Letter Exchange (DLX) e passando este aos restantes Exchanges no momento da sua criação. Tanto a solução de implementar um Alternate Exchange, como um Dead Letter Exchange não foram atualmente implementadas por não acrescentar qualquer valor ao Middleware que se pretendia desenvolver, pois este é um problema relativo apenas a um dos sistemas de queues suportados. 5.3.2 Tempo de Persistência das Mensagens Um consumidor pode ter um persistenceTime, ver Tabela 38, máximo correspondente ao valor máximo de um long67 (int 64) em Java, porém a data limite utilizada é calculada através do tempo especificado pelo consumidor mais o tempo atual em milissegundos, sendo que isto pode levar a um overflow do long (int64) em Java. 67Valor máximo do long é 9.223.372.036.854.775.807. 80
Quando um overflow ocorre em Java o valor torna-se negativo, por exemplo, somar o valor máximo do long mais 1 é igual ao valor mínimo do long68 em Java. Para a realização da eliminação por tempo da mensagem esta tem um atributo do tipo Date e este aceita como parâmetro um long que irá corresponder ao tempo em milissegundos a partir da meia-noite do dia 1 de janeiro de 1970. Ora se o long passado no momento da criação de um Date for negativo este terá uma data anterior a 1970 o que levará a mensagem a não ser persistida porque essa data já passou. Por forma a resolver o problema dos overflows é verificada se a soma do tempo de persistência especificado pelo consumidor e o tempo atual em milissegundos dá um valor negativo. No caso da soma dar um valor positivo utiliza-se o valor obtido na soma, caso contrário utiliza-se o valor máximo para a persistência da mensagem. No caso de a soma dar positiva é garantido que não existiu um overflow porque a soma do valor máximo permitido para um long com ele próprio é igual a -1. Isto é garantido até ao ano 292.278.994, pois a partir desse ano a leitura em milissegundos do tempo atual dará um valor negativo, caso não seja alterado, pelo que a soma do valor lido com o tempo especificado pelo utilizador poderá dar um valor positivo apesar de ter ocorrido um overflow, fazendo com que a mensagem não seja persistida. No entanto esta decisão no caso de não se poder somar os dois valores por causa de overflow leva a que a mensagem não fique persistida durante o tempo especificado pelo utilizador, porém esta fica persistida até ao ano 292.278.994. Apesar desta decisão sobre o tempo de persistência da mensagem devido ao sistema de BD utilizado as mensagens não são eliminadas quando esse tempo passa. Este problema pode ser resolvido utilizando Jobs da framework Spring Boot, mas estes não foram implementados uma vez que em nada prejudicam os consumidores. 5.3.3 Domain Name System (DNS) No momento do registo de um projeto na resposta é retornado o DNS de forma a não retornar o IPv4 para o utilizador, rota 1 da Tabela 27. Porém este DNS retornado não é atualmente válido por 2 motivos, não está a ser verificado pelo que pode existir um DNS igual e no caso de não existir não está a ser registado. Optou-se por retornar assim o DNS assumindo que existe um algoritmo para verificação e registo de DNS, sendo o DNS retornado o resultado final desse tal algoritmo. Apesar disto ser um problema, como o deployment do Middleware é realizado no GCP este permite a colocação de IP externos estáticos nas máquinas virtuais, pelo que os Middlewares possuem atualmente IP externos estáticos já que não existe um DNS verdadeiro para a resolução do IP da máquina virtual. Este IP estático podia ser retornado no momento do registo do projeto se o deployment do Middleware estivesse automático, mas como atualmente este é manual é então retornado o possível DNS a utilizar para a instância do Middleware. 68Valor mínimo do long é -9.223.372.036.854.775.808. 81
6 Caso de Estudo Com o intuito de demonstrar as funcionalidades e a utilização deste Middleware foi criado um caso de estudo. Este caso de estudo além de demonstrar as funcionalidades do sistema foi utilizado para a realização dos testes de desempenho presentes no Capítulo 7. Durante a conceção e implementação do Middleware salientou-se que existiam dois tipos de utilizadores, os produtores (Producers/Senders) e os consumidores (Consumers/Receivers), e que existem aplicações ou microsserviços que apenas produzem ou consumem mensagens, mas existem também alguns que produzem e consomem ao mesmo tempo, pelo que o caso de estudo é um serviço que consome e produz mensagens. Optou-se por este serviço do caso de estudo coincidir com as aplicações ou microsserviços que são simultaneamente produtores e consumidores de mensagens para não existir a necessidade de criar dois serviços distintos para demonstrar as funcionalidades do Middleware. O caso de estudo criado gera inicialmente 4 consumidores e 1 produtor. Foram criados vários consumidores de forma a ser possível representar várias das funcionalidades desenvolvidas. Os consumidores são criados com as especificações presentes na Tabela 19, apesar da password ser um campo obrigatório não foi aqui representado porque sendo um caso de estudo foi utilizado o valor password para todos os consumidores. Campos Consumidor 1 Consumidor 2 Consumidor 3 Consumidor 4 username consumer1 consumer2 consumer3 consumer4 broker rabbitmq rabbitmq rabbitmq kafka strategy direct direct topic topic exchange teste teste temporary — queue studycase xpto example studycase routingKey — teste example — persistenceTime 30.000 0 0 15.000 Tabela 19: Especificações dos consumidores criados. No caso do consumidor 1 como o sistema de queues (broker) é o rabbitmq e a estratégia é direct, o campo routingKey não é obrigatório ficando depois com o valor do campo queue. Os consumidores criados poderiam ter mais atributos, porém com os sistemas de queues e estratégias selecionadas estes não seriam utilizados pelo que não foram adicionados. O valor 30.000 e 15.000 para o atributo persistenceTime indica que as mensagens recebidas por estes produtores ficaram persistidas apenas por 30 e 15 segundos, respetivamente. 82
Na Tabela 20 estão especificadas os valores dos campos para os produtores criados, tal como nos consumidores o campo password não é especificado porque o valor é igual para todos. Campos Produtor 1 username producer1 broker rabbitmq strategy direct exchange teste queue studycase Tabela 20: Especificações dos produtores criados. Probabilidades dos eventos: 1. Enviar mensagem utilizando a queue por omissão registada no Middleware: 30% 2. Enviar mensagem para uma queue diferente da que está registada no Middleware: 30% 3. Enviar mensagem com uma estratégia diferente da que está registada no Middleware: 30% 4. Reler uma mensagem que esteja persistida: 10% Estes eventos ocorrem em intervalos de 3 segundos. O evento de enviar uma mensagem para queue diferente força o produtor a enviar esta para a queue ”teste” e o evento de enviar uma mensagem para uma estratégia diferente força o produtor a enviar esta para a estratégia ”topic” com um campo extra, sendo que este campo é o exchange com o valor ”temporary”. O campo exchange tem de ser adicionado uma vez que o rabbitmq necessita deste campo para enviar para uma estratégia diferente. Para o evento de reler uma mensagem é escolhido aleatoriamente um consumidor e de seguida algum identificador de uma mensagem que já tenha lido. 6.1 Demonstração Por forma a ilustrar o caso de estudo a funcionar este sempre que efetua um dos 4 eventos anteriormente descritos imprime para o terminal do Intellij IDEA Ultimate os dados relevantes a esse evento. Além destes eventos os consumidores sempre que recebem uma mensagem imprimem a mesma para o terminal. Para demonstrar as restantes funcionalidades do Middleware, como a alteração de sistemas de queues, estratégia e queue, recorreu-se ao Postman para efetuar os pedidos de alteração de dados, tanto sobre os produtores como os consumidores. Após 15 segundos ocorreram os eventos presentes na Figura 89, sendo que nesta é possível observar que quando o producer1 envia mensagens para a queue por omissão estas são recebidas pelo consumer1, porém quando este envia para uma queue diferente quem recebe as suas mensagens é o consumer2 e quando envia para 83
uma estratégia diferente quem recebe é o consumer3. Na Figura 89 os retângulos a amarelo representam o envio de mensagens por parte do producer1. Os dois primeiros envios ocorreram para a queue por omissão, tendo depois o terceiro e quinto envios feitos para uma queue diferente e o quarto envio para uma estratégia diferente. As restantes cores representam diferentes consumidores. Figura 89: Envio de mensagens e leituras das mesmas. Na Figura 90 é ilustrado o pedido efetuado ao Middleware desenvolvido de forma a alterar o sistema de queues do producer1 e na Figura 91 encontram-se as impressões dos eventos realizados após esta alteração, com a exceção da primeira impressão que corresponde à última da Figura 89. Como se pode observar na Figura 91 o producer1 continuou a enviar mensagens para a queue ”studycase”, mas a para o sistema de queues Kafka em vez do RabbitMQ tal como estava inicialmente. Isto é comprovado com a mensagem a ser recebida pelo consumer4, que está a utilizar o Kafka, em vez do consumer1 que as estava a receber inicialmente no RabbitMQ. Na Figura 91 a vermelho encontra-se o último evento da Figura 89, a amarelo o envio do producer1 para a queue por omissão, após a sua atualização e azul claro a receção da mesma por parte do consumer4. Nesta encontra-se ainda a azul escuro o evento de reler uma mensagem por parte do consumer3, que resultou numa mensagem de erro de que a mensagem pretendida já não existia, sendo que isto ocorreu porque este consumidor está com a persistência de mensagens desativada. 84
Figura 90: Alteração do Sistema de queues no producer1. Figura 91: Envio e leitura de mensagens após alteração do Sistema de queues do producer1. Com as alterações anteriores o evento de enviar mensagens para uma estratégia deixou de fazer sentido, pois este faz com que o producer1 envie mensagens com a estratégia topic, mas essa já é a sua estratégia por omissão após a alteração efetuada na Figura 90. Porém o evento de enviar mensagens para uma queue diferente continua a ocorrer, mas estas não estão a ser impressas pelo caso de estudo porque não existe atualmente nenhum consumidor à escuta nessa queue. Após as alterações é possível observar que o producer1 continuou a funcionar sem ter sido necessário reiniciá-lo. 85
Na Figura 92 é possível observar o pedido realizado ao Middleware desenvolvido de forma a alterar o sistema de queues do consumer2, além do sistema de queues foi também a estratégia pois o sistema de queues Kafka apenas funciona com a estratégia topic e ainda a queue, de forma a que este consumidor capte as mensagens enviadas pelo producer1 quando as mesmas são enviadas para a queue diferente da queue por omissão. Figura 92: Alteração do Sistema de queues no consumer2. Após estas alterações os resultados do envio e receção de mensagens no caso de estudo são os ilustrados na Figura 93. Nesta é possível observar que quando o producer1 envia mensagens para uma queue diferente da por omissão o consumer2 passa a receber essas mensagens. Na Figura 93 é possível observar que o producer1 enviou 2 mensagens seguidas para a queue teste, porém o consumer2 apenas leu a segunda. Isto deveu-se ao facto de entre as duas mensagens enviadas ter sido efetuado o pedido de alteração de sistema de queues ao consumer2, pedido presente na Figura 92. Nesta figura o producer1 ainda enviou mais uma mensagem mas foi para a queue por omissão tendo sido capturada pelo consumer4. 86
Figura 93: Envio e leitura de mensagens após alteração do Sistema de queues do consumer2. Foi ainda utilizado o Postman para mais um pedido de alteração de dados do consumer2, sendo que este pedido levou dados de um dos sistema de queues suportado, mas com a estratégia incompatível. Este pedido está visível na Figura 94, bem como a resposta obtida do Middleware desenvolvido. Figura 94: Alteração do Sistema de queues no consumer2 com estratégia incompatível. Apesar de o pedido da Figura 94 ter falhado este consumidor continuou a funcionar e com a sua conexão SSE aberta, para demonstrar que este não foi abaixo foi realizado um novo pedido através do Postman, Figura 95, em que é colocado o consumer2 com a mesma configuração que o consumer4. Visto que estes estão a utilizar o Kafka com a estratégia topic, qualquer mensagem enviada pelo producer1 para a queue por omissão levará a que ambos recebam a mensagem. 87
sensivelmente os mesmos valores nos testes realizados. Com os resultados obtidos pode concluir-se que com a arquitetura concebida e a lógica adicionada, por forma a acrescentar funcionalidades de valor acrescentado, degradam o desempenho do Middleware em relação aos sistemas de queues, sendo os resultados deste em parte justificados pelos pontos acima descritos. 94
8 Conclusão Com o crescente número de sistemas de queues no mercado e as dependências inseridas nas aplicações que utilizam estes fazem com que muitas destas aplicações tenham de ser bastante reestruturadas quando necessitam de trocar de um sistema de queues para outro. O Middleware desenvolvido durante esta dissertação alcançou os objetivos traçados, fazendo a integração de 3 sistemas de queues (RabbitMQ, ActiveMQ5 e Kafka) e permitindo assim às aplicações ficarem apenas dependentes do Middleware desenvolvido em vez dos sistemas de queues. Com este as aplicações podem trocar de um sistema de queues para outro sem a necessidade de serem reestruturadas e podem ainda usufruir de algumas características de valor acrescentado que foram adicionadas ao mesmo. Este Middleware respeita os requisitos identificados e com o estudo prévio sobre os sistemas de queues já existentes foi possível tirar o máximo partido dos mesmos. Este estudo foi fulcral para o desenho da arquitetura do Middleware, pois sem este estudo prévio muitos problemas seriam apenas detetados na fase de desenvolvimento e teste. Após a conclusão deste Middleware foi realizado um caso de estudo que permitiu demonstrar as funcionalidades do Middleware desenvolvido e além deste foi desenvolvido um caso de teste que permitiu verificar e calcular a degradação do desempenho na utilização dos sistemas de queues através deste. Com o resultado do teste de desempenho foi possível concluir que a arquitetura utilizada e a lógica introduzida degradaram bastante o tempo que uma mensagem demora a ser entregue ao consumidor após ter sido criada. Porém existem caraterísticas de valor acrescentado como a troca de um sistema de queues para outro sem ser necessário reiniciar a aplicação e a possibilidade de um consumidor persistir as suas mensagens do lado do Middleware para as reler quando necessitar. Apesar da degradação do tempo que uma mensagem demora a ser entregue a um consumidor as caraterísticas de valor acrescentado são uma mais valia para algumas aplicações, sendo que isto irá depender da área de negócio de cada uma das aplicações que necessite de comunicar de forma assíncrona. Um ponto a melhorar no Middleware desenvolvido é a divisão dos microsserviços, uma vez que o produtor e consumidor tem bastantes dados em comum e como uma aplicação pode ser produtora e consumidora ao simultaneamente faria sentido existir um componente extra para gerir os dados destes, deixando assim o microsserviço Sender apenas com a responsabilidade de enviar mensagens e o microsserviço Receiver com a responsabilidade de receber as mesmas. 8.1 Trabalho Futuro Tal como em todos os projetos e aplicações existem sempre melhorias a efetuar e funcionalidades a acrescentar. Esta dissertação não é exceção, pelo que para trabalho futuro ficam os seguintes pontos: • Desenvolver o deployment automático das instâncias para os novos projetos e também para as atualiza95
ções feitas aos projetos já existentes. • Implementar os requisitos não prioritários que não foram implementados, como o agendamento de mensagens. • Substituir a comunicação HTTP por comunicação HTTPS. • Implementar Access Tokens com Refresh Tokens. • Adicionar novos sistemas de queues, principalmente os que oferecerem maior relevância para as necessidades da comunidade. • Melhorar a escalabilidade do sistema, como por exemplo passando de Docker Swarm para Kubernetes. • Adicionar a possibilidade de utilização dos sistemas de queues em cluster ou federation, sendo no momento da criação do projeto especificado a quantidade de sistemas de queues e qual a arquitetura de comunicação entre eles. • Tratar das mensagens descartadas pelo RabbitMQ, Secção 5.3.1. • Adicionar painel de administração onde seja possível gerir todos os sistemas de queues, produtores e consumidores para uma instância. 96
Bibliografia [Act] ActiveMQ. ActiveMQ Artemis STOMP.https://activemq.apache.org/components/ artemis/documentation/latest/stomp.html. Acedido: 2020-12-13. [Act19a] ActiveMQ. ActiveMQ - Delay and Schedule Message Delivery.https://activemq.apache. org/delay-and-schedule-message-delivery. Acedido: 2020-12-23. 2019. [Act19b] ActiveMQ. ActiveMQ AMQP.https://activemq.apache.org/amqp. Acedido: 202012-13. 2019. [Act19c] ActiveMQ. ActiveMQ STOMP.https://activemq.apache.org/stomp. Acedido: 202012-13. 2019. [Ahu21] Madhur Ahuja. Architecture and Monitoring Apache ActiveMQ with Grafana.https://www. metricfire.com/blog/architecture-and-monitoring-apache-activemqwith-grafana/. Acedido: 2021-04-08. Mar. de 2021. [Akg13] Faruk Akgul. ZeroMQ. Birmingham, UK: Packt Publishing, 2013. ISBN: 978-1-78216-104-2. [Alv+18] Oscar Alvear et al. «Crowdsensing in Smart Cities: Overview, Platforms, and Environment Sensing Issues». Em: Sensors 18 (fev. de 2018), p. 460. DOI: 10.3390/s18020460. [Bez10] Yuri Morais Bezerra. «Multi-MOM : um middleware multi-paradigma, extensível e orientado a mensagens para computação móvel». Tese de mestrado. Brasil: Departamento de Informática, Universidade Federal da Paraíba, 2010. [CMS16] Stipe Celar, Eugen Mudnic e Zeljko Seremet. «State-Of-The-Art of Messaging for Distributed Computing Systems». Em: 2016 DAAAM 27th International Symposium on Intelligent Manufacturing and Automation. 2016, pp. 298–307. ISBN: 9783902734082. DOI: 10 . 2507 / 27th.daaam.proceedings.044. [DE17] Philippe Dobbelaere e Kyumars Sheykh Esmaili. «Kafka versus RabbitMQ: A Comparative Study of Two Industry Reference Publish/Subscribe Implementations: Industry Paper». Em: DEBS ’17. Barcelona, Spain: Association for Computing Machinery, 2017, pp. 227–238. ISBN: 9781450350655. DOI: 10.1145/3093742.3093908. [Dea13] Nigel Deakin. Java Message Service. USA: Oracle, 2013. ISBN: 978-0-596-522049. [Dev17] Journal Dev. JMS API Overview: JMS 1.x and JMS 2.x.https://www.journaldev.com/ 9776/jms1-and-jms2-api-overview. Acedido: 2020-11-08. 2017. 97
[Dos14] David Dossot. RabbitMQ Essentials. Birmingham, UK: Packt Publishing, 2014. ISBN: 978-178398-320-9. [EA15] N. Estrada e H. Astudillo. «Comparing scalability of message queue system: ZeroMQ vs RabbitMQ». Em: 2015 Latin American Computing Conference (CLEI). 2015, pp. 1–6. DOI: 10. 1109/CLEI.2015.7360036. [EB15] S. El Mimouni e M. Bouhdadi. «Formal modeling of the Simple Text Oriented Messaging Protocol using Event-B method». Em: 2015 IEEE/ACS 12th International Conference of Computer Systems and Applications (AICCSA). 2015, pp. 1–4. DOI: 10.1109/AICCSA.2015. 7507170. [Fer] Tiago Fernandes. What is Dependency Injection? https://www.growin.com/blog/ what-is-dependency-injection/. Acedido: 2021-04-20. [Gar15] Nishant Garg. Learning Apache Kafka, Second Edition. Birmingham, UK: Packt Publishing, 2015. ISBN: 978-1-78439-309-0. [Gura] Refactoring Guru. Adapter.https : / / refactoring . guru / design - patterns / adapter. Acedido: 2021-02-12. [Gurb] Refactoring Guru. Facade.https://refactoring.guru/design-patterns/facade. Acedido: 2021-02-12. [Gurc] Refactoring Guru. Singleton.https : / / refactoring . guru / design - patterns / singleton. Acedido: 2021-02-12. [Hin] Pieter Hintjens. 0MQ - The Guide.https://zguide.zeromq.org/docs/chapter2. Acedido: 2021-01-22. [Ion15] V. M. Ionescu. «The analysis of the performance of RabbitMQ and ActiveMQ». Em: 2015 14th RoEduNet International Conference - Networking in Education and Research (RoEduNet NER). 2015, pp. 132–137. DOI: 10.1109/RoEduNet.2015.7311982. [Kle+15] A. F. Klein et al. «An experimental comparison of ActiveMQ and OpenMQ brokers in asynchronous cloud environment». Em: 2015 Fifth International Conference on Digital Information Processing and Communications (ICDIPC). 2015, pp. 24–30. DOI: 10 . 1109 / ICDIPC . 2015.7323001. 98
[Kon] Tugce Konuklar. Hexagonal (Ports & Adapters) Architecture.https : / / medium . com / idealo-tech-blog/hexagonal-ports-adapters-architecture-e3617bcf00a0. Acedido: 2021-04-20. [KS16] R. K. Kodali e S. Soratkal. «MQTT based home automation system using ESP8266». Em: 2016 IEEE Region 10 Humanitarian Technology Conference (R10-HTC). 2016, pp. 1–5. DOI: 10.1109/R10-HTC.2016.7906845. [LLZ12] X. Lu, W. Lei e W. Zhang. «The Design and Implementation of XMPP-Based SMS Gateway». Em: 2012 Fourth International Conference on Computational Intelligence, Communication Systems and Networks. 2012, pp. 145–148. DOI: 10.1109/CICSyN.2012.35. [MG17] Apurva Mehta e Jason Gustafson. Confluent.https://www.confluent.io/blog/ transactions-apache-kafka/. Acedido: 2020-12-20. Nov. de 2017. [Nai17] N. Naik. «Choice of effective messaging protocols for IoT systems: MQTT, CoAP, AMQP and HTTP». Em: 2017 IEEE International Systems Engineering Symposium (ISSE). 2017, pp. 1–7. DOI: 10.1109/SysEng.2017.8088251. [Rab20] RabbitMQ. RabbitMQ Protocols.https://www.rabbitmq.com/protocols.html. Acedido: 2020-12-23. 2020. [RMC09] Mark Richards, Richard Monson-Haefel e David A. Chappel. Java Message Service. USA: O’Reilly Media, Incorporated, 2009. ISBN: 978-0-596-522049. [SBD11] Bruce Snyder, Dejan Bosanac e Rob Davies. ActiveMQ in Action. USA: Manning Publications Co., 2011. ISBN: 978-1-933988-94-8. [Sch+14] D. Schuster et al. «Global-Scale Federated Access to Smart Objects Using XMPP». Em: 2014 IEEE International Conference on Internet of Things (iThings), and IEEE Green Computing and Communications (GreenCom) and IEEE Cyber, Physical and Social Computing (CPSCom). 2014, pp. 185–192. DOI: 10.1109/iThings.2014.35. [See13] Raphael Thomas Seebacher. «Messaging Challenges in a Globally Distributed Network». Tese de mestrado. Zürich, Deutschland: TIK, ETH Zürich, 2013, p. 14. [SM17] Dipa Soni e Ashwin Makwana. «A SURVEY ON MQTT: A PROTOCOL OF INTERNET OF THINGS (IOT)». Em: abr. de 2017. [STO12] STOMP. STOMP: The Simple Text Orientated Messaging Protocol v1.2.http://stomp. github.io/stomp-specification-1.2.html. Acedido: 2020-11-12. 2012. 99
[Wal19] Felix Walcher. «KNX to MQTT/AQMP». Tese de mestrado. Wien, Österreich: Fakultät für Informatik, Technischen Universität Wien, 2019, pp. 7–15. 100
A Diagramas de Classe A.1 Registration Figura 102: Diagrama de Classes do serviço Registration. 101
A.2 Sender Figura 103: Diagrama de Classes do serviço Sender. 102
A.3 Receiver Figura 104: Diagrama de Classes do serviço Receiver. 103
Variável Tipo de dados Obrigatório Localização username string sim Body password string sim Body broker string sim Body strategy string sim Body queue string semi Body exchange string semi Body headers object semi Body Authorization Bearer token sim Header Tabela 30: Campos da rota número 1. A Tabela 31 aplica-se também às Tabelas 34 e 36 a 38. Sistema de Queues Estratégia Campos obrigatórios RabbitMQ direct exchange e queue topic exchange e queue fanout exchange headers exchange e headers ActiveMQ5 direct queue topic queue Kafka topic queue Tabela 31: Combinação de campos semi obrigatórios para a rota 1, 5 e 7 a 9. A Tabela 32 representa os atributos para a rota 2 e 3 simultaneamente porque estas necessitam exatamente dos mesmos atributos. O atributo userId é um atributo gerado na rota 1 da Tabela 18 e retornado no interior do campo data da resposta, denominado de id e ilustrado na Tabela 50. Este atributo tem o formato UUID. Das Tabelas 34 a 37 o userId representado é o mesmo utilizado na Tabela 32. Nos Headers é necessário acrescentar um token no formato JWT no campo Authorization. Este token é gerado na rota número 4 da Tabela 18 e retornado no campo Authorization dos Headers da resposta HTTP. Este mesmo token é utilizado para as rotas 5 a 9. 110
Variável Tipo de dados Obrigatório Localização userId string sim Path Authorization Bearer token sim Header Tabela 32: Campos das rotas número 2 e 3. Para a rota número 4 são necessários dois atributos, o projectToken que é o token gerado na rota 1 presente na Tabela 17 e o Header Authorization que consiste num Basic Auth token. Este token é gerado através da codificação em base 64 do username e password do utilizador separados por ’:’, isto é, a codificação de <username>:<password>. Variável Tipo de dados Obrigatório Localização projectToken string sim Body Authorization Basic auth sim Header Tabela 33: Campos da rota número 4. Na rota número 5 são apenas obrigatórios três atributos, representados na Tabela 34, o userId e Authorization já referidos e o data que irá conter os dados da mensagem a ser enviada para os sistemas de queues. Variável Tipo de dados Obrigatório Localização data string sim Body queue string não Body exchange string não Body strategy string não Body headers object não Body userId string sim Path Authorization Bearer token sim Headers Tabela 34: Campos da rota número 5. 111
Variável Tipo de dados Obrigatório Localização userId string sim Body Authorization Bearer token sim Header Tabela 35: Campos da rota número 6. Variável Tipo de dados Obrigatório Localização queue string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 36: Campos da rota número 7. Variável Tipo de dados Obrigatório Localização strategy string sim Body queue string não Body exchange string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 37: Campos da rota número 8. 112
Variável Tipo de dados Obrigatório Localização broker string sim Body queue string não Body strategy string não Body exchange string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 38: Campos da rota número 9. O atributo persistenceTime presente na Tabela 39 representa o tempo em milissegundos que uma mensagem recebida por um consumidor vai ficar persistida. O valor mínimo para este atributo é 0, sendo o máximo 9.223.372.036.854.775.807. Este atributo segue as mesmas restrições para a Tabela 48. Na Tabela 39 o atributo queue já é obrigatório e não semi obrigatório como na Tabela 30. Os restantes atributos são semi obrigatórios porque dependem da escolha do atributo broker e strategy. A Tabela 40 ilustra quais são os atributos necessários para cada uma das combinações possíveis, sendo que esta aplica-se também às rotas 16 a 18, Tabelas 45 a 47, respetivamente. Variável Tipo de dados Obrigatório Localização username string sim Body password string sim Body persistenceTime int64 não Body broker string sim Body strategy string sim Body queue string sim Body exchange string semi Body routingKey string semi Body headers object semi Body Authorization Bearer token sim Header Tabela 39: Campos da rota número 10. 113
Sistema de Queues Estratégia Campos obrigatórios RabbitMQ direct exchange topic exchange e routingKey fanout exchange headers exchange e headers Tabela 40: Combinação de campos semi obrigatórios para a rota 10 e 16 a 18. O atributo userId presente na Tabela 41 é um atributo gerado na rota 10 da Tabela 18 e retornado no interior do campo data da resposta, denominado de id e ilustrado na Tabela 50. Este atributo tem o formato UUID. Para as Tabelas 43 a 49 este userId possui as mesmas restrições. Nos Headers é necessário acrescentar um token no formato JWT no campo Authorization, sendo que este token é gerado na rota número 13 da Tabela 18 e retornado no campo Authorization dos Headers da resposta HTTP. Este mesmo token é utilizado para as rotas 14 a 21, Tabelas 43 a 49. A Tabela 41 representa simultaneamente os atributos necessários para as rotas 11 e 12 por serem exatamente os mesmos. Variável Tipo de dados Obrigatório Localização userId string sim Path Authorization Bearer token sim Header Tabela 41: Campos das rotas número 11 e 12. As restrições para Tabela 42 são as mesmas apresentadas na Tabela 33. Variável Tipo de dados Obrigatório Localização projectToken string sim Body Authorization Basic auth sim Header Tabela 42: Campos da rota número 13. Variável Tipo de dados Obrigatório Localização userId string sim Path Authorization Bearer token sim Header Tabela 43: Campos da rota número 14. 114
Variável Tipo de dados Obrigatório Localização userId string sim Body Authorization Bearer token sim Header Tabela 44: Campos da rota número 15. Os atributos necessários e opcionais para a rota número 16 são os apresentados na Tabela 45, apesar desta ser a rota para atualização da queue a ser utilizada o próprio atributo queue não é obrigatório por causa do RabbitMQ, uma vez que os atributos routingKey e headers são atributos únicos para este sistema. Esta rota pode ser utilizada para atualização dos mesmos sem atualização da queue em si. Variável Tipo de dados Obrigatório Localização queue string não Body routingKey string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 45: Campos da rota número 16. Variável Tipo de dados Obrigatório Localização strategy string não Body queue string não Body exchange string não Body routingKey string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 46: Campos da rota número 17. 115
Variável Tipo de dados Obrigatório Localização broker string sim Body strategy string não Body queue string não Body exchange string não Body routingKey string não Body headers object não Body userId string sim Path Authorization Bearer token sim Header Tabela 47: Campos da rota número 18. Variável Tipo de dados Obrigatório Localização persistenceTime int64 sim Body userId string sim Path Authorization Bearer token sim Header Tabela 48: Campos da rota número 19. Os atributos para a rota 20 e 21 são ambos ilustrados na Tabela 49 uma vez que estes são iguais para as duas rotas. O atributo messageId segue o formato UUID, sendo que este é gerado na rota número 5 da Tabela 18 e retornado na rota número 14 dentro do campo data e denominado de id, ver Tabela 50. Variável Tipo de dados Obrigatório Localização userId string sim Path messageId string sim Path Authorization Bearer token sim Header Tabela 49: Campos das rotas número 20 e 21. B.2.2 Respostas Todos os pontos de entrada, com a exceção da rota número 14, seguem o formato anteriormente referido, sendo que em caso de sucesso o valor do appCode será sempre 1200 e o valor do status será sempre SUCCESS. 116
Como estes dois valores são sempre constantes em caso de sucesso, não serão ilustrados na Tabela 50, onde são ilustrados os valores dos campos message edata para cada uma das rotas. # Rota Mensagem Dados 1 Producer registered. id: ”ade34209-1abc-9f21-0baf-73ba942f983e” broker: ”rabbitmq” strategy: ”direct” exchange: ”teste” queue: ”myQueue” headers: { ”header”: ”oneHeader”, ”anotherHeader”: ”twoHeader”} 2 Producer info. Igual à rota 1. 3 Producer deleted. Igual à rota 1. 4 Producer logged in. Igual à rota 1. 5 Message successfully sent. null 6 Producer connection closed. null 7 Producer queue successfully replaced. Igual à rota 1. 8 Producer strategy successfully replaced. Igual à rota 1. 9 Producer broker successfully updated. Igual à rota 1. 10 Consumer registered. id: ”ade34209-1abc-9f21-0baf-73ba942f983e” persistenceTime: 5000 broker: ”rabbitmq” strategy: ”direct” exchange: ”teste” queue: ”myQueue” routingKey: ”myRouting” headers: { ”header”: ”fstHeader”, ”anotherHeader”: ”sndHeader”} 11 Consumer info. Igual à rota 10. 12 Consumer deleted. Igual à rota 10. 13 Consumer logged in. Igual à rota 10. 117
14 Server Sent-Event id: ”ade34209-1abc-9f21-0baf-73ba942f983e” data: ”Hello World.” queue: ”teste” 15 Consumer connection closed. null 16 Consumer queue successfully replaced. Igual à rota 10. 17 Consumer strategy successfully replaced. Igual à rota 10. 18 Consumer broker successfully updated. Igual à rota 10. 19 Consumer persistence successfully replaced. Igual à rota 10. 20 Message retrieved successfully. Igual à rota 14. 21 Message successfully deleted. Igual à rota 14. Tabela 50: Respostas aos pontos de entrada do serviço Middleware. É de realçar que na Tabela 50 nas rotas 1 a 4 e 7 a 9 alguns dos parâmetros como: exchange, queue e headers podem não estar definidos, valor null, e nas rotas 10 a 13 e 16 a 19 alguns dos parâmetros como: exchange, routingKey e headers podem não estar definidos (valor null). Isto acontece por devido aos atributos semi obrigatórios que podem não estar definidos caso não sejam necessários. A rota 14 apesar de na Tabela 50 apresentar a mensagem ”Server Sent-Event” esta mensagem não é enviada, apenas está presente para representar o tipo de resposta que é devolvida ao utilizador, pois os dados não serão apenas os apresentados, mas sim uma sequência de dados com o formato apresentado. B.2.3 Erros Existem vários erros neste serviço, sendo todos apresentados na Tabela 51 juntamente com a descrição presente na mensagem de erro. O campo Código da Tabela 51 representa o appCode e a Mensagem o campo message da resposta personalizada. O campo data estatus não são representados na Tabela 51 pelos mesmos motivos apresentados na Secção 5.2.2.1 em relação à Tabela 28. O erro BadRequest é lançado quando alguma das restrições apresentadas para cada uma das rotas não é cumprida. As reticências presentes na descrição deste erro representam a localização do campo que apresentava erros e o respetivo valor recebido. Os erros TokenProjectIncompatible e TokenDontBelongToUser ocorrem quando existem problemas com os tokens, como por exemplo o token conter informação sobre outro projeto ou o token conter informação sobre um utilizador diferente daquele que invocou o método, respetivamente. InvalidCredentials ocorre quando o utilizador tenta realizar o login e não obtêm sucesso, sendo que isto acontece porque o nome de utilizador ou a password estão incorretos. Quando um recurso não é encontrado ocorre o erro UserNotFound ou MessageNotFound, dependendo se o 118
recurso User, Producer ou Consumer, ou Message não existir, respetivamente. O erro JsonKeyInFault é lançado unicamente quando um dos atributos semi obrigatórios não é enviado. Por exemplo na utilização do sistema de queues RabbitMQ o campo exchange passa a ser necessário pelo que se não for enviado despolota este erro. Quanto aos erros ExchangeConflict e UserConflict como os nomes indicam são referentes a conflitos na criação de recursos, o primeiro é um erro específico à utilização do sistema de queues RabbitMQ, em que não podem existir exchanges com nomes iguais e estratégias diferentes. O segundo erro é quando se tenta criar um User mas já existe um com o mesmo nome de utilizador. BrokerNotSupported e BrokerStrategyIncompatible são erros relacionados com o suporte dos sistemas de queues e suporte das respetivas estratégias. Por exemplo se for criada uma instância com suporte para RabbitMQ e Kakfa, se se pretender utilizar o sistemas de queues ActiveMQ5 será lançado o erro BrokerNotSupported. O erro BrokerStrategyIncompatible ocorre por exemplo no caso de se escolher o sistema de queues Kafka com a estratégia direct. O erro InternalError pode ocorrer em variadas situações, sendo um erro bastante genérico para este serviço. CouldNotSendMessage ocorre quando um utilizador tenta enviar uma mensagem mas ocorre um erro na comunicação entre o serviço e o sistema de queues. O último erro da Tabela 51 é o CouldNotConnectToService e este ocorre no componente Discovery quando este tenta conetar com umas das réplicas de Receiver e esta tentativa ocorre sem sucesso. Nome Código Mensagem BadRequest 1400 Values with errors: ... TokenProjectIncompatible 1401 Token is invalid for this instance. InvalidCredentials 1402 Username and/or password invalid. TokenDontBelongToUser 1403 Token don’t belong to producer/consumer <userId>. UserNotFound 1404 Producer/Consumer <userId> don’t exist. JsonKeyInFault 1406 The following key is in fault on json - <key>. MessageNotFound 1407 Message <messageId> don’t exist. ExchangeConflict 1409 Specified exchange already exists with other strategy - <exchangeName>. (RabbitMQ specific) UserConflict 1410 Producer/Consumer <userId> already registered. BrokerNotSupported 1415 This instance doesn’t support the specified Queue System - <brokerName>. BrokerStrategyIncompatible 1416 Queue System and Strategy are incompatible. InternalError 1500 Something went wrong. 119