O conceito de REST é popular há mais de 10 anos e ainda assim é difícil achar duas pessoas que concordem com o que é RESTful ou não. Para piorar a situação, uma busca rápida vai retornar montanhas de conteúdos conflitantes ou simplesmente errados. Depois de passar por inúmeras discussões a esse respeito, resolvi me envolver na conversa, fazendo o máximo para não causar ainda mais desentendimento. Por isso, escolhi o formato de perguntas frequentes e vou tentar ser o mais objetivo possível.
1. O que é REST?
Uma olhada rápida na Wikipedia pode enganar:
Representational state transfer (REST) or RESTful Web services are one way of providing interoperability between computer systems on the Internet. REST-compliant Web services allow requesting systems to access and manipulate textual representations of Web resources using a uniform and predefined set of stateless operations.
Em uma grosseira tradução para o português, isso ficaria mais ou menos assim:
Representational state transfer (Transferência representational de estado - REST) ou serviços RESTful são uma maneira de prover interoperabilidade entre sistemas de computadores na internet. Serviços que seguem o padrão REST permitem que sistemas requisitantes acessem e manipulem representações textuais de recursos web, usando um conjunto uniforme e pré definido de operações desprovidas de estado.
A tradução aqui realizada chega a exigir uma tradução da tradução, mais isso não importa! O que conta aqui é que enquanto essa definição está correta, ela descreve um serviço REST não o padrão REST em si. O mais correto seria uma definição parecida com:
REST é um padrão arquitetural que impõe um conjunto de orientações e restrições, focado em garantir escalabilidade e extensibilidade, entre outras características.
2. O que torna REST diferente?
O ponto central do padrão REST veio da conclusão de que protocolos de rede existentes (HTTP é um deles) já forneciam toda a funcionalidade básica para integração, sem a necessidade de construção de protocolos complexos como o SOAP. Numa época dominada pela famosa "estrela da morte WS (WS*)" (mais detalhesabaixo), essa proposta foi um grande caso de simplificação.
Sendo assim, se você estiver criando uma API REST sobre HTTP, os conceitos básicos a serem usados são:
- verbos HTTP (GET, POST, PUT, PATCH, DELETE, etc): são usados para expressar a intenção por trás de um comando;
- recursos HTTP: estão no coração das interações e são endereçáveis através de URLs claras e significativas;
- caching se torna possível e simples através de e-tags;
- media types informam que tipo de formato deve ser usada para representative uma informação. Por exemplo: um mesmo endpoint pode aceitar texto puro ou JSON como entrada.
3. Então REST é JSON sobre HTTP, correto?
Na verdade, não.
Conforme a resposta anterior, JSON é uma das várias representações possíveis para um recurso. XML, formatos binários ou qualquer outro formato de serialização podem, em princípio, ser usados por um serviço RESTful.
Em teoria, é possível criar um serviço RESTful usando um formato diferente de HTTP como base. Mas é difícil achar achar exemplos reais por aí.
4. Mas REST é SOA ou anti-SOA?
Bom, os dois tem uma coisa em comum: ambos padrões sofrem com interpretações exageradas e são motivos de discussões infinitas na internet.
A arquitetura orientada a serviço se tornou um tabu para muitos, mas na realidade prega princípios que podem ser vistos em muitos microservices modernos. O problema, e a grande causa de confusão, não é sobre o porquê de SOA, mas o como (SOAP).
No fim das contas, os dois conceitos são ortogonais: você pode implementar SOA através de serviços RESTful (uma abordagem bastante comum) ou ter várias APIs RESTful sem adotar SOA.
5. REST é CRUD puro? Se não, como implemento operações não CRUD?
Não. E confesso que eu estava torcendo para você não fazer a segunda pergunta. De toda forma, vamos à resposta: o engano de equiparar REST e CRUD é muito comum e provavelmente vem da grande quantidade de exemplos que seguem essa linha.
Para tornar as coisas ainda mais confusas, vários autores propõe o mapeamento de comandos (ex: o cancelamento de um pedido) numa operação de update (normalmente um PATCH) sobre um recurso.
Enquanto isso pode ser ok e até mesmo RESTful, essa abordagem não apresenta intencionalidade (um update pode ser qualquer coisa, enquanto um cancelamento é uma ação bastante específica) e pode tornar coisas como validação de permissões (ex: esse usuário pode faturar um pedido, mas não cancelar) e auditoria, questões muito mais complexas.
O raciocínio imediato após o parágrafo anterior, em muitos casos, é: "No exemplo de cancelamento, um exemplo de operação REST significativa seria criar um recurso de cancelamento e permitir que clientes façam um POST contra /order/123cancellation, correto?"
Enquanto essa pode ser uma boa saída em vários cenários, é sempre ter em mente uma importante ressalva: se levada ao extremo, essa abordagem pode gerar uma explosão de recursos que representam operações, o que pode tornar a API muito mais complexa.
6. E o que é esse papo sobre REST e idempotência?
REST e idempotência são conceitos completamente diferentes, mas o protocolo HTTP tem especificações sobre idempotência. Como REST é fiel ao princípio de usar os pontos fortes do protocolo de base, ele acaba herdando os mesmos conceitos. Se esse papo de idempotência for novo para você, recomendo esse link. A leitura vale a pena!
Os verbos GET, PUT, PATCH e DELETE devem ser idempotents. Isso quer dizer que apesar de que apenas GET é uma operação "segura", todas essas operações deveriam apresentar o mesmo comportamento se repetidas com o mesmo input, exceto por uma potencial diferença na mensagem de resposta, como um 404 no caso de um segundo DELETE. Vale a pena salientar que o estado interno do serviço se mantém constante.
Entre outras coisas, isso quer dizer que comandos PATCH e PUT deveriam evitar operações stateful, como por exemplo incrementar um contador num recurso. Geralmente, POST não é idempotente, mas uma violação de constraint (ex: adicionar duas vezes uma pessoa com o mesmo CPF) podem mudar esse comportamento.
7. Mas então o que não é REST?
Essa pergunta é mais fácil de ser respondida com exemplos. Confira:
WS* (também conhecido como WS Star ou WS Death Star)
Um conjunto de padrões da indústria (leia-se grandes corporações, comitês, etc) para web services que dominaram o espaço corporativo nos anos 90 e no princípio dos 00. Sucumbiu sob o peso da própria complexidade, mas ainda pode ser visto em muitos sistemas legados ou em ferramentas corporativas que acham que REST é só um modismo.
A maior divergência consiste no uso de um protocolo sobre a camada de transporte, focado em abstraí-la ao máximo. Seguindo nessa linha, permite muito mais flexibilidade sobre como desenhar uma API (não necessariamente algo bom nesse caso), já que não é necessário seguir a semântica do protocolo de base. Em linguagem humana, isso quer dizer que serviços WS podem não ser orientados a recurso.
RPC (Remote Procedure Call)
Outro grande ponto de confusão, já que só usar HTTP e JSON não garante aderência a REST. Uma RPC torna as operações, não os recursos, o elemento central. Por exemplo, um cancelamento poderia ser feito usando /cancelOrder ou, mais sutilmente, /orders/123/cancel. Note a diferença com relação ao exemplo REST.
GraphQL
Uma das novas tecnologias em voga no mercado, GraphQL é uma linguagem (não um padrão arquitetônico), focada em entregar suas queries e mutations (ao invés de operações) numa única URL (ao invés de um endereço por recurso), usando exclusivamente HTTP (REST não se limita a isso).
Uma atitude sensata é ignorar os posts no estilo "Por que GraphQL é melhor que REST?" e entender as principais diferenças entre as duas abordagens. Para uma visão mais detalhada sobre essas diferenças, recomendo esse post.
8. Ok, mas o que é esse tal de HATEOAS?
Quando REST parece um conceito tão abstrato, vem alguém falar dessa terrível sigla. O padrão Hypermedia As The Engine of Application State (Hipermídia Como Modelo de Estado de Aplicativos, não que a tradução ajude no entendimento) é o santo graal do REST e tem como base um conceito simples: da mesma maneira como uma pessoa pode usar hiperlinks para navegar na web, uma app pode usar hiperlinks para navegar uma API.
Parece muita loucura? Aqui vai um exemplo: digamos que uma app faz um GET em orders{:id} para buscar detalhes de um pedido. Agora ela deve buscar detalhes do cliente que o colocou. Para fazer isso, o payload contém uma URL que aponta para esse recurso específico (ex: /customers/{:id}).
Esse design permite a descoberta de relacionamentos entre entidades (recursos) de uma API e pode ser estendida para contemplar inclusão de múltiplos recursos relacionados (ex: GET /order/{id}/?include=customer) numa única chamada HTTP. Note que é possível usar URLs que pertencem a outros domínios, o que teoricamente permite uma navegação sem nenhum tipo de fronteira.
9. REST é uma bala de prata? Tudo bem se eu não usar esse padrão?
Perguntando desse jeito, a resposta óbvia para a primeira pergunta é não. Como em qualquer escolha técnica, existem pontos fracos no padrão.
Quanto a alternativas, divergir de REST pode ser perfeitamente aceitável (o pessoal do GraphQL, por exemplo, traz pontos bem interessantes pra mesa), desde que seja uma decisão bem embasada.
Dúvida sobre endpoints específicos
by Silvair Leite Soares,
Re: Dúvida sobre endpoints específicos
by Diogo Lucas,
Interessate....
by Marcus Rosa Pereira,
Dúvida sobre endpoints específicos
by Silvair Leite Soares,
Seu comentário está aguardando aprovação dos moderadores. Obrigado por participar da discussão!
Em primeiro lugar, meus parabéns, pela riqueza de detalhes nas definições e esclarecimentos.
Quanto ao exemplo citado:
"Outro grande ponto de confusão, já que só usar HTTP e JSON não garante aderência a REST. Uma RPC torna as operações, não os recursos, o elemento central. Por exemplo, um cancelamento poderia ser feito usando /cancelOrder ou, mais sutilmente, /orders/123/cancel. Note a diferença com relação ao exemplo REST."
Acredito que uma outra abordagem interessante, seria manter endereços genéricos para os recursos, ex.: "/orders/123" e deixar o servidor identificar cada alteração e executar ações específicas com base nos próprios dados do recurso.
Exemplo:
1-Recurso original obtido pela requisição (GET) ao endpoint "/orders/5a6f1fd224b1b05ad62ce808":
{
"_id":"5a6f1fd224b1b05ad62ce808",
"cliente":10,
"cancelada":false,
...
}
2-Ocorre a alteração do recurso (orders), neste caso, alterando o atributo "cancelada" para "true"
3-Nova requisição (PUT) para o endpoint "/orders/5a6f1fd224b1b05ad62ce808". Desta vez, com o recurso atualizado no corpo da requisição.
{
"_id":"5a6f1fd224b1b05ad62ce808",
"cliente":10,
"cancelada":true,
...
}
4-Servidor identifica alteração e executa ações específicas inerentes ao cancelamento do objeto order.
Obviamente que não se pode utilizar esta abordagem em todos os casos, mas utilizo para a maioria das situações. Pra mim, tem funcionado muito como forma de diminuição de endereços a serem mapeados nos consumidores ou gateway de endpoints REST.
Re: Dúvida sobre endpoints específicos
by Diogo Lucas,
Seu comentário está aguardando aprovação dos moderadores. Obrigado por participar da discussão!
Oi Silvair!
Obrigado pelo comentário :D
Quanto à abordagem que vc descreveu, ao meu ver, sim, ela é RESTful e perfeitamente válida. Nesse caso, a operação de update (PUT ou PATCH) incorpora a maior parte das transições possíveis para um recurso (ex: cancelamento, faturamento e envio de um produto).
A grande vantagem dessa abordagem é a simplicidade (e nesse caso recomendo o uso de PATCH enviando apenas os campos que foram alterados). Por outro lado, em sistemas complexos a criação de recursos para algumas operações chave pode trazer algumas vantagens:
1. Expressividade: um update pode ser um cancelamento, um faturamento, etc. Uma operação explícita comunica exatamente que operação está acontecendo. Isso é um princípio e o valor é discutível, mas facilita os itens abaixo;
2. Controle de acesso. Digamos que apenas pessoas da expedição possam marcar p pedido como enviado. Se a transição é uma operação explícita, é possível ter um mapeamento simples: usando OAuth, por exemplo, seria possível dizer que o endpoint /orders/{:id}/shipment requer o escopo "enviar pedidos". Esse tipo de lógica pode até mesmo ser colocada fora do serviço, num gateway ou num sidecar, já que ela é fácil de generalizar;
3. Auditoria. Para saber quem tomou que ação basta olhar o tipo da operação e quem a executou. Sem isso, é necessário inspecionar o corpo das mensagens. Ainda factível, mas mais trabalhoso e mais caro computacionalmente;
Tendo dado todo esse discurso, costumo começar com um modelo CRUD puro e evoluir para comandos caso haja a necessidade (assumindo uma API privada, onde tenho o luxo de quebrar contrato).
Interessate....
by Marcus Rosa Pereira,
Seu comentário está aguardando aprovação dos moderadores. Obrigado por participar da discussão!
Mas contém alguns erros de digitação/tradução.