Design de API Web RESTful

A maioria das aplicações Web modernas expõem APIs que os clientes podem utilizar para interagir com a aplicação. Uma API Web bem estruturada deve suportar:

  • Independência da plataforma. Qualquer cliente deve conseguir chamar a API, independentemente de como a API é implementada internamente. Tal requer a utilização de protocolos padrão, assim como um mecanismo a partir do qual o cliente e o serviço Web podem acordar qual o formato dos dados a trocar.

  • Evolução do serviço. A API Web deve ter a capacidade de evoluir e adicionar funcionalidades de forma independente a partir das aplicações cliente. À medida que a API evolui, as aplicações cliente existentes devem continuar a funcionar sem modificação. Todas as funcionalidades devem ser detetáveis para que os aplicativos cliente possam usá-las totalmente.

Estas orientações descrevem os problemas que deve ter em consideração quando conceber uma API Web.

O que é o REST?

Em 2000, Roy Fielding propôs a REST (Representational State Transfer) como uma abordagem arquitetónica à conceção de serviços Web. A REST é um estilo de arquitetura para a criação de sistemas distribuídos com base na hipermédia. A REST é independente de qualquer protocolo subjacente e não está necessariamente associada ao HTTP. No entanto, as implementações de API REST mais comuns usam HTTP como o protocolo de aplicativo, e este guia se concentra na criação de APIs REST para HTTP.

Uma das principais vantagens do REST sobre o HTTP é que ele usa padrões abertos e não vincula a implementação da API ou dos aplicativos cliente a nenhuma implementação específica. Por exemplo, um serviço Web REST pode ser escrito na ASP.NET e as aplicações cliente podem utilizar qualquer linguagem ou conjunto de ferramentas, os quais podem gerar pedidos de HTTP e analisar as respostas HTTP.

