The REST of us – Parte 2 - Mobiliza
20/11/2014 Por Diogo Reus
No post anterior vimos quais foram as premissas que nortearam as pesquisas de Roy Fielding e, com o intuito de suporta-las, ele baseou a arquitetura REST em uma série de restrições que se tornaram uma espécie de checklist saber se suas arquitetura está de acordo com o que foi projetado para uma aplicação ser "rest compliant". Ao descrever essas restrições focaremos em: sua definição, como se dá sua implementação, sua relação com a arquitetura e como ela se relaciona com as premissas definidas:
Esta é uma das restrições mais básicas e que mais fundamentam a arquitetura. Tratamos essa restrição como Cliente-Servidor pelo fato de estarmos falando de serviços web. Em grande parte dos casos, contudo, podemos abstrair o conceito para uma comunicação onde temos o papel do requerente e do requerido em uma chamada.
Sua implementação exige que a lógica de um processo seja distribuída entre as duas pontas do processo garantindo que ambas se preocupem basicamente com sua área específica de atuação. Outro ponto importante é que o requerente não precisa conhecer a fundo o serviço requerido, e o inverso também se aplica, uma vez que ambos têm responsabilidades bem definidas e distintas.
Para o estilo arquitetural esta é uma restrição fundamental, fortalecendo o relacionamento entre o serviço e seu consumidor e principalmente da interface que medeia sua relação (Interface regular). Várias outras restrições se baseiam na arquitetura Cliente-Servidor para sustentar suas definições, bem como sustenta algumas das premissas definidas como Escalabilidade e Visibilidade.
Esta restrição afirma que nenhum estado do cliente deve ser armazenado no servidor, assim, toda requisição deve conter as informações necessárias para que o serviço consiga executar a ação requerida. Entre uma requisição e outra qualquer retenção de estado por parte do servidor como forma de dar sentido a requisições futuras quebraria esta restrição.
Em termos de implementação esta restrição implica que o cliente é que guarda estado entre requisições e os comunica com o servidor para dar sentido a uma requisição. Assim, uma requisição qualquer é autossuficiente e, portanto o servidor se torna capaz de, uma vez respondida a requisição, não guardar qualquer informação relativa à mesma. Não retendo estado, o servidor consegue ser econômico na utilização de seus hardwares (CPU, memória e banda), diluindo esta responsabilidade com o cliente.
Esta restrição se fundamenta na restrição de comunicação pareada Cliente-Servidor e dá suporte para as restrições e funcionalidade de Cache e Sistema em camadas bem como vem implementar várias das premissas como Escalabilidade, Visibilidade e Confiabilidade. Contudo, implica negativamente na Performance, fazendo com que o cliente torne cada requisição autossuficiente, o que potencialmente pode resultar na necessidade de comunicar informação redundante, aumentando tráfego e processamento.
É a capacidade que o cliente, o servidor, ou qualquer mediador do processo tem de definir que uma certa resposta é passível ou não de ser armazenada e encaminhada para subsequentes chamadas com as mesmas características. Esta é característica importante para entrega informações estáticas ou de baixa dinamicidade, e pode ocorrer de maneira implícita (armazenado do lado do cliente), explícito (quando o servidor aponta se uma resposta é passível de cache e sua validade) ou negociado entre as pontas.
Esta restrição requer do sistema a capacidade de produzir uma métrica sólida para controle de cache, bem como estar preparado para responder requisições inteira ou parcialmente com dados do cache. Todo o processo de comparação entre requisições para definir se elas são compatíveis também é de responsabilidade do servidor.
Cache, por sua vez, afeta diretamente a Performance em diversos níveis, mas principalmente diminuindo o tempo de resposta médio significativamente, e liberando o servidor para operações que requerem o tratamento da requisição. Por outro lado, cache pode resultar em informações desatualizadas sendo respondidas como informação atualizada ao cliente, causando disparidade entre o estado atual de um recurso e o estado do cache válido. Vários intermediadores cuidam da invalidação do cache automaticamente, mas este é um assunto complexo e ainda tido como um dos pontos de maior atrito em desenvolvimento de sistemas distribuídos e foi essa complexidade que motivou a brincadeira:
Sistema em camadas:
REST é definido como um estilo arquitetural disposto ou passível de disposição em camadas onde cada camada se comunica somente com suas diretas antecessora e sucessora. Cada camada pode ser composta de "clientes" ou "servidores", com contratos de comunicação específicos ou estabelecendo eventos para processamento das informações entre essas unidades.
Em termos de cliente e servidor, essa restrição garante o desacoplamento, uma vez que a informação terá camadas de tratamento individualizadas em seu processamento. Para intermediadores a capacidade de estratificação e formalização da comunicação garante que cada uma das camadas execute funções específicas de fácil integração e reutilização.
Um sistema em camadas suporta principalmente as premissas de Cache e servidores Sem estado e está diretamente ligada à Interfaces regulares. Esta estrutura também vem a fundamentar Escalabilidade, Visibilidade, Simplicidade em detrimento da Performance, onde requisições acabam sendo retrabalhadas entre as camadas.
Código sob demanda:
Código sob demanda define a possibilidade de serviços baseados em REST responderem como representação de um recurso informação executável pelo cliente. Esta possibilidade de entrega de funcionalidades no entanto deve ser suportada pelo cliente criando um ambiente de execução propício para sua execução.
Este não pode ser considerado uma restrição uma vez que Roy aponta ele como sendo opcional na estruturação de uma arquitetura baseada em REST. Ele contudo acredita que esta funcionalidade, dependendo do escopo da aplicação, poderia fornecer a Escalabilidade e a Performance de execução necessárias em detrimento da Visibilidade e Simplicidade da arquitetura.
Interface regular:
Deixamos a restrição de Interface regular com última, contudo, é a restrição que dá cara à arquitetura e define sua estruturação primária. Em sua essência tem o conceito abrangente de que todos os serviços e seus consumidores, inseridos em um ambiente REST, devem compartilhar uma interface de comunicação comum.
Contudo, aqui Roy para de tratar em termos genéricos e determina tríade que rege toda a comunicação em uma arquitetura REST:
Sintaxe para identificação de recursos (URI):
São identificadores com os quais serviços expõem seus recursos. Recursos podem ser dados, pontos de processamento, arquivos ou qualquer elemento exposto pelo serviço. Importante ressaltar que essa restrição como descrita na dissertação de Roy não especifica um padrão para a sintaxe desses identificadores, contudo, a sintaxe mais utilizada é a sintaxe Web conhecida como URI(Uniform Resource Identifier).
A sintaxe URI pode ser simplificada através do esquema:
{esquema}://{autoridade}{caminho}?{query}#{fragmento}
Esquema: "HTTP", "FTP", "mailto", "URN", "tel", "rtsp", "file"
Autoridade: www.app.com
Caminho: /users
Query: ?fields=_id,content
Fragmento: #titleContent
Media types ou formatos de mídia:
Formato no qual o requerente negocia com o serviço os formatos na qual o requerente está apto a interpretar a resposta e em qual formato a mesma está sendo enviada. Quando falamos de HTTP os tipos de conteúdo comumente suportados são os definidos pela IANA. Esses contratos são negociados da seguinte maneira:
Accept: application/xml, application/json – Cliente declara formatos aceitos de resposta
Métodos são os tipos de operação a serem realizados em um recurso e são geralmente compartilhados entre os serviços de uma mesma coleção. Assim, com a padronização de uma sintaxe para os recursos de um serviço REST a padronização dos métodos padroniza a comunicação entre as duas partes do processo.
Uma vez que os métodos tendem a serem reutilizados através dos serviços eles são geralmente genéricos ou uniformes, assim, uma típica interface regular fornece um pequeno grupo de métodos abstratos responsáveis por indicar ações básicas de processamento dentro do sistema. Deste modo, quando são definidos os métodos como elementos de uma interface regular são padronizados e definidos os protocolos de comunicação que serão utilizados para expressar e transportar esse métodos. Como já é de se esperar o protocolo mais comum usado em arquiteturas REST é o HTTP que define 6 métodos-base em sua versão 1.1:
Aqui vale a pena fazermos um parênteses, pois, as definições descritas acima são implementações fortemente ligadas ao protocolo HTTP 1.1 das definições genéricas que Roy descreveu na sua tese.
In order to obtain a uniform interface, multiple architectural constraints are needed to
guide the behavior of In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of hypermedia as the engine of application state.
Com a tríade descrita acima são atendidos 3 dos 4 pontos que Roy apontou, restando apenas o que ele descreve como hypermedia as the engine of application state(HATEOAS), o que algumas pessoas consideram uma definição opcional da arquitetura, porém, ele é enfatico ao defendê-la. Vamos tratar mais sobre esse tema no terceiro post da série, pois, esse é um conceito de nome e definições complexas, contudo, sua implementação é razoavelmente simples, clarificando seus benefícios.
Com todos estes conceitos em mente vale sistematizar e exemplificar o que o estilo arquitetural é. REST significa Transferência de Estado Representativo (Representational State Transfer) o que quer dizer que o que é transferido entre as partes, neste caso cliente e servidor, nada mais é do que uma representação do estados de um recurso e aqui dois conceitos são muito importantes: estado e recurso.
Um recurso é um objeto genérico de dados que a aplicação, através de um identificador único(URI), expõe suas representações. OK, ainda não está claro?
Vamos supor que uma aplicação qualquer expõe o recurso de um usuário através do URI: www.app.com/api/user/1. Quando um cliente precisa deste recurso ele faz uma requisição para o servidor em uma formatação de dados específica, XML, JSON ou YAML por exemplo, e o que é fornecido para cliente é a representação do recurso no formato indicado e no seu estado atual.
Outras arquiteturas populares(SOAP, WS-*) se baseiam no conceito de RPC(Remote Procedure Call) que é uma tecnologia formulada para comunicações entre processos e que se tornou popular para arquitetura de sistemas distribuídos como forma do cliente disparar uma ação especifica no servidor. Como dá para perceber esta arquitetura se baseava em ações, assim a mesma chamada para acessar os dados do usuário poderia ser projetada da seguinte maneira: www.app.com/getUserById?id=1
Não foi por acaso que usei o verbo get na declaração da chamada acima, pois como já se pode notar arquiteturas baseadas no conceito de RPC necessitam de utilização de verbos para descrever as ações a serem executadas enquanto REST se baseia no recurso em si. O que se costuma dizer é que o foco saiu dos verbos descritivos das ações para os substantivos que identificam os recursos. O que alguns de vocês já devem ter percebido é que temos um furo na nossa lógica, quando acessamos um recurso, como no exemplo REST acima, não foi descrito em nenhum momento qual é ação a ser executada. Isso se dá pelo fato de que REST se baseia em uma série de verbos de ações(métodos) que são especificados na chamada ao recurso e não em seu URI. Falando de HTTP temos 4 principais métodos para operação de recursos:
GET: Método que representa a recuperação de informação, na forma de entidade, que foi identificada através do URI. Caso o URI exponha um processo o método GET trata do resultado desta operação. Na especificação HTTP 1.1 (RFC 2616) este método pode ser executado de maneira condicional ou parcial.
POST: Métodos identificados como POST, de maneira geral, são associados a operações definidas no lado do servidor sobre a entidade identificada no URI. Geralmente utilizado para criação de recursos onde o cliente não possui forma de identificar o novo recurso de maneira singular.
PUT: Este método recebe um bloco de informações que representam um recurso e os armazena sob URI determinado. Quando este método é utilizado sob um recurso existente o servidor deve considerar que a versão recebida é a vigente e em caso de um recurso inexistente o servidor define um novo recurso com as dadas características.
DELETE: Método bem direto que visa excluir um recurso identificado pelo URI.
Os métodos acima cobrem as ações básicas de gerenciamento de recursos, contudo, esta mesma especificação contém alguns outros métodos auxiliares:
OPTIONS: Requer do servidor informação sobre as opções de comunicações disponíveis para um dado serviço permitindo assim ao cliente identificar opções e requisitos da comunicação estabelecida.
HEAD: Método idêntico ao GET, contudo, o servidor omite o corpo da mensagem em sua resposta e retorna o mesmo cabeçalho de uma chamada "real". Método geralmente utilizado para obter meta-data sobre um recurso sem precisar trafegar o recurso no corpo da mensagem.
TRACE: Método utilizado para invocar na camada de aplicação um ciclo fechado de retorno da mesma chamada como corpo da resposta e permite uma análise pelo cliente do que está sendo recebido pelo servidor.
CONNECT: Método reservado para conexões proxy que podem ser dinamicamente alternadas.
Aqui vale a pena fazer uma menção honrosa a um método que não está na especificação vigente para o protocolo HTTP, contudo, é comum vários serviços exporem o método chamado PATCH para suprir a falta de um método de atualização parcial de um recurso. Como vimos anteriormente o método PUT quando utilizado sob um recurso existente trata os dados recebidos como o estado mais atual e sobrescreve o seu estado anterior.
Já deve ter ficado claro que é difícil falar de REST sem falar de HTTP mas, como foi dito no começo do primeiro artigo, Roy usou o protocolo como base na formulação da arquitetura e mesmo que soe estranho podemos dizer que o HTTP nada mais é do que um protocolo implementado sob os preceitos REST. É claro que aqui a linha do tempo fica estranha, pois, cronologicamente a implementação veio antes da própria especificação tornando esse mais um daqueles casos onde após a flecha lançada foi pintado o alvo em seu entorno. Contudo, não se pode tirar o mérito de Roy uma vez que a abstração dos conceitos que tornaram o protocolo HTTP tão popular foi de vital importância para sua formalização e replicação em outras arquiteturas de sistemas distribuídos.
Vamos fechar esse post por aqui com todas as restrições que Roy estipulou para parametrizar qualquer arquitetura que se baseia nos conceitos de REST e vendo também como é difícil desassociar a arquitetura de sua mais famosa implementação, o HTTP. A partir daqui vamos discutir a implementação da arquitetura na prática e como Leonard Richardson e Martin Fowler classificam-na no que eles chamam de maturidade de um sistema REST.