Conceito fundamental
REST, acrônimo para Representational State Transfer, é um estilo arquitetural para sistemas hipermídia distribuídos. Ele foi introduzido e definido, no ano 2000, por Roy Fielding, em sua tese de doutorado. Trata-se de um conjunto de restrições arquiteturais aplicados a World Wide Web.
Apenas sistemas desenvolvidos observando corretamente o estilo REST podem ser identificados como RESTful.
Null Style
Em sua tese, Roy Fielding, defende a existência de duas abordagens para a elaboração de um design arquitetural.
A primeira abordagem é aquela onde um projeto de sistema começa “do zero” – como que em uma “tela em branco” – e vai sendo elaborado utilizando elementos familiares até que, em dado momento, satisfaça as necessidades pretendidas.
A segunda abordagem parte de um sistema “pronto”, com todos os elementos demandados para atender uma determinada necessidade já presentes, porém sem restrições. Então, restrições são aplicadas para “aparar” os elementos do sistema até que ele satisfaça apenas a um conjunto de especificações bem determinado. Este “ponto de partida”, sem restrições, é designado por Roy Fielding como Null Style.
REST, como estilo arquitetural, tem como Null Style a World Wide Web.
World Wide Web
A World Wide Web (WWW) é um sistema de informação onde documentos e outros recursos são identificados por Uniform Resource Locators (URLs, como https://example.com/), interligáveis por hiperlinks, acessíveis pela Internet.
Os recursos da Web são transferidos por meio de implementações de protocolos, como HTTP, e podem ser acessados pelos usuários por aplicativos navegadores e publicados por um aplicativos servidores.
O cientista inglês Sir Timothy Berners-Lee inventou a World Wide Web em 1989. Ele escreveu o primeiro navegador da web em 1990 enquanto trabalhava no CERN (European Organization for Nuclear Research) perto de Genebra, Suíça. O navegador foi lançado fora do CERN para outras instituições de pesquisa a partir de janeiro de 1991, e depois para o público em geral em agosto de 1991. A Web começou a entrar no uso diário em 1993-4, quando sites de uso geral começaram a se tornar disponíveis.
Definindo “recurso”
Para entender REST é fundamental assimilar o conceito de “recursos”. A RFC 2396, de 1998, define “recurso” como:
Um recurso pode ser qualquer coisa que tenha identidade. Os exemplos familiares incluem um documento eletrônico, uma imagem, um serviço (por exemplo, “previsão do tempo de hoje para Los Angeles”) e uma coleção de outros recursos. Nem todos os recursos são “recuperáveis” pela rede; por exemplo, seres humanos, empresas e livros encadernados em uma biblioteca também podem ser considerados recursos. O recurso é o mapeamento conceitual para uma entidade ou conjunto de entidades, não necessariamente a entidade que corresponde a esse mapeamento em qualquer instância específica no tempo. Assim, um recurso pode permanecer constante mesmo quando seu conteúdo — as entidades às quais atualmente corresponde — muda ao longo do tempo, desde que o mapeamento conceitual não seja alterado no processo.
URI e URL
URI, acrônimo para Uniform Resource Identificator, é uma identificação simples para um recurso. Enquanto isso, URL, acrônimo para Uniform Resource Locator, especifica também como este recurso deve ser acessado.
Todas URLs são URIs, mas nem toda URI é uma URL.
Elementos arquiteturais para REST
O estilo arquitetural REST define abstrações para os elementos arquitetônicos em um sistema hipermídia distribuído, enquanto ignora os detalhes de implementação.
Sistemas RESTful geralmente serão compostos por componentes, conectores que ligam esses componentes e os dados que eles utilizam.
Elementos arquiteturais relacionados com “Dados”
Sistemas RESTful se caracterizam pela movimentação dos dados através de componentes de processamento, em oposição a outros estilos onde os diversos componentes operam sobre uma única “fonte da verdade”. Essa característica, reforça a coesão, eventualmente comprometendo a eficiência.
Ao projetar dados, um componente “servidor” pode-se adotar uma das seguintes três estratégias:
- renderizar os dados para uma representação específica;
- enviar os dados em sua representação nativa, acompanhados de código que realiza a renderização para uma representação específica;
- enviar os dados em sua representação nativa, acompanhados de metadados para que o componente “cliente” adote uma estratégia de processamento;
São elementos arquiteturais relacionados com dados:
- recurso;
- identificador, geralmente uma URI ou URN;
- representações, como HTML, JSON e XML;
- metadados da representação, como cabeçalhos como media-type e last-modified
- metadados do recurso, com cabeçalhos como alternates e vary
- metadados de controle, com cabeçalhos como if-modified-since e cache-control
Elementos arquiteturais “Conectores”
Os conectores são as tecnologias responsáveis por acessar e transportar os dados entre dois ou mais componentes. Basicamente, eles podem ser agrupados em tecnologias clientes, servidoras, para caching, transporte (tunnel) e resolver (ex: servidores DNS).
Os principais tipos de conectores são “cliente” e “servidor”. A diferença essencial entre os dois é que um cliente inicia a comunicação fazendo uma requisiçã0, enquanto um servidor escuta as conexões e responde às requisições para fornecer acesso aos seus serviços. Eventualmente, um mesmo componente pode incluir conectores de cliente e de servidor.
Um terceiro tipo de conector, o conector de “caching”, pode ficar localizado próximo do “cliente” ou ou do “servidor” a fim de salvar respostas armazenáveis em cache para interações atuais para que possam ser reutilizadas para interações posteriores. Um cache pode ser usado por um cliente para evitar a repetição da comunicação da rede, ou por um servidor para evitar a repetição do processo de geração de uma resposta, com ambos os casos servindo para reduzir a latência de interação.
Elementos arquiteturais “Componentes”
Os componentes são elementos de processamento que se conectam a outros por meio de conectores. Os componentes mais comuns são o “cliente” e o “servidor”. Em um modelo em camadas, um mesmo componente pode ser, ao mesmo tempo, “cliente” e “servidor” realizando algum tipo de transformação ou segurança.
Sistemas RESTful são “cliente-servidor”
A primeira restrição definida for Fielding é que sistemas que seguem o estilo arquitetural REST sejam implementações cliente-servidor. Cabe ao componente “cliente” disponibilizar interfaces com o usuário. Enquanto isso, componentes “servidor” fornecem dados. Em sistemas RESTful, eventualmente, um mesmo componente de software poderá operar como cliente e servidor.
Um software navegador utilizado para navegar na internet atua como cliente. Afinal, envia requisições HTTP, para um servidor, de forma a acessar e manipular dados.
A separação de responsabilidades, segundo Fielding, reforça atributos de qualidade como portabilidade – permitindo que implementações “cliente” independentes sejam realizadas em diversas plataformas – e a escalabilidade – simplificando os componentes “servidor”. Além disso, por habilitar que “cliente” e “servidor” evoluam de forma independente, colaboram para o evolvability.
Componentes “servidor” são stateless
A segunda restrição definida por Fielding é que sistemas RESTful sejam stateless. Ou seja, cada requisição de componentes “cliente” deve conter todas as informações contextuais necessárias para serem atendidas, sem depender de qualquer dado de contexto armazenado no componente “servidor”. Assim, informações de sessão devem ser mantidas, inteiramente, apenas no componente “cliente”.
Por serem stateless, sistemas RESTful têm melhor visibilidade, confiabilidade e escalabilidade. Afinal:
- cada solicitação pode ser verificada, completamente, de maneira isolada, já que contem todos os dados necessários para a análise;
- por não manterem estado de contexto, é mais fácil recuperar o “lado servidor” de falhas parciais;
- por não haver necessidade de manter estado de contexto, sistemas RESTful são mais fáceis de implementar e de escalar.
Como pontos potencialmente negativos, a restrição de que componentes “servidor” sejam stateless implica em tráfego de dados maior na rede, pelo transporte da informação de estado contexto. Além disso, há potencial excesso de autonomia dos componentes “cliente” (que operam sem supervisão do servidor).
Recursos devem ser indicados como sendo “cacheáveis” ou “não-cacheáveis”
A terceira restrição definida por Fielding é que sistemas RESTful devem indicar a possibilidade de adoção de caching nos componentes “cliente”. Para isso, todas as respostas do componente “servidor” devem ser identificadas, implícita ou explicitamente, como armazenáveis ou não armazenáveis em cache.
A adoção de caching melhora a eficiência, colaborando para a escalabilidade e desempenho. Entretanto, dados armazenados em cache podem ficar desatualizados, antes de serem invalidados, comprometendo a confiabilidade.
Comunicações entre “cliente” e “servidor” devem ocorrer através de interface uniforme
A quarta restrição definida por Fielding é que as comunicações entre componentes “cliente” e “servidor”, em sistemas RESTful, ocorram através de interfaces uniformes (consistentes). Dessa forma, a implementação dos componentes “cliente” e “servidor” podem ocorrer de forma desacoplada (e independente).
Há quatro restrições arquiteturais adicionais derivadas da decisão de adotar interfaces uniformes:
- Recursos devem ser identificados consistentemente. Na prática, recursos em um sistema RESTful devem estar associados a identificações estáveis, ou seja, que não mudam durante as interações, tampouco quando o estado interno do recurso (conjunto de dados que o compõe) é alterado.
- Recursos devem ser manipulados a partir de representações. A representação de um recurso é composta por uma sequência de bytes e metadados que a descrevem. Opcionalmente, uma representação também contém metadados que descrevem o recurso em si. Em implementações REST usando HTTP, por exemplo, entidades de domínio são recursos que podem ser expostos em representações usando XML, JSON e mais; enquanto isso, metadados descrevendo uma representação são fornecidos no cabeçalho;
- Mensagens devem ser auto-descritivas. Ou seja, devem conter todas as informações para que o receptor – cliente ou servidor – tenha condições realizar interpretação correta, sem necessitar de mensagens adicionais ou documentação em separado.
- Hipermídia deve ser utilizada como mecanismo de estado (HATEOAS). Utilizando hiperlinks e, possivelmente, modelos consistentes de URI, como parte dos metadados que descrevem o recurso, seja para descrever documentos ligados como ativações de estado.
HATEOAS
Hypermedia as the Engine of Application State, ou HATEOAS, é uma “maneira” de implementar APIs REST utilizando hipermídia para indicar que ações ou navegações estão disponíveis para um determinado recurso. Estas ações e a navegação são derivadas do estado do recurso e, eventualmente, da própria API. Elas são disponibilizadas para o cliente através de uma coleção de links.
{ “custormerId”: 1, “firstName”: “John”, “lastName”: “Doe”, ... links: [] }
Não há uma definição universalmente aceita sobre onde esta coleção de links deve ser fornecida. Há quem defenda a inclusão na representação do recurso. Outros, defendem a utilização dos cabeçalhos da “response HTTP”. Quanto aos dados necessários em cada link, há a RFC (5598).
O link para Self
O primeiro link da coleção deveria ser uma referência para o próprio objeto.
{ “custormerId”: 1, “firstName”: “John”, “lastName”: “Doe”, ... links: [{ “rel”: “self”, “href”: “http://mydomain/customers/1” }] }
Dessa forma, caso a representação do recurso “passeie” pela aplicação cliente, ou, caso ela, eventualmente, seja persistida ou compartilhada com outros contextos, em qualquer momento, seria possível determinar sua origem sem “inferências no código”.
Links para as ações suportadas pelo recurso
Imagine-se concebendo uma interface para o usuário. Nela, deve ser possível alterar os dados de um recurso ou solicitar sua exclusão. Eventualmente, alguns recursos (um pedido, por exemplo) não podem ser alterados sob certas condições. Ou ainda, não podem ser excluídos. Como e onde a “lógica” para determinar as ações possíveis deve ser implementada?
{ “custormerId”: 1, “firstName”: “John”, “lastName”: “Doe”, ... links: [{ “rel”: “self”, “href”: “http://mydomain/customers/1” },{ “rel”: “update”, “href”: “http://mydomain/customers/1” },{ “rel”: “delete”, “href”: “http://mydomain/customers/1” }] }
Fornecer a lista de ações possíveis para um recurso na coleção de links permite que essa lógica seja implementada totalmente no serviço e simplifica muito a implementação da aplicação cliente. O botão “excluir”, por exemplo, estaria disponível apenas se o link correspondente estivesse na coleção.
Outra vantagem de retornar a lista de links com as ações disponíveis é que a lógica pode mudar no servidor, incluindo os endereços, sem alteração alguma no código da aplicação cliente.
Links para navegação
Eventualmente, um recurso retornado pela API possuirá outros recursos relacionados. Por exemplo, em uma aplicação empresarial, um “cliente” possuirá uma coleção de “pedidos” que realizou.
{ “custormerId”: 1, “firstName”: “John”, “lastName”: “Doe”, ... links: [{ “rel”: “self”, “href”: “http://mydomain/customers/1” },{ “rel”: “update”, “href”: “http://mydomain/customers/1” },{ “rel”: “delete”, “href”: “http://mydomain/customers/1” },{ “rel”: “orders”, “href”: “http://mydomain/customers/1/orders” }] }
A presença desses links evita que a aplicação cliente precise fazer “inferências” sobre como “chegar” em recursos relacionados, diminuindo acoplamento. Essa ação autoriza, por exemplo, a mudança na estratégia de rotas sem afetar a aplicação cliente.
{ “custormerId”: 1, “firstName”: “John”, “lastName”: “Doe”, ... links: [ ..., { “rel”: “orders”, “href”: “http://mydomain/orders?customerId=1” ] }] }
Não há restrições quanto a utilizar HTTP POST para atualizações
Uma das “verdades absolutas” sobre REST, que não é verdade, é a ideia de que o verbo HTTP POST não pode ser utilizado para atualizar recursos. Aliás, o próprio Roy Fielding é bastante opinativo a este respeito.
A única coisa que o REST exige dos métodos é que eles sejam definidos uniformemente para todos os recursos (ou seja, para que os intermediários não precisem saber o tipo de recurso para entender o significado da solicitação). Enquanto o método está sendo usado de acordo com sua própria definição, REST não tem muito a dizer sobre isso.
Por exemplo, não é RESTful usar GET para executar operações inseguras porque isso violaria a definição do método GET em HTTP, o que, por sua vez, enganaria intermediários e spiders. Não é RESTful usar POST para recuperação de informações quando essas informações correspondem a um recurso potencial, porque esse uso impede a reutilização segura e o efeito de rede de ter um URI. Mas por que você não deve usar o POST para realizar uma atualização? O hipertexto pode informar ao cliente qual método usar quando a ação executada não for segura. PUT é necessário quando não há hipertexto dizendo ao cliente o que fazer, mas a falta de hipertexto não é particularmente RESTful. (Roy Fielding)
Implementações do “servidor” podem ser compostas em camadas
A quinta restrição definida por Fielding é que componentes “servidor” podem ser desenvolvidos a partir de composições que obedecem o estilo de arquitetura em camadas. Ou seja, componentes “cliente” tem acesso apenas a camada mais externa, e esta pode utilizar recursos de camadas inferiores, em diversas representações, para compor novos recursos.
Camadas podem ser utilizadas para encapsular sistemas legados, protegendo tanto novos “clientes” de “servidores” legados, como para proteger novos “servidores” de “clientes” legados.
Camadas diferentes podem operar, inclusive, em níveis de abstração distintos, prevenindo changing coupling. Por exemplo:
- em nível mais baixo, expondo “entidades” como recursos nativos de sistemas pesados,
- em nível intermediário, expondo recursos relacionados a processos de negócio que são composições das “entidades”
- em nível mais alto, expondo recursos relacionados a “experiência” que são composições dos “processos”.
Finalmente, tecnicamente, camadas também podem ser utilizadas para melhorar a escalabilidade habilitando funcionalidades como balanceamento de carga e represamento, protegendo sistemas legados não preparados para escalabilidade.
Componentes “servidor” podem fornecer code-on-demand para “clientes”
A sexta, e última, restrição indicada por Fielding é que componentes “servidor” podem fornecer code-on-demand para componentes “cliente”. Ou seja, em sistemas RESTful componentes “servidor” podem extender funcionalidades de componentes “cliente” através do download de código executável na forma de applets ou scripts. Teoricamente, isso deve simplificar a implementação de features comuns com implementações pré-implementadas.
Conway, outra vez!
O desenvolvimento de sistemas RESTful preconiza clara separação das equipes em desenvolvedores de componentes “cliente” e componentes “servidor”.
Quando o modelo “em camadas” de componentes “servidor” é adotado, é comum, que cada camada seja mantida por um time dedicado. Aliás, em casos extremos, cada componente “servidor”, em cada camada, poderá ser mantido por um time especialista.
Eventualmente, um time de operações poderá ser estabelecido para projeto, implementação e manutenção de conectores. O mesmo também pode ocorrer para manutenção das fontes de dados.
Indicações e contraindicações
APIs REST parecem ser uma unanimidade. Infelizmente, essa “unanimidade” é questionável frente ao fato de que muitas implementações não respeitam alguns de seus princípios.
APIs REST são, seguramente, mais acessíveis e fáceis de usar do que APIs SOAP. Entretanto, é importante destacar que são, fundamentalmente, data-driven. Eventualmente, alguns domínios são melhor expressados frente a modelos RPC.
// TODO
Antes de avançar para o próximo capítulo, recomendo as seguintes reflexões:
- As implementações auto-proclamadas RESTful que você conhece realmente o são?
- Em que cenários você não utilizaria REST como estilo arquitetural para desenho de APIs?
- Você está habituado a utilizar HATEOAS?
O chato da crase novamente… 😉 No lugar de “… do estilo mais adaptado as necessidades”, o correto é “.. do estilo mais adaptado às necessidades”.