Seguem-se alguns dos princípios de design principais das APIs RESTful com HTTP:

  • As APIs REST são concebidas em torno dos recursos, que são qualquer tipo de objeto, dados ou serviço que pode ser acedido pelo cliente.

  • Um recurso tem um identificador, um URI que identifica exclusivamente esse recurso. Por exemplo, o URI de uma encomenda de cliente específico pode ser:

    https://adventure-works.com/orders/1
    
  • Os clientes interagem com um serviço ao trocar representações de recursos. Muitas APIs Web utilizam o JSON como o formato de troca. Por exemplo, um pedido GET para o URI listado acima pode devolver este corpo de resposta:

    {"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
    
  • As APIs REST utilizam uma interface uniforme, que ajuda a desacoplar as implementações do cliente e do serviço. Para APIs REST construídas em HTTP, a interface uniforme inclui o uso de verbos HTTP padrão para executar operações em recursos. As operações mais comuns são GET, POST, PUT, PATCH e DELETE.

  • As APIs REST utilizam um modelo de pedido sem estado. As solicitações HTTP devem ser independentes e podem ocorrer em qualquer ordem, portanto, manter informações de estado transitórias entre as solicitações não é viável. O único local onde as informações são armazenadas é nos próprios recursos e cada pedido deve ser uma operação atómica. Esta restrição permite que os serviços Web sejam altamente dimensionáveis, uma vez que não é necessário manter qualquer afinidade entre clientes e servidores específicos. Qualquer servidor pode processar um pedido de qualquer cliente. Dito isto, outros fatores podem limitar a escalabilidade. Por exemplo, muitos serviços Web gravam em um armazenamento de dados de back-end, o que pode ser difícil de dimensionar. Para obter mais informações sobre estratégias para expandir um armazenamento de dados, consulte Particionamento de dados horizontal, vertical e funcional.

  • As APIs REST estão condicionadas por ligações de hipermédia contidas na representação. Por exemplo, o seguinte mostra uma representação JSON de uma encomenda. Contém ligações para obter ou atualizar o cliente associado à encomenda.

    {
      "orderID":3,
      "productID":2,
      "quantity":4,
      "orderValue":16.60,
      "links": [
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
        {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
      ]
    }
    

Em 2008, Leonard Richardson propôs o seguinte modelo de maturidade para as APIs Web:

  • Nível 0: definir um URI e todas as operações são pedidos POST enviados a este URI.
  • Nível 1: criar URIs separados para recursos individuais.
  • Nível 2: utilizar métodos HTTP para definir operações nos recursos.
  • Nível 3: utiliza hipermédiar(HATEOAS, descrito abaixo).

O nível 3 corresponde a uma verdadeira API RESTful, de acordo com a definição de Fielding. Na prática, muitas APIs Web publicadas enquadram-se em torno do nível 2.

Organizar o design da API em torno de recursos

Concentre-se nas entidades de negócios expostas pela API Web. Por exemplo, num sistema de comércio eletrónico, as entidades primárias podem ser os clientes e as encomendas. Pode criar uma encomenda através do envio de um pedido HTTP POST que contém as informações da encomenda. A resposta HTTP indica se a encomenda foi realizada com sucesso ou não. Sempre que possível, os URIs de recursos devem ser baseados em substantivos (o recurso) e não verbos (as operações no recurso).

https://adventure-works.com/orders // Good

https://adventure-works.com/create-order // Avoid

Um recurso não precisa ser baseado em um único item de dados físico. Por exemplo, um recurso de encomenda pode ser implementado internamente como várias tabelas numa base de dados relacional, mas apresentado ao cliente como uma única entidade. Evite criar APIs que simplesmente espelham a estrutura interna de uma base de dados. O objetivo da REST consiste em modelar entidades e as operações que uma aplicação pode realizar nessas entidades. Um cliente não deve ser exposto à implementação interna.

As entidades são, muitas vezes, agrupadas em coleções (encomendas, clientes). Uma coleção é um recurso individual do item numa coleção e deve ter o seu próprio URI. Por exemplo, o URI seguinte pode representar a coleção de encomendas:

https://adventure-works.com/orders

O envio de um pedido HTTP GET para o URI da coleção produz uma lista de itens na coleção. Cada item na coleção tem também o seu próprio URI exclusivo. Um pedido HTTP GET enviado ao URI do item devolve os detalhes desse item.

Adote uma convenção de nomenclatura consistente nos URIs. Em geral, ajuda utilizar nomes no plural para os URIs que referenciam coleções. É uma boa prática organizar URIs de coleções e itens numa hierarquia. Por exemplo, /customers é o caminho para a coleção de clientes e /customers/5 é o caminho para o cliente com o ID igual a 5. Esta abordagem ajuda a manter a API Web intuitiva. Além disso, muitas estruturas API Web podem encaminhar pedidos com base nos caminhos URI parametrizados, pelo que pode definir uma rota para o caminho /customers/{id}.

Considere também as relações entre os diferentes tipos de recursos e a forma como pode expor estas associações. Por exemplo, /customers/5/orders pode representar todas as encomendas do cliente 5. Pode também seguir na outra direção e representar a associação de uma encomenda de volta para um cliente com um URI como /orders/99/customer. No entanto, se expandir demasiado este modelo, poderá tornar-se complicado implementá-lo. Uma melhor solução consiste em fornecer ligações navegáveis para recursos associados no corpo da mensagem da resposta HTTP. Este mecanismo é descrito mais detalhadamente na secção Utilizar HATEOAS para permitir a navegação para recursos relacionados.

Em sistemas mais complexos, pode ser tentador fornecer URIs que permitem a um cliente navegar através de vários níveis de relações, tal como /customers/1/orders/99/products. No entanto, este nível de complexidade pode ser difícil de manter e não oferecerá nenhum flexibilidade se as relações entre os recursos forem alteradas no futuro. Em vez disso, tente manter os URIs relativamente simples. Assim que uma aplicação possui uma referência para um recurso, deve ser possível utilizar esta referência para localizar itens relacionados a esse recurso. A consulta anterior pode ser substituída pelo URI /customers/1/orders para localizar todas as encomendas do cliente 1 e, em seguida, por /orders/99/products para localizar os produtos nesta encomenda.

Gorjeta

Evite a necessidade de URIs de recursos mais complexos do que collection/item/collection.

Outro fator é que todos os pedidos Web impõem uma carga no servidor Web. Quantos mais pedidos, maior a carga. Por conseguinte, tente evitar APIs Web “conversadoras” que expõem um grande número de recursos pequenos. Essa API pode exigir que um aplicativo cliente envie várias solicitações para localizar todos os dados necessários. Em vez disso, pode querer desnormalizar os dados e combinar informações relacionadas com recursos maiores que podem ser obtidos com um único pedido. No entanto, tem de equilibrar esta abordagem com o custo da obtenção de dados que não são necessários para o cliente. A obtenção de objetos de grande dimensão pode aumentar a latência de um pedido e implicar custos de largura de banda adicionais. Para obter mais informações sobre estes antipadrões de desempenho, veja Antipadrão E/S Chatty e Antipadrão de Obtenção Externa.

Evite introduzir dependências entre a API Web e as origens de dados subjacentes. Por exemplo, se os dados estiverem armazenados na base de dados relacional, a API Web não terá de expor cada tabela como uma coleção de recursos. Na verdade, trata-se provavelmente de uma má conceção. Em vez disso, considere a API Web como uma abstração da base de dados. Se necessário, introduza uma camada de mapa entre a base de dados e a API Web. Dessa forma, as aplicações cliente são isoladas das alterações no esquema da base de dados subjacente.

Por fim, pode não ser possível mapear cada operação implementada por uma API Web num recurso específico. Pode processar estes cenários sem recursos através de pedidos HTTP que invocam uma função e devolvem os resultados como uma mensagem de resposta HTTP. Por exemplo, uma API Web que implementa operações de cálculo simples, tal como adicionar e subtrair, pode fornecer URIs que expõem estas operações como pseudorrecursos e utiliza a cadeia de consulta para especificar os parâmetros necessários. Por exemplo, uma solicitação GET para o URI /add?operand1=99&operand2=1 retornaria uma mensagem de resposta com o corpo contendo o valor 100. No entanto, utilize estes formulários de URIs com moderação.

Definir operações de API em termos de métodos HTTP

O protocolo HTTP define diversos métodos que atribuem significado semântico a um pedido. Os métodos HTTP comuns utilizados pelas APIs Web RESTful são:

  • GET: obtém uma representação do recurso no URI especificado. O corpo da mensagem de resposta contém os detalhes do recurso pedido.
  • POST: cria um novo recurso no URI especificado. O corpo da mensagem de pedido contém os detalhes do novo recurso. Tenha em atenção que POST também pode ser utilizado para acionar as operações que não criam realmente recursos.
  • PUT: cria ou substitui o recurso no URI especificado. O corpo da mensagem de pedido especifica o recurso a ser criado ou atualizado.
  • PATCH: realiza uma atualização parcial de um recurso. O corpo do pedido especifica o conjunto de alterações a aplicar ao recurso.
  • DELETE: remove o recurso no URI especificado.

O efeito de um pedido específico deve depender do facto de um recurso ser uma coleção ou um item individual. A tabela a seguir resume as convenções comuns adotadas pela maioria das implementações RESTful usando o exemplo de comércio eletrônico. Nem todas essas solicitações podem ser implementadas — depende do cenário específico.

Recurso POST GET PUT DELETE
/customers Criar um novo cliente Obter todos os clientes Atualização em massa de clientes Remover todos os clientes
/customers/1 Erro Obter os detalhes do cliente 1 Atualizar os detalhes do cliente 1, se existir Remover o cliente 1
/customers/1/orders Criar uma nova encomenda para o cliente 1 Obter todas as encomendas do cliente 1 Atualização em massa das encomendas do cliente 1 Remover todas as encomendas do cliente 1

As diferenças entre POST, PUT e PATCH podem ser confusas.

  • Um pedido POST cria um recurso. O servidor atribui um URI ao novo recurso e devolve-o ao cliente. No modelo REST, aplica frequentemente pedidos POST às coleções. O novo recurso é adicionado à coleção. Um pedido POST também pode ser utilizado para enviar dados para processamento de um recurso existente, sem a criação de qualquer novo recurso.

  • Um pedido PUT cria um recurso ou atualiza um existente. O cliente especifica o URI do recurso. O corpo do pedido contém uma representação completa do recurso. Se já existir um recurso com este URI, será substituído. Caso contrário, um novo recurso será criado se o servidor suportar fazê-lo. Os pedidos PUT são mais frequentemente aplicados aos recursos compostos por itens individuais, tal como um cliente específico, em vez de coleções. Um servidor pode suportar atualizações, mas não a criação através do método PUT. O suporte da criação através do método PUT depende se o cliente pode atribuir de forma significativa um URI a um recurso antes de este existir. Se não for possível, utilize POST para criar recursos e PUT ou PATCH para atualizar.

  • Um pedido PATCH realiza uma atualização parcial de um recurso existente. O cliente especifica o URI do recurso. O corpo do pedido especifica um conjunto de alterações a aplicar ao recurso. Este procedimento pode ser mais eficiente do que utilizar PUT, uma vez que o cliente envia apenas as alterações, não a representação do recurso completa. Tecnicamente, PATCH também poderá criar um novo recurso (ao especificar um conjunto de atualizações para um recurso “nulo”) se o servidor o suportar.

Os pedidos PUT têm de ser idempotentes. Se um cliente enviar o mesmo pedido PUT várias vezes, os resultados deverão ser sempre iguais (o mesmo recurso será modificado com os mesmos valores). Não é garantido que os pedidos POST e PATCH sejam idempotentes.

Conformidade com a semântica HTTP

Esta secção descreve algumas considerações comuns para criar uma API em conformidade com a especificação HTTP. No entanto, não abrange todos os recursos ou cenário possíveis. Quando em dúvida, consulte as especificações HTTP.

Tipos de suporte

Conforme mencionado anteriormente, os clientes e servidores trocam representações de recursos. Por exemplo, num pedido POST, o corpo do pedido contém uma representação do recurso a criar. Num pedido GET, o corpo da resposta contém uma representação do recurso obtido.

No protocolo HTTP, os formatos são especificados através da utilização de tipos de suporte, também designados por tipos de MIME. Para dados não binários, a maioria das APIs da Web suporta JSON (tipo de mídia = application/json) e possivelmente XML (tipo de mídia = application/xml).

O cabeçalho Content-Type num pedido ou numa resposta especifica o formato da representação. Veja a seguir um exemplo de um pedido POST que inclui dados JSON:

POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57

{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}

Se o servidor não suportar o tipo de suporte, deverá devolver o código de estado HTTP 415 (Tipo de Suporte não Suportado).

Um pedido de cliente pode incluir um cabeçalho Accept que contém uma lista de tipos de suporte, que o cliente aceita do servidor na mensagem de resposta. Por exemplo:

GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json

Se o servidor não puder corresponder a nenhum dos tipos de mídia listados, ele deverá retornar o código de status HTTP 406 (Não Aceitável).

Métodos GET

Por norma, um método GET bem-sucedido devolve o código de estado HTTP 200 (OK). Se não conseguir localizar o recurso, o método deverá devolver 404 (Não Encontrado).

Se a solicitação foi atendida, mas não há nenhum corpo de resposta incluído na resposta HTTP, então ele deve retornar o código de status HTTP 204 (Sem Conteúdo); Por exemplo, uma operação de pesquisa que não produz correspondências pode ser implementada com esse comportamento.

Métodos POST

Se um método POST criar um novo recurso, devolverá o código de estado HTTP 201 (Criado). O URI do novo recurso está incluído no cabeçalho Location da resposta. O corpo da resposta contém uma representação do recurso.

Se o método realizar algum processamento, mas não criar um novo recurso, o método poderá devolver um código de estado HTTP 200 e incluir o resultado da operação no corpo da resposta. Em alternativa, se não for devolvido nenhum resultado, o método poderá devolver o código de estado HTTP 204 (Sem Conteúdo) sem nenhum corpo de resposta.

Se o cliente colocar dados inválidos no pedido, o servidor deverá devolver o código de estado HTTP 400 (Pedido Incorreto). O corpo da resposta pode conter informações adicionais sobre o erro ou uma ligação para um URI que fornece mais detalhes.

Métodos PUT

Se um método PUT criar um novo recurso, devolverá o código de estado HTTP 201 (Criado), tal como um método POST. Se o método atualizar um recurso existente, devolverá 200 (OK) ou 204 (Sem Conteúdo). Em alguns casos, pode não ser possível atualizar um recurso existente. Nesse caso, considere devolver o código de estado HTTP 409 (Conflito).

Pondere implementar operações HTTP PUT em massa, que podem realizar atualizações em lote para vários recursos numa coleção. O pedido PUT deve especificar o URI da coleção e o corpo do pedido deve especificar os detalhes dos recursos a serem modificados. Esta abordagem pode ajudar a reduzir o diálogo e a melhorar o desempenho.

Métodos PATCH

Com um pedido PATCH, o cliente envia um conjunto de atualizações para um recurso existente, sob a forma de um documento de patch. O servidor processa o documento de patch para realizar a atualização. O documento de patch não descreve todo o recurso, mas apenas um conjunto de alterações a aplicar. A especificação do método PATCH (RFC 5789) não define um formato específico para os documentos de patch. O formato deve ser inferido a partir do tipo de suporte no pedido.

JSON é provavelmente o formato de dados mais comum para as APIs Web. Existem dois formatos principais de patch baseados em JSON, denominados patch JSON e patch de intercalação JSON.

O patch de intercalação JSON é um pouco mais simples. O documento de patch tem a mesma estrutura que o recurso JSON original, mas inclui apenas o subconjunto dos campos que devem ser alterados ou adicionados. Além disso, um campo pode ser eliminado ao especificar null para o valor do campo no documento de patch. (Tal significa que o patch de intercalação não será adequado se o recurso original tiver valores nulos explícitos.)

Por exemplo, suponha que o recurso original tem a seguinte representação JSON:

{
    "name":"gizmo",
    "category":"widgets",
    "color":"blue",
    "price":10
}

Segue-se um possível patch de intercalação JSON para este recurso:

{
    "price":12,
    "color":null,
    "size":"small"
}

Isso diz ao servidor para atualizar price, excluir colore adicionar size, enquanto name e category não são modificados. Para obter detalhes exatos sobre o patch de intercalação JSON, veja RFC 7396. O tipo de mídia para o patch de mesclagem JSON é application/merge-patch+json.

O patch de intercalação não será adequado se o recurso original contiver valores nulos explícitos, devido a um significado especial de null no documento de patch. Além disso, o documento de patch não especifica a ordem pela qual o servidor deve aplicar as atualizações. Tal pode ou não ser relevante, uma vez que depende dos dados e do domínio. O patch JSON, definido em RFC 6902, é mais flexível. Especifica as alterações como uma sequência de operações a aplicar. As operações incluem adicionar, remover, substituir, copiar e testar (para validar os valores). O tipo de mídia para o patch JSON é application/json-patch+json.

Seguem-se algumas condições de erro comuns que podem ser encontradas ao processar um pedido PATCH, juntamente com o código de estado HTTP adequado.

Condição de erro Código de estado HTTP
O formato do documento de patch não é suportado. 415 (Tipo de Suporte não Suportado)
Documento de patch com formato incorreto. 400 (Pedido Incorreto)
O documento de patch é válido, mas as alterações não podem ser aplicadas ao recurso no seu estado atual. 409 (Conflito)

Métodos DELETE

Se a operação de exclusão for bem-sucedida, o servidor Web deverá responder com o código de status HTTP 204 (Sem Conteúdo), indicando que o processo foi tratado com êxito, mas que o corpo da resposta não contém mais informações. Se o recurso não existir, o servidor Web poderá devolver HTTP 404 (Não Encontrado).

Operações assíncronas

Às vezes, uma operação POST, PUT, PATCH ou DELETE pode exigir um processamento que leva um tempo para ser concluído. Se você aguardar a conclusão antes de enviar uma resposta ao cliente, isso pode causar latência inaceitável. Se assim for, considere realizar a operação de forma assíncrona. Devolva o código de estado HTTP 202 (Aceite) para indicar que o pedido foi aceite para processamento, mas não foi concluído.

Deve expor um ponto final que devolve o estado de um pedido assíncrono, pelo que o cliente pode monitorizar o estado ao consultar o ponto final do estado. Inclui o URI do ponto final do estado no cabeçalho Location da resposta 202. Por exemplo:

HTTP/1.1 202 Accepted
Location: /api/status/12345

Se o cliente enviar um pedido GET para este ponto final, a resposta deverá conter o estado atual do pedido. Opcionalmente, também pode incluir um tempo estimado para a conclusão ou uma ligação para cancelar a operação.

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

Se a operação assíncrona criar um novo recurso, o ponto final de estado deverá devolver o código de estado 303 (Ver Outro) após a conclusão da operação. Na resposta 303, inclua um cabeçalho Location para fornecer o URI do novo recurso:

HTTP/1.1 303 See Other
Location: /api/orders/12345

Para obter mais informações sobre como implementar essa abordagem, consulte Fornecendo suporte assíncrono para solicitações de longa execução e o padrão de solicitação-resposta assíncrona.

Conjuntos vazios nos corpos das mensagens

Sempre que o corpo de uma resposta bem-sucedida estiver vazio, o código de status deve ser 204 (Sem conteúdo). Para conjuntos vazios, como uma resposta a uma solicitação filtrada sem itens, o código de status ainda deve ser 204 (Sem Conteúdo), não 200 (OK).

Filtrar e paginar dados

A exposição de uma coleção de recursos através de um único URI pode levar as aplicações à obtenção de grandes quantidades de dados quando é preciso apenas um subconjunto das informações. Por exemplo, suponha que uma aplicação cliente precisa de localizar todas as encomendas com um custo com um valor específico. Este pode obter todas as encomendas a partir do URI /orders e, em seguida, filtrá-las do lado do cliente. Claramente, este processo é altamente ineficaz. Desperdiça largura de banda da rede e capacidade de processamento no servidor que aloja a API Web.

Em vez disso, a API pode permitir a transmissão de um filtro na cadeia de consulta do URI, tal como /orders?minCost=n. A API da Web é então responsável por analisar e manipular o minCost parâmetro na cadeia de caracteres de consulta e retornar os resultados filtrados no lado do servidor.

Os pedidos GET sobre recursos de coleção podem, potencialmente, devolver um grande número de itens. Deve criar uma API Web para limitar a quantidade de dados devolvidos por qualquer pedido Web único. Considere suportar cadeias de consulta que especificam o número máximo de itens a obter e um deslocamento inicial para a coleção. Por exemplo:

/orders?limit=25&offset=50

Considere também impor um limite superior do número de itens devolvidos, para ajudar a impedir ataques Denial of Service. Para ajudar as aplicações cliente, os pedidos GET que devolvem dados paginados também devem incluir alguma forma de metadados que indique o número total de recursos disponíveis na coleção.

Pode utilizar uma estratégia semelhante para ordenar os dados à medida que são obtidos ao fornecer um parâmetro de ordenação que aceita um nome de campo como o valor, tal como /orders?sort=ProductID. No entanto, esta abordagem pode ter um efeito negativo na colocação em cache, uma vez que os parâmetros fazem parte da cadeia de consulta do identificador de recursos utilizada por várias implementações de cache, tal como a chave para os dados em cache.

Pode expandir esta abordagem para limitar os campos devolvidos em cada item, se cada item contiver uma grande quantidade de dados. Por exemplo, pode utilizar um parâmetro de cadeia de consulta que aceita uma lista de campos separados por vírgulas, tal como /orders?fields=ProductID,Quantity.

Escolha predefinições significativas para todos os parâmetros opcionais nas cadeias de consulta. Por exemplo, defina o parâmetro limit como 10 e o parâmetro offset como 0 se implementar a paginação, defina o parâmetro de ordenação como a chave do recurso se implementar a ordenação e defina o parâmetro fields como todos os campos no recurso, se suportar projeções.

Suportar respostas parciais para recursos binários grandes

Um recurso pode conter campos binários grandes, tal como ficheiros ou imagens. Para ultrapassar os problemas causados por ligações pouco fiáveis e intermitentes, e para melhorar os tempos de resposta, considere ativar estes recursos para serem obtidos em segmentos. Para tal, a API Web deve suportar o cabeçalho Accept-Ranges de pedidos GET para recursos grandes. Este cabeçalho indica que a operação GET suporta pedidos parciais. A aplicação cliente pode submeter pedidos GET que devolvem um subconjunto de um recurso, especificado como um intervalo de bytes.

Além disso, considere implementar pedidos de HTTP HEAD para estes recursos. Um pedido HEAD é semelhante a um pedido GET, só que devolve apenas os cabeçalhos HTTP que descrevem o recurso, com um corpo de mensagem vazio. Uma aplicação cliente pode emitir um pedido HEAD para determinar se deve obter um recurso com pedidos GET parciais. Por exemplo:

HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1

Segue-se uma mensagem de resposta de exemplo:

HTTP/1.1 200 OK

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

O cabeçalho Content-Length fornece o tamanho total do recurso e o cabeçalho Accept-Ranges indica que a operação GET correspondente suporta resultados parciais. A aplicação cliente pode utilizar estas informações para obter a imagem em segmentos mais pequenos. O primeiro pedido obtém os primeiros 2500 bytes com o cabeçalho Range:

GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499

A mensagem de resposta indica que esta é uma resposta parcial ao devolver o código de estado HTTP 206. O cabeçalho Content-Length especifica o número real de bytes devolvidos no corpo da mensagem (não o tamanho do recurso) e o cabeçalho Content-Range indica de qual a parte do recurso se trata (bytes 0-2499 de 4580):

HTTP/1.1 206 Partial Content

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580

[...]

Um pedido subsequente da aplicação cliente pode obter o resto do recurso.

Uma das motivações principais atrás da REST é a possibilidade de navegar por todo o conjunto de recursos sem a necessidade de conhecer anteriormente o esquema do URI. Cada pedido HTTP GET deve devolver as informações necessárias para encontrar os recursos relacionados diretamente com o objeto pedido através de hiperligações incluídas na resposta e também deve ser fornecido com as informações que descrevem as operações disponíveis em cada um destes recursos. Este princípio é conhecido como HATEOAS ou Hypertext como o Motor do Estado da Aplicação. Com efeito, o sistema é uma máquina de estado finito e a resposta a cada pedido contém as informações necessárias para mover de um estado para outro. Não devem ser necessárias outras informações.

Nota

Atualmente não existem normas de uso geral que definam como modelar o princípio HATEOAS. Os exemplos mostrados nesta seção ilustram uma possível solução proprietária.

Por exemplo, para lidar com a relação entre uma encomenda e um cliente, a representação de uma encomenda pode incluir ligações que identificam as operações disponíveis para o cliente de uma encomenda. Segue-se uma representação possível:

{
  "orderID":3,
  "productID":2,
  "quantity":4,
  "orderValue":16.60,
  "links":[
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"DELETE",
      "types":[]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"DELETE",
      "types":[]
    }]
}

Neste exemplo, a matriz links tem um conjunto de ligações. Cada ligação representa uma operação numa entidade relacionada. Os dados de cada ligação incluem a relação (“customer”), o URI (https://adventure-works.com/customers/3), o método HTTP e os tipos de MIME suportados. Estas são todas as informações que uma aplicação cliente precisa para conseguir invocar a operação.

A matriz links também inclui informações de autorreferência sobre o próprio recurso que foi obtido. Este tem a relação self.

Pode alterar o conjunto de ligações devolvido, dependendo do estado do recurso. Isto é o que significa quando se diz que o hipertexto é o “motor do estado da aplicação”.

Controlo de versões de uma API RESTful Web

É muito pouco provável que uma API Web permaneça estática. À medida que os requisitos comerciais se alteram, novas coleções de recursos poderão ser adicionadas, as relações entre os recursos poderão mudar e a estrutura dos dados nos recursos poderá ser corrigida. Enquanto a atualização de uma API Web para processar requisitos novos ou diferentes é um processo relativamente simples, tem de considerar os efeitos que essas alterações vão ter em aplicações cliente a consumir API Web. O problema é que, embora o desenvolvedor projetando e implementando uma API da Web tenha controle total sobre essa API, o desenvolvedor não tem o mesmo grau de controle sobre os aplicativos cliente, que podem ser criados por organizações de terceiros que operam remotamente. O fundamento principal consiste em permitir que aplicações cliente existentes continuem a funcionar inalteradas, ao mesmo tempo que permitem que novas aplicações cliente tirem partido das novas funcionalidades e recursos.

O controlo de versões permite a uma API Web indicar as funcionalidades e os recursos que a mesma expõe e uma aplicação cliente pode submeter pedidos que são direcionados para uma versão específica de um recurso ou uma funcionalidade. As secções seguintes descrevem várias abordagens diferentes, cada uma com os seus próprios benefícios e compromissos.

Sem controlo de versões

Esta é a abordagem mais simples e pode ser aceitável para algumas APIs internas. Alterações significativas podem ser representadas como novos recursos ou novas ligações. Adicionar conteúdo a recursos existentes pode não apresentar uma alteração significativa, pois os aplicativos cliente que não esperam ver esse conteúdo irão ignorá-lo.

Por exemplo, uma solicitação ao URI https://adventure-works.com/customers/3 deve retornar os detalhes de um único cliente contendo id, namee address campos esperados pelo aplicativo cliente:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Nota

Para simplicidade, as respostas do exemplo apresentadas nesta secção não incluem ligações HATEOAS.

Se o campo DateCreated for adicionado ao esquema dos recursos do cliente, a resposta deverá ter o seguinte aspeto:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}

As aplicações cliente existentes podem continuar a funcionar corretamente se tiverem a capacidade de ignorar campos não reconhecidos, enquanto as novas aplicações cliente podem ser concebidas para processar este novo campo. No entanto, se ocorrerem mais alterações radicais ao esquema de recursos (por exemplo, remover ou renomear os campos) ou as relações entre os recursos forem alteradas, estas poderão constituir alterações recentes que impedem que as aplicações cliente existentes funcionem corretamente. Nessas situações, você deve considerar uma das seguintes abordagens.

Controlo de versões de URI

Sempre que modifica a API Web ou altera o esquema de recursos, adiciona um número de versão ao URI de cada recurso. Os URIs anteriormente existentes devem continuar a funcionar como antes, devolvendo recursos que estão em conformidade com o esquema original.

Estendendo o exemplo anterior, se o address campo for reestruturado em subcampos contendo cada parte constituinte do endereço (como streetAddress, city, state, e zipCode), esta versão do recurso poderá ser exposta por meio de um URI contendo um número de versão, como https://adventure-works.com/v2/customers/3:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Este mecanismo de controlo de versões é muito simples, mas precisa que o serviço encaminhe o pedido para o ponto final adequado. No entanto, pode tornar-se difícil pois a API Web evoluiu ao longo de várias iterações e o servidor tem de suportar um determinado número de versões diferentes. Além disso, do ponto de vista de um purista, em todos os casos os aplicativos cliente estão buscando os mesmos dados (cliente 3), então o URI não deve ser realmente diferente dependendo da versão. Este esquema também complica a implementação de HATEOAS, uma vez que todas as ligações têm de incluir o número da versão nos seus URIs.

Controlo de versões de cadeia de consulta

Em vez de fornecer vários URIs, você pode especificar a versão do recurso usando um parâmetro dentro da cadeia de caracteres de consulta anexada à solicitação HTTP, como https://adventure-works.com/customers/3?version=2. O parâmetro da versão deverá ser predefinido para um valor significativo, tal como 1, se for omitido por aplicações cliente mais antigas.

Esta abordagem tem a vantagem semântica de que o mesmo recurso é obtido sempre pelo mesmo URI, mas depende do código que processa o pedido para analisar a cadeia de consulta e o envio da resposta HTTP adequada. Esta abordagem também sofre das mesmas complicações de implementação HATEOAS que o mecanismo de controlo de versões do URI.

Nota

Alguns browsers e proxies Web mais antigos não colocarão em cache respostas de pedidos que incluem uma cadeia de consulta no URI, Isso pode degradar o desempenho de aplicativos da Web que usam uma API da Web e que são executados a partir desse navegador.

Controlo de versões do cabeçalho

Ao invés de anexar o número da versão como um parâmetro da cadeia de consulta, pode implementar um cabeçalho personalizado que indica a versão do recurso. Esta abordagem requer que a aplicação cliente adicione o cabeçalho adequado a todos os pedidos, embora o código de processamento do pedido do cliente pudesse utilizar um valor predefinido (versão 1) se o cabeçalho da versão for omitido. Os exemplos a seguir usam um cabeçalho personalizado chamado Custom-Header. O valor deste cabeçalho indica a versão da API Web.

Versão 1:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Versão 2:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Tal como nas duas abordagens anteriores, a implementação do HATEOAS requer a inclusão do cabeçalho personalizado apropriado em quaisquer ligações.

Controlo de versões do tipo de suporte

Quando uma aplicação cliente envia um pedido HTTP GET para um servidor Web, este deve definir o formato do conteúdo que pode processar com um cabeçalho Accept, conforme descrito anteriormente nestas orientações. Frequentemente, o objetivo do cabeçalho Accept consiste em permitir que a aplicação cliente especifique se o corpo da resposta deve ser XML, JSON ou outro formato comum que o cliente pode analisar. No entanto, é possível definir os tipos de suportes personalizados que incluem informações que permitem à aplicação cliente indicar a versão de um recurso do qual está à espera.

O exemplo seguinte mostra um pedido que especifica um cabeçalho Accept com o valor application/vnd.adventure-works.v1+json. O elemento vnd.adventure works.v1 indica ao servidor Web que este deve devolver a versão 1 do recurso, enquanto o elemento json especifica que o formato do corpo de resposta deve ser JSON:

GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json

O código a processar o pedido é responsável por processar o cabeçalho Accept e respeitar o mesmo o mais possível (a aplicação cliente pode especificar vários formatos no cabeçalho Accept e, nesse caso, o servidor Web pode escolher o formato mais adequado para o corpo da resposta). O servidor Web confirma o formato dos dados no corpo da resposta com o cabeçalho Content-Type:

HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Se o cabeçalho Accept não especificar todos os tipos conhecidos de suporte, o servidor Web poderá gerar uma mensagem de resposta HTTP 406 (Não Aceitável) ou devolver uma mensagem com um tipo de suporte predefinido.

Esta abordagem é possivelmente a mais pura dos mecanismos de controlo de versões e presta-se naturalmente a HATEOAS, que pode incluir o tipo de MIME de dados relacionados em ligações de recursos.

Nota

Quando seleciona uma estratégia de controlo de versões, deve também considerar as implicações no desempenho, especialmente a colocação em cache no servidor Web. O controlo de versões do URI e os esquemas de controlo de versões da cadeia de consulta são compatíveis com a cache, assim como a mesma combinação de cadeia de consulta/URI se refere sempre aos mesmos dados.

Geralmente, o controlo de versões do Cabeçalho e os mecanismos de controlo de versões do Tipo de Suporte precisa de uma lógica adicional para examinar os valores no cabeçalho personalizado ou no cabeçalho Accept. Num ambiente em grande escala, quando muitos clientes utilizam versões diferentes de uma API Web, tal pode resultar numa quantidade significativa de dados duplicados em cache do lado do servidor. Este problema poderá agravar-se se uma aplicação cliente comunicar com um servidor Web através de um proxy que implementa a colocação em cache e que apenas reencaminha um pedido para o servidor Web se este não detiver atualmente uma cópia dos dados solicitados na respetiva cache.

Iniciativa Open API

A Iniciativa Open API foi criada por um consórcio da indústria para uniformizar as descrições da API REST entre os fornecedores. Como parte desta iniciativa, a especificação do Swagger 2.0 foi renomeada Especificação OpenAPI (OAS) e colocada sob a Iniciativa Open API.

Você pode querer adotar OpenAPI para suas APIs da Web. Alguns pontos a considerar:

  • A Especificação OpenAPI inclui um conjunto de diretrizes opinativas sobre como uma API Rest deve ser concebida. Esta possui vantagens relativamente à interoperabilidade, mas requer mais atenção aquando da conceção da API para que esteja em conformidade com a especificação.

  • A OpenAPI promove uma abordagem de primeiro contrato, ao invés de uma abordagem de primeira implementação. Primeiro contrato significa criar o contrato da API (a interface) e, em seguida, escrever o código que implementa o contrato.

  • Ferramentas como Swagger podem gerar bibliotecas de clientes ou documentação para contratos de API. Por exemplo, consulte ASP.NET páginas de ajuda da API Web usando o Swagger.

Próximos passos

  • Diretrizes da API REST do Microsoft Azure. Recomendações detalhadas para projetar APIs REST no Azure.

  • Lista de verificação da API Web. Uma lista útil de itens a ter em consideração aquando da conceção e implementação de uma API Web.

  • Open API Initiative. Documentação e detalhes de implementação da Open API.