-
Notifications
You must be signed in to change notification settings - Fork 0
4. Fundamentação Teórica
Uma API (Application Programming Interface) é um software que possui um conjunto de serviços que intermediam o acesso às funcionalidades de um sistema operacional, de alguma aplicação ou mesmo de algum outro serviço. As APIs podem ser bibliotecas de códigos como a API Collections da linguagem Java, onde temos classes que respondem a métodos públicos. Porém, as APIs que estamos interessados são aquelas disponibilizadas via rede ou via web, como a API do Spotify, onde podemos pesquisar por artistas ou pelos álbuns do catálogo de música. Web API ou Web Services é uma API que fornece sua interface de comunicação através da rede (CIRIACO, 2009).
A Chamada de Procedimento Remota (Remote Procedure Call) RPC foi uma técnica de comunicação entre processos para construir aplicações distribuídas. Uma aplicação cliente invoca remotamente um método como se estivesse fazendo uma chamada de método local. Destacamos pela sua popularidade as implementações baseadas no paradigma da orientação a objeto do CORBA (Common Object Request Broker Architectures) (IBM - Brasil, 2012) e a Java RMI (Remote Method Invocation) (UFRJ. GTA/UFRJ, 2022), usadas na construção e conexão de serviços e aplicações. Contudo, tanto CORBA quanto Java RMI possuem especificações amplas e complexa (RICARTE, 2002), muito em função de ambos usarem protocolos construídos sobre TCP (JOHANN, 2011), o que dificulta o desenvolvimento da interoperabilidade.
Como reação a essa sofisticação limitante, empresas do porte da IBM e Microsoft desenvolveram a técnica SOAP (Simple Object Acess Protocol), baseada no padrão da arquitetura orientada a serviço (IBM, 2021). SOAP define interface de serviço e operações sobre serviços (web services) descritos por WSDL (Web Services Description Language) fazendo trocas de mensagens no formato XML (eXtensible Markup Language). Teve um crescimento rápido, mas novamente a complexidade dos formatos das mensagens, bem como a complexidade da especificação ao redor do SOAP inviabilizam a agilidade na construção de aplicações distribuídas. No contexto de desenvolvimento das aplicações distribuídas modernas, SOAP web services é considerada hoje mais uma tecnologia legada. A maioria das aplicações distribuídas dos dias atuais são desenvolvidas usando o estilo arquitetura REST.
REST (Representational State Transfer) é um modelo arquitetural usado para desenvolver Web API (OLIVEIRA, 2015). Portanto, não é uma tecnologia, é uma especificação que define a forma de comunicação entre componentes de software na web. Pode-se desenvolver API REST em qualquer linguagem de programação que ofereça bibliotecas de conectividade à rede. REST foi desenhado para utilizar protocolos de comunicação já existentes, como o HTTP. Esta arquitetura foi proposta por Fielding (2000), na qual ele formaliza um conjunto de melhores práticas e regras (constraints) para desenvolvimento de Web API.
Há dois papéis bem definidos na arquitetura de uma API: cliente-servidor ou consumidor-provedor. Há o papel de cliente ou consumidor da API, que pode ser uma aplicação front-end, mobile, web ou até mesmo outra API. A aplicação consumidora envia requisições para o servidor ou provedor da API que por sua vez responde às requisições.
Uma das grandes vantagens decorrente do trabalho do Fielding (2000) foi a separação entre cliente e servidor. Aplicações cliente e servidor devem poder evoluir separadamente, sem qualquer tipo de dependência entre elas, podendo serem substituídas sem interferir em nada na outra, desde que a interface entre elas permaneça inalterada. Dessa forma, temos uma maior flexibilidade e portabilidade, pois o sistema cliente pode evoluir independente do sistema provedor e vice-versa. Esses sistemas podem ser escritos e gerenciados por equipes ou até mesmo por empresas diferentes.
Outra regra ou constraint defendida por Fielding (2000) é que as APIs devem ter interface uniforme. Interface é uma espécie de contrato entre as partes, cliente e servidor, em que se estabelece um conjunto de operações bem definidas do sistema. Uma interface uniforme simplifica e desacopla a arquitetura, o que permite que ambos os lados evoluam de forma independente. As operações devem ser identificadas por URIs e deve-se usar um padrão de protocolo de comunicação para se interagir com a API. Usualmente é utilizado o protocolo HTTP e em decorrência, usamos os verbos ou métodos do protocolo para realizar diferentes operações, tais como GET, POST, PUT, DELETE e outros. No contrato também deve estar estabelecido que as respostas têm de ser padronizadas e devem incluir, inclusive, informações de como o cliente deve tratar a mensagem resposta.
Em função do desacoplamento proporcionado pela interface uniforme, REST prevê a possibilidade de sistemas em camadas, onde teríamos outros servidores entre o cliente e o servidor. Esses servidores intermediários podem oferecer uma camada de segurança, balanceamento de carga, caching, etc. Essas camadas não devem afetar nem a requisição do cliente nem a resposta do servidor, e mais, o cliente não deve sequer saber quantas camadas intermediaria existem.
Uma constraint importante da API REST é a característica de Stateless (sem estado), isto é, a aplicação não deve possuir estado. A requisição feita ao servidor deve conter tudo que for necessário para que seja devidamente processada. O servidor não pode manter uma sessão ou algum tipo de histórico de uso entre uma requisição e outra. O servidor não pode conhecer o cliente, no sentido de que o servidor não pode armazenar informações da requisição anterior. Não estamos dizendo que não se pode fazer autenticação ou armazenar informações do cliente, queremos dizer é que o servidor não armazena informações contextuais, tais como: quem é o cliente, se está logado ou não, qual foi a última operação que ele fez.
A API pode fazer caching das respostas das requisições. A título de exemplo, podemos pensar em uma lista de cidades ou estados. Quantas vezes isso é alterado dentro de um dia, um mês ou até um ano? Portanto, bastaria fazer essa requisição apenas uma vez e a API poderia dizer ao cliente que a resposta pode ser armazenada, e então o cliente guarda os dados em um cache interno. Quando o cliente fizer a mesma requisição novamente, o cache entra em ação e nem se chega a consumir a rede. Fazendo uso da característica REST de sistema em camadas, este serviço de caching pode ser feito por um servidor intermediário entre cliente e servidor atuando como um proxy. Isso melhora a escalabilidade da aplicação, bem como a performance, uma vez que diminui o número de hits no servidor ou a quantidade de acessos. Cache no REST não é uma obrigatoriedade, trata-se apenas de uma possibilidade a ser utilizada quando for necessário.
Outra característica opcional do REST é o código sob demanda. Por não se aplicar na maioria dos casos essa característica é muito pouco utilizada. A ideia é o servidor retorna algum código para ser executado no cliente, por exemplo, o servidor retornaria, junto com os dados tabulados, o código JavaScript responsável por montar um gráfico.
Cabe aqui fazermos uma observação, por vezes encontramos os temos REST e RESTful. REST é estilo arquitetural que possui as constraints, ou seja, é a especificação, já RESTful é a API desenvolvida em conformidade com as constraints. Os desenvolvedores puristas acreditam que uma REST API deve seguir fielmente os princípios do REST, sem exceção, conforme foi estabelecido por Roy Fielding (2000). Já os desenvolvedores pragmáticos defendem uma abordagem mais prática; eles implementam as constraints, mas estão abertos a fazerem concessões nos casos em que, seguir a rigor os canônicos do REST geraria complicações em demasia, que não agregaria tanto valor assim. Os desenvolvedores pragmáticos preferem renunciar à API ser 100% RESTful para tornar o desenvolvimento ou o uso da API mais simples.
REST não restringe uso de protocolos de comunicação, mas para se colocar em prática é preciso usar um, e o mais comum é o protocolo HTTP (Hypertext Transfer Protocol). Quando uma aplicação cliente faz uma requisição ao servidor de API, ela a faz enviando uma mensagem HTTP e o servidor, por sua vez, retorna uma mensagem de resposta ao cliente.
Cada mensagem de requisição enviada pelo cliente possui um método. O método do protocolo possui a semântica da operação a ser efetuada sobre um determinado recurso. É através do método que dizemos ao servidor qual tipo de ação queremos executar em certo recurso identificado pela URI. O protocolo HTTP possui vários métodos, por exemplo, o método GET é usado para recuperar dados, o POST para inserir novos dados, o DELETE para apagar, e o PUT costuma ser usado para modificar, mas por vezes encontramos implementações fazendo inserções.
As mensagens HTTP possuem o campo URI, que identifica o recurso que queremos dentro do servidor. Outro campo que compõe a mensagem é a versão (HTTP/versão) do protocolo. A maioria das APIs REST publicadas usam a versão 1.1, mas poderia ser alguma outra mais recente. A gRPC, que falaremos mais adiante, é concebida sobre HTTP/2.0. Atualmente a última versão do protocolo é a HTTP/3.0.
Os cabeçalhos trazem informações no formato de chave e valor. O servidor ou o cliente as utilizam para interpretar a mensagem de requisição ou de resposta. Há vários pares chave-valor pré-definidos no protocolo HTTP, mas é possível criar nossos próprios cabeçalhos customizados. Por último, temos o corpo da mensagem (payload), que pode ser opcional, a depender do método utilizado. É no corpo que enviamos os dados do cliente para o servidor e é no corpo da mensagem que estará contido os dados da resposta.
A forma de um cliente interagir com uma API é através de seus recursos (resources). Recurso é qualquer coisa disposta na web: um documento, uma página, um vídeo ou uma imagem. É algo que tem importância suficiente para ser referenciada como uma coisa no software: um catálogo de produtos ou um único produto, um pagamento ou uma nota fiscal.
REST usa URI (Uniform Resource Identifier) para identificar recursos. URI é um conjunto de caracteres que tem por objetivo dar uma identificação para os recursos de forma não ambígua. URL (Uniform Resource Locator) é um tipo de identificação de recurso, ou seja, é uma URI, mas além de identificar, ela também fornece a localização onde o recurso está disponível. Em suma, um recurso para ser alcançado precisa de uma URI, ou mais especificamente, precisamos de uma URL para requisitá-lo usando protocolo HTTP.
Os recursos possuem representações, que nada mais são do que códigos que representam o estado atual dos recursos. Como algumas representações usadas, podemos citar o JSON e XML para dados, JPEG para imagens, etc. O mesmo recurso pode ser visualizado ou representado de diferentes formas. Quando o cliente da API faz uma requisição, ele pode especificar qual representação ele deseja, isto é, qual formato ele consegue processar ou interpretar. Esta informação do tipo do formato ou representação é adicionada no cabeçalho HTTP da requisição com o nome de chave Accept. O valor dessa chave do cabeçalho é chamado de media-type. Tem diversos media-types padronizados, como application/json, application/xml. Essa indicação de qual formato o recurso é retornado é chamada de content negociation. Isto é, o consumidor está negociando com o servidor a representação que quer, e o servidor pode aceitar ou não.
Um conceito importante para API de modo geral é o da Idempotência. Uma operação idempotente significa que ela pode ser aplicada mais de uma vez sem que o resultado da primeira aplicação se altere. O método GET é idempotente, pois não importa quantas vezes se faça o GET sobre um recurso, o resultado da primeira operação não é afetado pelas demais. O método PUT, implementado para atualizar um valor, por exemplo, atualizar o preço de um produto para cinco reais. Aplicar a mesma atualização no preço para cinco reais não afeta o resultado da primeira atualização. É o mesmo caso de quando estamos editando um documento e clicamos no botão salvar seguidas vezes. A ação de salvar o documento da segunda até a enésima vez não altera em nada o resultado do primeiro salvamento, ou seja, o estado do recurso documento não é afetado pelas operações subsequentes, elas não geram efeito colateral na primeira.
Já o método POST que insere novos dados não é idempotente, uma vez que invocações sucessivas alteram o estado do recurso. Método seguro é aquele que não modifica recurso e método idempotente é aquele no qual invocações sucessivas não alteram o estado da aplicação. Logo, o método POST não é seguro nem idempotente. O GET é um método seguro e idempotente. Já o PUT é idempotente, mas não é seguro, pois altera o recurso.
gRPC é uma estrutura de desenvolvimento (framework) moderna de chamada de procedimento remota (Remote Procedure Call) desenvolvida pela Google. Prometida como de alto desempenho, pode ser executada em qualquer ambiente (multiplataforma), multilinguagem de programação, e capaz de conectar serviços de forma eficiente com e entre Data Centers, com suporte ao balanceamento de carga, rastreamento, verificação de integridade e autenticação. Indicada para computação distribuída para conectar dispositivos, aplicativos móveis e navegadores a serviços de back-end API (GRPC AUTHORS, 2021).
Muito dos ganhos que o gRPC traz se deve ao fato de ter sido construído sobre HTTP/2 (M. BELSHE, 2015), o que trouxe substancial melhora na latência. Com o HTTP/1.x (R. FIELDING, 1999), se o cliente desejar fazer requisições em paralelo, a fim de melhorar o desempenho, era necessário criar múltiplas conexões TCP, pois no HTTP/1.x apenas uma resposta pode ser enviada por conexão. Já a versão HTTP/2.0 oferece multiplexação da conexão TCP e comunicação bidirecional, com várias requisições e respostas sendo feitas na mesma conexão TCP, o que elimina o bloqueio de cabeça de linha (head-of-line blocking). HTTP/2.0 ainda realiza enquadramento binário e compressão dos dados do cabeçalho (GRIGORIK, 2013).
O gRPC torna a criação de serviços e aplicações distribuídas facilitada, pois a aplicação cliente faz chamadas a métodos diretamente na aplicação servidora, como se estivesse lidando com objetos locais. Como em muitos sistemas RPC, gRPC baseia-se na ideia de definição de serviço e especificação de métodos que podem ser chamados remotamente. No lado servidor a interface é implementada e executamos o servidor gRPC para tratar as chamadas do cliente. No lado cliente, o cliente tem o stub, a representação do servidor no lado cliente e que provê os mesmos métodos do servidor (GRPC AUTHORS, 2021).
Figura 1 – Visão geral da comunicação gRPC
Fonte: Site desenvolvedores Google, guia, Introdução ao gRPC
gRPC foi projetado para ser naturalmente poliglota. Clientes e servidores gRPC podem executar e conversar uns com os outros em vários ambientes, servidores na cloud com clientes desktop ou mobile, podendo ser escritos em qualquer linguagem com suporte ao gRPC.
As chamadas gRPC podem ser de quatro tipos. Unary RPC é a forma do HTTP/1.1 trabalhar, onde o cliente envia uma única requisição e obtém uma única resposta. Na Server Streaming RPC o cliente faz uma única requisição, porém, o servidor envia um fluxo (stream) de mensagens em resposta a solicitação do cliente. No modo Client Streaming RPC é o cliente que envia várias mensagens e o servidor retorna apenas uma mensagem de resposta. Na Bidirecional Streaming RPC o cliente e o servidor podem processar operações de leitura e escrita de mensagens em qualquer ordem, uma vez que os streams são independentes (CORE CONCEPTS... 2021).
gRPC permite que se especifique por quanto tempo se pode esperar por uma RPC. Ambos, cliente e servidor, podem determinar de forma independente se a chamada foi concluída, com sucesso ou não, e ambos os lados podem cancelar a chamada RPC em curso a qualquer momento (CORE CONCEPTS... 2021).
As definições de contrato de mensagens e serviço no gRPC são feitas em arquivos com extensões .proto
. Trata-se de um tipo de Linguagem de Definição de Interface (IDL) (GOOGLE DEVELOPERS, 2021). As mensagens são serializadas usando o Protobuf, um formato de mensagem binário eficiente, que resulta em cargas de mensagens pequenas. A partir da definição do arquivo .proto
, o framework gRPC gera código em uma linguagem de programação específica, por meio dos compiladores disponibilizados através de plugins.
Cliente e servidor podem estar implementados em linguagens diferentes e rodando em ambientes ou plataformas diferentes. Por exemplo, o compilador do lado cliente irá serializar o objeto Java em uma mensagem proto request, que por sua vez, será coletada da rede pelo servidor, e o código gerado pelo seu plugin compilador fará a reconstrução do objeto para a linguagem do compilador, por exemplo, C++, Python, JavaScript, Dart ou outras. Processada a requisição, o servidor converte o objeto C++ de sua mensagem de resposta em um proto response e retorna ao cliente. O que torna possível a tradução de objetos em diversas linguagens é que ambos implementam a mesma IDL, a mesma definição de interface de serviço, a saber, o arquivo .proto
, compreendido por todos os plugins-compiladores.
A concepção da linguagem Golang ou Go, aconteceu em setembro de 2007 através de Robert Griesemer, Ken Thompson e Rob Pike para solucionar desafios de engenharia enfrentados diariamente na Google.
A linguagem foi lançada em novembro de 2009 e atualmente é considerado um sucesso, sendo muito utilizada dentro e fora da Google, com efeitos notáveis em abordagens para concorrência de rede e engenharia de software através de outras linguagens e suas ferramentas.
Go é fortemente usada no coração das soluções de dados da Google para indexação de páginas web ao redor do mundo, fornecendo suporte ao Google Search e mantendo os dados de pesquisa atualizados e abrangentes.
Go também é aplicado em outras grandes tecnologias da Google, como em serviços de otimização de conteúdo do Google Chrome, serviços de hospedagem do Firebase e no seu setor de produção, auxiliando equipes de engenharia a atingir um alto padrão de confiabilidade.
Dart é uma linguagem desenvolvida pela Google, lançada na GOTO Conference 2011, voltada ao ágil desenvolvimento de aplicativos, buscando facilitar a experiência do cliente e o aumento de produtividade. Com seu chamado hot reload torna-se muito mais fáceis mudanças no código, correções de bugs, etc. Além de fornecer suporte a diversas plataformas, como web, dispositivos móveis e desktop. O Dart também é a base do Flutter, um framework para desenvolvimento de aplicativos multiplataforma.
É uma linguagem com segurança de tipagem, com verificação de tipo estática. A tipagem é obrigatória, porém a anotação de tipos é opcional, pois tem inferência de tipos. Oferece também tipos dinâmicos, que auxiliam na checagem durante execução, muito útil para realização de experimentos.
Outra característica do Dart é o null safety, o que significa que valores nulos não serão aceitos, a não ser que o desenvolvedor especifique para que sejam.
“Os Protocol Buffers fornecem um mecanismo extensível de plataforma neutra e de linguagem neutra para serializar dados estruturados de maneira compatível com versões anteriores e posteriores” (GOOGLE, 2022). Protocol Buffers é mais rápido e mais leve do que JSON.
Sua capacidade é de alguns megabytes, fornecendo a serialização de pacotes de informações para linguagem de baixo nível, sendo amigável ao tráfego e para armazenamento de dados a longo prazo. Possibilitam também a junção de novos dados ao pacote sem a invalidação de dados já existentes. Podem ser usados tanto na comunicação entre servidores como também no armazenamento em disco.
Vantagens que podem ser citadas dos Protocol Buffers são a compactação dos dados, a rápida leitura e análise, poder ser entendido por diversas linguagens de programação e inda fazer a geração de códigos por meio dos plugins .
Uma das vantagens mais notável dos Protocol Buffers é o fato de possibilitar com certa facilidade a comunicação, entre si, de diversas linguagens de programação. Atualmente, de acordo com a Google, há suporte para oito linguagens de programação diferentes, além das linguagens Dart e Go que são de domínio da própria empresa.
Na Figura 2, retirada do site para desenvolvedores da Google, é possível ter um entendimento do fluxo gerado através dos Protocol Buffers:
Figura 2 - Protocol buffers workflow
Fonte: Site desenvolvedores Google, guia, visão geral sobre Procol Buffers
O primeiro passo é definir a interface de serviço, que contém os métodos que permitiram aos clientes fazerem chamadas remotas, os parâmetros dos métodos e as definições das mensagens para serem usadas na invocação. Toda essa definição de serviço e estrutura de dados das mensagens ficam registrados em um arquivo .proto
.
A seguir, utilizamos o plugin específico da linguagem de programação desejada para compilar o arquivo .proto
e gerar códigos (protobuff code), usualmente contendo classes e outras estruturas que refletem a definição do serviço na linguagem específica.
Na terceira etapa, fazemos uso das classes geradas para implementar os métodos declarados e disponibilizar os serviços caso estejamos no lado servidor, ou se estivermos no lado cliente, usamos os códigos para conectarmos à API, a fim de podermos consumir os serviços disponibilizados.
Por último, usamos o protocol buffers para serializar nossos objetos, enviar pela rede e reconstruí-los do outro lado, sem nos preocuparmos com os detalhes da transmissão e serialização.
Éder Marques - @earmarques - [email protected]
All rights reserved - Distributed above GPL3 license. See LICENSE to more information.
-
Resumo
-
1. Introdução
-
2. Justificativa
-
3. Objetivos
-
4. Fundamentação Teórica
4.1. RPC Legado
4.2. REST
4.3. gRPC
4.4. Golang
4.5. Dart
4.6. Protocol Buffers
-
5. Trabalhos Similares
-
6. Metodologia
-
7. Desenvolvimento
7.1. JavaScript - Sorteador de número
7.1.1. Definição de contrato – sorteio.proto
7.1.2. Servidor gRPC – NodeJS
7.2. Golang – Fornecedor de id
7.2.1. Definição de contrato – gerador_id.proto
7.2.2. Servidor gRPC – Golang
7.3. Dart – Banco de dados
7.3.1. Definição de contrato – aluno.proto
7.3.2. Servidor gRPC de banco de dados e Cliente gRPC de Golang
7.4. Java – Aplicação Cliente
7.5. Simulação
-
8. Resultados e Discussões
-
9. Conclusões
-
Referências