Compartilhar via


Práticas recomendadas para design de API Web RESTful

Uma implementação de API Web RESTful é uma API Web que emprega princípios arquitetônicos REST (Transferência de Estado Representacional) para alcançar uma interface sem estado e fracamente acoplada entre um cliente e um serviço. Uma API Web que é RESTful dá suporte ao protocolo HTTP padrão para executar operações em recursos e retornar representações de recursos que contêm links de hipermedia e códigos de status de operação HTTP.

Uma API Web RESTful deve se alinhar aos seguintes princípios:

  • Independência da plataforma, o que significa que os clientes podem chamar a API Web independentemente da implementação interna. Para alcançar a independência da plataforma, a API Web usa HTTP como um protocolo padrão, fornece documentação clara e dá suporte a um formato familiar de troca de dados, como JSON ou XML.

  • Acoplamento flexível, o que significa que o cliente e o serviço Web podem evoluir de forma independente. O cliente não precisa saber a implementação interna do serviço Web e o serviço Web não precisa saber a implementação interna do cliente. Para obter acoplamento flexível em uma API Web RESTful, use apenas protocolos padrão e implemente um mecanismo que permita que o cliente e o serviço Web concordem com o formato dos dados a serem trocados.

Este artigo descreve as práticas recomendadas para criar APIs Web RESTful. Ele também aborda padrões de design comuns e considerações para a criação de APIs Web fáceis de entender, flexíveis e mantenedíveis.

Conceitos de design de API web RESTful

Para implementar uma API Web RESTful, você precisa entender os conceitos a seguir.

  • URI (Uniform Resource Identifier): As APIs REST são projetadas em torno de recursos, que são qualquer tipo de objeto, dados ou serviço que o cliente pode acessar. Cada recurso é representado por um URI que identifica exclusivamente esse recurso. Por exemplo, o URI para um pedido específico de um cliente pode ser:

    https://api.contoso.com/orders/1
    
  • A representação de recurso define como um recurso identificado por seu URI é codificado e transportado pelo protocolo HTTP em um formato específico, como XML ou JSON. Os clientes que desejam recuperar um recurso específico devem usar o URI do recurso na solicitação à API. A API retorna uma representação de recurso dos dados que o URI indica. Por exemplo, um cliente pode fazer uma solicitação GET para o identificador https://api.contoso.com/orders/1 de URI para receber o seguinte corpo JSON:

    {"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}
    
  • A interface uniforme é como as APIs RESTful obtêm acoplamento flexível entre implementações de cliente e serviço. Para APIs REST que são criadas em HTTP, a interface uniforme inclui o uso de verbos HTTP padrão para executar operações comoGET, POST, PUTe PATCHDELETE em recursos.

  • Modelo de solicitação sem estado: As APIs RESTful usam um modelo de solicitação sem estado, o que significa que as solicitações HTTP são independentes e podem ocorrer em qualquer ordem. Por esse motivo, não é viável manter informações de estado transitórias entre solicitações. O único local onde as informações são armazenadas é nos próprios recursos, e cada solicitação deve ser uma operação atômica. Um modelo de solicitação sem estado dá suporte à alta escalabilidade porque não precisa manter nenhuma afinidade entre clientes e servidores específicos. No entanto, o modelo sem estado também pode limitar a escalabilidade devido a desafios com a escalabilidade do armazenamento de back-end do serviço Web. Para obter mais informações sobre estratégias para expandir um armazenamento de dados, consulte Particionamento de dados.

  • Links de Hipermídia: As APIs REST podem ser controladas por links de hipermídia que estão contidos em cada representação de recurso. Por exemplo, o bloco de código a seguir mostra uma representação JSON de uma ordem. Ele contém links para obter ou atualizar o cliente associado ao pedido.

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

Definir URIs de recurso da API Web RESTful

Uma API Web RESTful é organizada em torno de recursos. Para organizar seu design de API em torno de recursos, defina URIs de recurso que mapeiam as entidades de negócios. Quando possível, baseie URIs de recurso em substantivos (o recurso) e não em verbos (as operações no recurso).

Por exemplo, em um sistema de comércio eletrônico, as principais entidades comerciais podem ser clientes e pedidos. Para criar um pedido, um cliente envia as informações de pedido em uma solicitação HTTP POST para o URI do recurso. A resposta HTTP à solicitação indica se a criação do pedido foi bem-sucedida.

O URI para criar o recurso de pedido pode ser algo como:

https://api.contoso.com/orders // Good

Evite usar verbos em URIs para representar operações. Por exemplo, o URI a seguir não é recomendado:

https://api.contoso.com/create-order // Avoid

As entidades geralmente são agrupadas em coleções como clientes ou pedidos. Uma coleção é um recurso separado dos itens dentro da coleção, portanto, ela deve ter seu próprio URI. Por exemplo, o seguinte URI pode representar a coleção de pedidos:

https://api.contoso.com/orders

Depois que o cliente recupera a coleção, ele pode fazer uma solicitação GET para o URI de cada item. Por exemplo, para receber informações sobre um pedido específico, o cliente executa uma solicitação HTTP GET no URI https://api.contoso.com/orders/1 para receber o seguinte corpo JSON como uma representação de recurso dos dados de ordem interna:

{"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}

Convenções de nomenclatura de URI de recursos

Ao criar uma API Web RESTful, é importante que você use as convenções corretas de nomenclatura e relação para recursos:

  • Use substantivos para nomes de recursos. Use substantivos para representar recursos. Por exemplo, use /orders ao invés de /create-order. Os métodos HTTP GET, POST, PUT, PATCH e DELETE já implicam a ação verbal.

  • Use substantivos no plural para nomear URIs de coleções. Em geral, é bom usar substantivos plurais para URIs que fazem referência a coleções. É uma boa prática organizar URIs de coleções e itens em uma hierarquia. Por exemplo, /customers é o caminho para a coleção do cliente e /customers/5 é o caminho para o cliente com uma ID igual a 5. Essa abordagem ajuda a manter a API Web intuitiva. Além disso, muitas estruturas de API Web podem rotear solicitações com base em caminhos de URI parametrizados, para que você possa definir uma rota para o caminho /customers/{id}.

  • Considere as relações entre diferentes tipos de recursos e como você pode expor essas associações. Por exemplo, o /customers/5/orders pode representar todos os pedidos do cliente 5. Você também pode abordar a relação na outra direção, representando a associação de um pedido para um cliente. Nesse cenário, o URI pode ser /orders/99/customer. No entanto, estender esse modelo demasiadamente pode torná-lo difícil de ser implementado. Uma abordagem melhor é incluir links no corpo da mensagem de resposta HTTP para que os clientes possam acessar facilmente os recursos relacionados. Usar Hipertexto como Motor do Estado de Aplicação (HATEOAS) para habilitar a navegação a recursos relacionados descreve este mecanismo em mais detalhes.

  • Mantenha as relações simples e flexíveis. Em sistemas mais complexos, você pode estar inclinado a fornecer URIs que permitem que o cliente navegue por vários níveis de relações, como /customers/1/orders/99/products. No entanto, esse nível de complexidade pode ser difícil de manter e é inflexível no caso de as relações entre os recursos mudarem no futuro. Em vez disso, tente manter os URIs relativamente simples. Depois que um aplicativo tiver uma referência a um recurso, você poderá usar essa referência para localizar itens relacionados a esse recurso. Você pode substituir a consulta anterior pelo URI /customers/1/orders para localizar todos os pedidos do cliente 1 e, em seguida, usar /orders/99/products para localizar os produtos nessa ordem.

    Dica

    Evite exigir URIs de recurso mais complexas do que coleção/item/coleção.

  • Evite um grande número de recursos pequenos. Todas as solicitações da Web impõem uma carga no servidor Web. Quanto mais solicitações, maior a carga. APIs Web que expõem um grande número de recursos pequenos são conhecidas como APIs Web tagarelas. Tente evitar essas APIs porque elas exigem que um aplicativo cliente envie várias solicitações para localizar todos os dados necessários. Em vez disso, considere desnormalizar os dados e combinar informações relacionadas em recursos maiores que podem ser recuperados por meio de uma única solicitação. No entanto, você ainda precisa equilibrar essa abordagem em relação à sobrecarga de busca de dados que o cliente não precisa. A recuperação de objeto grande pode aumentar a latência de uma solicitação e gerar custos adicionais de largura de banda. Para obter mais informações sobre esses antipadrões de desempenho, veja Chatty I/O e Extraneous Fetching.

  • Evite criar APIs que espelham a estrutura interna de um banco de dados. A finalidade do REST é modelar entidades de negócios e as operações que um aplicativo pode executar nessas entidades. Um cliente não deve ser exposto à implementação interna. Por exemplo, se seus dados estão armazenados em um banco de dados relacional, a API da Web não precisa expor cada tabela como uma coleção de recursos. Essa abordagem aumenta a superfície de ataque e pode resultar em vazamento de dados. Em vez disso, pense na API da Web como uma abstração do banco de dados. Se necessário, introduza uma camada de mapeamento entre o banco de dados e a API da Web. Essa camada garante que os aplicativos cliente sejam isolados das alterações no esquema de banco de dados subjacente.

Dica

Talvez não seja possível mapear todas as operações implementadas por uma API Web para um recurso específico. Você pode lidar com esses cenários não relacionados a recursos por meio de solicitações HTTP que invocam uma função e retornam os resultados como uma mensagem de resposta HTTP.

Por exemplo, uma API Web que implementa operações de calculadora simples, como adicionar e subtrair, pode fornecer URIs que expõem essas operações como pseudo-recursos e usam a cadeia de caracteres de consulta para especificar os parâmetros necessários. Uma solicitação GET para o URI /add?operand1=99&operand2=1 retorna uma mensagem de resposta com o corpo que contém o valor 100.

No entanto, você deve usar essas formas de URIs com moderação.

Definir métodos de API Web RESTful

Os métodos de API Web RESTful se alinham com os métodos de solicitação e os tipos de mídia definidos pelo protocolo HTTP. Esta seção descreve os métodos de solicitação mais comuns e os tipos de mídia usados em APIs Web RESTful.

Métodos de solicitação HTTP

O protocolo HTTP define muitos métodos de solicitação que indicam a ação que você deseja executar em um recurso. Os métodos mais comuns usados nas APIs Web RESTful são GET, POST, PUT, PATCH e DELETE. Cada método corresponde a uma operação específica. Ao criar uma API Web RESTful, use esses métodos de forma consistente com a definição de protocolo, o recurso que está sendo acessado e a ação que está sendo executada.

É importante lembrar que o efeito de um método de solicitação específico deve depender se o recurso é uma coleção ou um item individual. A tabela a seguir inclui algumas convenções que a maioria das implementações RESTful usa.

Importante

A tabela a seguir usa uma entidade de comércio eletrônico customer de exemplo. Uma API Web não precisa implementar todos os métodos de solicitação. Os métodos que ele implementa dependem do cenário específico.

Recurso POSTAR OBTER PÔR EXCLUIR
/clientes Criar um novo cliente Recuperar todos os clientes Atualização em massa de clientes Remover todos os clientes
/clientes/1 Erro Obter os detalhes do cliente 1 Atualizar os detalhes do cliente 1 se ele existir Remover cliente 1
/clientes/1/pedidos Criar um novo pedido para o cliente 1 Obter todos os pedidos do cliente 1 Atualização em massa de pedidos para o cliente 1 Remover todos os pedidos do cliente 1

Solicitações GET

Uma solicitação GET recupera uma representação do recurso no URI especificado. O corpo da mensagem de resposta contém os detalhes do recurso solicitado.

Uma solicitação GET deve retornar um dos seguintes códigos de status HTTP:

Código de status de HTTP Motivo
200 (OK) O método retornou com êxito o recurso.
204 (Sem Conteúdo) O corpo da resposta não contém nenhum conteúdo, como quando uma solicitação de pesquisa não retorna nenhuma correspondência na resposta HTTP.
404 (Não encontrado) O recurso solicitado não pode ser encontrado.

Solicitações POST

Uma solicitação POST deve criar um recurso. O servidor atribui um URI para o novo recurso e retorna esse URI ao cliente.

Importante

Para solicitações POST, um cliente não deve tentar criar seu próprio URI. O cliente deve enviar a solicitação para o URI da coleção e o servidor deve atribuir um URI ao novo recurso. Se um cliente tentar criar seu próprio URI e emitir uma solicitação POST para um URI específico, o servidor retornará o código de status HTTP 400 (BAD REQUEST) para indicar que o método não tem suporte.

Em um modelo RESTful, as solicitações POST são usadas para adicionar um novo recurso à coleção que o URI identifica. No entanto, uma solicitação POST também pode ser usada para enviar dados para processamento em um recurso existente, sem a criação de nenhum novo recurso.

Uma solicitação POST deve retornar um dos seguintes códigos de status HTTP:

Código de status de HTTP Motivo
200 (OK) O método fez algum processamento, mas não cria um novo recurso. O resultado da operação pode ser incluído no corpo da resposta.
201 (Criado) O recurso foi criado com êxito. 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.
204 (Sem Conteúdo) A resposta não possui conteúdo.
400 (Solicitação inválida) O cliente colocou dados inválidos na solicitação. O corpo da resposta pode conter mais informações sobre o erro ou um link para um URI que fornece mais detalhes.
405 (Método não permitido) O cliente tentou fazer uma solicitação POST para um URI que não dá suporte a solicitações POST.

Solicitação PUT

Uma solicitação PUT deve atualizar um recurso existente se ele existir ou, em alguns casos, criar um novo recurso se ele não existir. Para fazer uma solicitação PUT:

  1. O cliente especifica o URI do recurso e inclui um corpo de solicitação que contém uma representação completa do recurso.
  2. O cliente faz a solicitação.
  3. Se já existir um recurso com esse URI, ele será substituído. Caso contrário, um novo recurso será criado, se a rota der suporte a ele.

Os métodos PUT são aplicados a recursos que são itens individuais, como um cliente específico, em vez de coleções. Um servidor pode oferecer suporte a atualizações, mas não a uma criação por meio de PUT. A possibilidade de dar suporte à criação via PUT depende se o cliente pode atribuir um URI de forma significativa e confiável a um recurso antes que ele exista. Se não puder, use POST para criar recursos e fazer com que o servidor atribua o URI. Em seguida, use PUT ou PATCH para atualizar o URI.

Importante

As solicitações PUT devem ser idempotentes, o que significa que enviar a mesma solicitação várias vezes sempre resulta na modificação do mesmo recurso com os mesmos valores. Se um cliente reenviar uma solicitação PUT, os resultados deverão permanecer inalterados. Por outro lado, não há garantia de que as solicitações POST e PATCH sejam idempotentes.

Uma solicitação PUT deve retornar um dos seguintes códigos de status HTTP:

Código de status de HTTP Motivo
200 (OK) O recurso foi atualizado com êxito.
201 (Criado) O recurso foi criado com êxito. O corpo da resposta pode conter uma representação do recurso.
204 (Sem Conteúdo) O recurso foi atualizado com êxito, mas o corpo da resposta não contém nenhum conteúdo.
409 (Conflito) A solicitação não pôde ser concluída devido a um conflito com o estado atual do recurso.

Dica

Considere a possibilidade de implementar operações HTTP PUT em massa, que podem realizar atualizações em lote para vários recursos em uma coleção. A solicitação PUT deve especificar o URI da coleção. O corpo da solicitação deve especificar os detalhes dos recursos a serem modificados. Essa abordagem pode ajudar a reduzir a conversa e melhorar o desempenho.

Solicitações de PATCH

Uma solicitação PATCH executa uma atualização parcial para um recurso existente. O cliente especifica o URI do recurso. O corpo da solicitação especifica um conjunto de alterações a serem aplicadas ao recurso. Esse método pode ser mais eficiente do que usar solicitações PUT porque o cliente envia apenas as alterações e não toda a representação do recurso. PATCH também pode criar um novo recurso especificando um conjunto de atualizações para um recurso vazio ou nulo se o servidor der suporte a essa ação.

Com uma solicitação PATCH, o cliente envia um conjunto de atualizações para um recurso existente na forma de um documento de patch. O servidor processa o documento patch para executar a atualização. O documento de patch especifica apenas um conjunto de alterações a serem aplicadas em vez de descrever todo o recurso. A especificação do método PATCH, RFC 5789, não define um formato específico para documentos de patch. O formato deve ser deduzido a partir do tipo de mídia na solicitação.

O JSON é um dos formatos de dados mais comuns para APIs Web. Os dois principais formatos de patch baseados em JSON são patch JSON e patch de mesclagem JSON.

O patch de mesclagem JSON é mais simples do que o patch JSON. O documento de patch tem a mesma estrutura que o recurso JSON original, mas inclui apenas o subconjunto de campos que devem ser alterados ou adicionados. Além disso, um campo pode ser excluído especificando null como o valor do campo no documento patch. Essa especificação significa que o patch de mesclagem não será adequado se o recurso original puder ter valores nulos explícitos.

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

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

Aqui está um possível patch de mesclagem JSON para este recurso:

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

Esse patch de mesclagem informa ao servidor para atualizar price, excluir color, e adicionar size. Os valores para name e category não são modificados. Para obter mais informações sobre o patch de mesclagem JSON, consulte RFC 7396. O tipo de mídia para patch de mesclagem JSON é application/merge-patch+json.

O patch de mesclagem não é adequado se o recurso original puder conter valores nulos explícitos devido ao significado especial de null no documento de patch. O documento de patch também não especifica a ordem em que o servidor deve aplicar as atualizações. Se essa ordem importa depende dos dados e do domínio. O patch JSON, definido no RFC 6902, é mais flexível porque especifica as alterações como uma sequência de operações a serem aplicadas, incluindo adicionar, remover, substituir, copiar e testar para validar valores. O tipo de mídia para patch JSON é application/json-patch+json.

Uma solicitação PATCH deve retornar um dos seguintes códigos de status HTTP:

Código de status de HTTP Motivo
200 (OK) O recurso foi atualizado com êxito.
400 (Solicitação inválida) Documento de correção malformado.
409 (Conflito) O documento patch é válido, mas as alterações não podem ser aplicadas ao recurso em seu estado atual.
415 (Tipo de mídia sem suporte) Não há suporte para o formato de documento patch.

Solicitações DELETE

Uma solicitação DELETE remove o recurso no URI especificado. Uma solicitação DELETE deve retornar um dos seguintes códigos de status HTTP:

Código de status de HTTP Motivo
204 (SEM CONTEÚDO) O recurso foi excluído com êxito. O processo foi tratado com êxito e o corpo da resposta não contém mais informações.
404 (NÃO ENCONTRADO) O recurso não existe.

Tipos de recursos MIME

Representação de recurso é como um recurso identificado pelo URI é codificado e transportado pelo protocolo HTTP em um formato específico, como XML ou JSON. Os clientes que desejam recuperar um recurso específico devem usar o URI na solicitação à API. A API responde retornando uma representação de recurso dos dados indicados pelo URI.

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

O cabeçalho Content-Type em uma solicitação ou resposta especifica o formato de representação de recurso. O exemplo a seguir demonstra uma solicitação POST que inclui dados JSON:

POST https://api.contoso.com/orders
Content-Type: application/json; charset=utf-8
Content-Length: 57

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

Se o servidor não oferece suporte para o tipo de mídia, ele deverá retornar um código de status HTTP 415 (Tipo de mídia sem suporte).

Uma solicitação de cliente pode incluir um cabeçalho Accept que contém uma lista de tipos de mídia que o cliente aceita do servidor na mensagem de resposta. Por exemplo:

GET https://api.contoso.com/orders/2
Accept: application/json, application/xml

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).

Implementar métodos assíncronos

Às vezes, um método POST, PUT, PATCH ou DELETE pode exigir um processamento que leva tempo para ser concluído. Se você aguardar a conclusão antes de enviar uma resposta ao cliente, isso poderá causar latência inaceitável. Nesse cenário, considere tornar o método assíncrono. Um método assíncrono deve retornar o código de status HTTP 202 (Aceito) para indicar que a solicitação foi aceita para processamento, mas está incompleta.

Exponha um endpoint que retorna o status de uma solicitação assíncrona para que o cliente possa monitorar o status fazendo requisições ao endpoint de status. Inclua o URI do ponto de extremidade de status no cabeçalho Location da resposta 202. Por exemplo:

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

Se o cliente enviar uma solicitação GET para esse ponto de extremidade, a resposta deve conter o status atual da solicitação. Opcionalmente, ele pode incluir um tempo estimado para conclusão ou um link 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 de extremidade de status deverá retornar o código de status 303 (Consulte Outro) após a conclusão da operação. A resposta 303 inclui um cabeçalho Location que fornece o URI do novo recurso:

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

Para obter mais informações, consulte Fornecer suporte assíncrono para solicitações de execução longa e padrão de Request-Reply assíncrono.

Implementar paginação e filtragem de dados

Para otimizar a recuperação de dados e reduzir o tamanho da carga, implemente a paginação de dados e a filtragem baseada em consulta em seu design de API. Essas técnicas permitem que os clientes solicitem apenas o subconjunto de dados necessários, o que pode melhorar o desempenho e reduzir o uso da largura de banda.

  • A paginação divide grandes conjuntos de dados em partes menores e gerenciáveis. Use parâmetros de consulta como limit especificar o número de itens a serem retornados e offset especificar o ponto de partida. Certifique-se de também fornecer padrões significativos para limit e offset, como limit=25 e offset=0. Por exemplo:

    GET /orders?limit=25&offset=50
    
    • limit: especifica o número máximo de itens a serem retornados.

      Dica

      Para ajudar a evitar ataques de negação de serviço, considere impor um limite superior ao número de itens retornados. Por exemplo, se seu serviço define max-limit=25, e um cliente solicita limit=1000, seu serviço poderá retornar 25 itens ou um erro HTTP BAD-REQUEST, dependendo da documentação da API.

    • offset: especifica o índice inicial para os dados.

  • A filtragem permite que os clientes refinem o conjunto de dados aplicando condições. A API pode permitir que o cliente passe o filtro na cadeia de caracteres de consulta do URI:

    GET /orders?minCost=100&status=shipped
    
    • minCost: filtra pedidos que têm um custo mínimo de 100.
    • status: filtra os pedidos que têm um status específico.

Considere as melhores práticas a seguir:

  • A classificação permite que os clientes classifiquem dados usando um sort parâmetro como sort=price.

    Importante

    A abordagem de classificação pode ter um efeito negativo no cache porque os parâmetros de cadeia de caracteres de consulta fazem parte do identificador de recurso que muitas implementações de cache usam como a chave para dados armazenados em cache.

  • A seleção de campo para projeções definidas pelo cliente permite que os clientes especifiquem apenas os campos necessários usando um fields parâmetro como fields=id,name. Por exemplo, você pode usar um parâmetro de cadeia de caracteres de consulta que aceita uma lista delimitada por vírgulas de campos, como /orders?fields=ProductID, Quantity.

Sua API deve validar os campos solicitados para garantir que o cliente tenha permissão para acessá-los e não exporá campos que normalmente não estão disponíveis por meio da API.

Apoiar respostas parciais

Alguns recursos contêm campos binários grandes, como arquivos ou imagens. Para superar problemas causados por conexões não confiáveis e intermitentes e melhorar os tempos de resposta, considere dar suporte à recuperação parcial de grandes recursos binários.

Para dar suporte a respostas parciais, a API Web deve dar suporte ao cabeçalho Accept-Ranges para solicitações GET para recursos grandes. Esse cabeçalho indica que a operação GET oferece suporte a solicitações parciais. O aplicativo cliente pode enviar solicitações GET que retornam um subconjunto de um recurso, especificado como um intervalo de bytes.

Além disso, considere a possibilidade de implementar solicitações HTTP HEAD para esses recursos. Uma solicitação HEAD é semelhante a uma solicitação GET, porém retorna apenas cabeçalhos HTTP que descrevem o recurso com um corpo de mensagem vazio. Um aplicativo cliente pode emitir uma solicitação HEAD para determinar se deve ou não buscar um recurso pelo uso de solicitações GET parciais. Por exemplo:

HEAD https://api.contoso.com/products/10?fields=productImage

Aqui está 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 dá o tamanho total do recurso, e o cabeçalho Accept-Ranges indica que a operação GET correspondente oferece suporte a resultados parciais. O aplicativo cliente pode usar essas informações para recuperar a imagem em partes menores. A primeira solicitação busca os primeiros 2.500 bytes usando o cabeçalho Range:

GET https://api.contoso.com/products/10?fields=productImage
Range: bytes=0-2499

A mensagem de resposta indica que essa resposta é parcial retornando o código de status HTTP 206. O cabeçalho Content-Length especifica o número real de bytes retornados no corpo da mensagem e não o tamanho do recurso. O cabeçalho Content-Range indica qual parte do recurso é retornada (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

[...]

Uma solicitação subsequente do aplicativo cliente pode recuperar o restante do recurso.

Implementar HATEOAS

Um dos principais motivos para usar REST é a capacidade de navegar por todo o conjunto de recursos sem conhecimento prévio do esquema de URI. Cada solicitação HTTP GET deve retornar as informações necessárias para localizar os recursos relacionados diretamente ao objeto solicitado por meio de hiperlinks incluídos na resposta. A solicitação também deve receber informações que descrevem as operações disponíveis em cada um desses recursos. Esse conceito é conhecido como HATEOAS, ou Hipertexto como o Mecanismo de Estado do Aplicativo. O sistema é efetivamente um computador de estado finito e a resposta a cada solicitação contém as informações necessárias para mover de um estado para outro. Nenhuma outra informação deve ser necessária.

Observação

Não há padrões de uso geral que definam como modelar o princípio HATEOAS. Os exemplos nesta seção ilustram uma solução possível e proprietária.

Por exemplo, para lidar com a relação entre um pedido e um cliente, a representação de um pedido pode incluir links que identificam as operações disponíveis para o cliente do pedido. O seguinte bloco de código é uma possível representação:

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

Nesse exemplo, o array links tem um conjunto de links. Cada link representa uma operação em uma entidade relacionada. Os dados para cada link incluem a relação ("customer"), o URI (https://api.contoso.com/customers/3), o método HTTP e os tipos MIME com suporte. O aplicativo cliente precisa dessas informações para invocar a operação.

A links matriz também inclui informações de auto-referência sobre o recurso recuperado. Esses links têm o relacionamento auto.

O conjunto de links retornados pode ser alterado dependendo do estado do recurso. A ideia de que o hipertexto é o mecanismo do estado do aplicativo descreve esse cenário.

Implementar controle de versão

Uma API Web não permanece estática. À medida que os requisitos de negócios mudam, novas coleções de recursos são adicionadas. À medida que novos recursos são adicionados, as relações entre recursos podem mudar e a estrutura dos dados nos recursos pode ser alterada. Atualizar uma API Web para lidar com requisitos novos ou diferentes é um processo simples, mas você deve considerar os efeitos que essas alterações têm em aplicativos cliente que consomem a API Web. O desenvolvedor que projeta e implementa uma API Web tem controle total sobre essa API, mas não tem o mesmo grau de controle sobre aplicativos cliente criados por organizações parceiras. É importante continuar a dar suporte a aplicativos de cliente existentes, permitindo que novos aplicativos usem novas funcionalidades e recursos.

Uma API da Web que implementa o controle de versão pode indicar os recursos e funcionalidades que ela expõe, e um aplicativo cliente pode enviar solicitações direcionadas a uma versão específica de um recurso ou funcionalidade. As seções a seguir descrevem várias abordagens diferentes, cada qual com suas próprias vantagens e desvantagens.

Sem versionamento

Essa abordagem é a mais simples e pode funcionar para algumas APIs internas. Alterações significativas podem ser representadas como novos recursos ou novos links. Adicionar conteúdo aos recursos existentes pode não apresentar uma alteração significativa porque os aplicativos cliente que não esperam ver esse conteúdo o ignoram.

Por exemplo, uma solicitação ao URI https://api.contoso.com/customers/3 deve retornar os detalhes de um único cliente que contém os idnameaddress campos que o aplicativo cliente espera:

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

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Observação

Para simplificar, as respostas de exemplo mostradas nesta seção não incluem links HATEOAS.

Se o DateCreated campo for adicionado ao esquema do recurso do cliente, a resposta será semelhante a:

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

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}

Os aplicativos cliente existentes poderão continuar funcionando corretamente se puderem ignorar campos não reconhecidos. Enquanto isso, novos aplicativos cliente podem ser projetados para lidar com esse novo campo. No entanto, modificações mais drásticas no esquema de recursos, incluindo remoções de campos ou a renomeação de campos, podem ocorrer. Ou as relações entre recursos podem mudar. Essas atualizações podem constituir alterações significativas que impedem que os aplicativos cliente existentes funcionem corretamente. Nesses cenários, considere uma das seguintes abordagens:

Versionamento de URI

Cada vez que você modifica a API da Web ou altera o esquema de recursos, você adiciona um número de versão ao URI para cada recurso. As URIs existentes anteriormente devem continuar operando normalmente retornando recursos em conformidade com seu esquema original.

Por exemplo, o address campo no exemplo anterior é reestruturado em subcampos que contêm cada parte constituinte do endereço, como streetAddress, city, statee zipCode. Esta versão do recurso pode ser exposta por meio de um URI que contém um número de versão, como https://api.contoso.com/v2/customers/3:

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

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Esse mecanismo de controle de versão é simples, mas depende do servidor para rotear a solicitação para o ponto de extremidade apropriado. No entanto, ela pode se tornar desordada à medida que a API Web amadurece por meio de várias iterações e o servidor precisa dar suporte a várias versões diferentes. Do ponto de vista de um purista, em todos os casos, os aplicativos cliente buscam os mesmos dados (cliente 3), portanto, o URI não deve ser diferente de acordo com a versão. Esse esquema também complica a implementação do HATEOAS porque todos os links precisam incluir o número de versão em suas URIs.

Controle de versão de cadeia de consulta

Em vez de fornecer várias URIs, você pode especificar a versão do recurso usando um parâmetro dentro da cadeia de caracteres de consulta acrescentada à solicitação HTTP, como https://api.contoso.com/customers/3?version=2. O parâmetro de versão deve ser padrão para um valor significativo, como 1, se os aplicativos cliente mais antigos o omitirem.

Essa abordagem tem a vantagem semântica de que o mesmo recurso sempre é recuperado do mesmo URI. No entanto, esse método depende do código que manipula a solicitação para analisar a cadeia de caracteres de consulta e enviar de volta a resposta HTTP apropriada. Essa abordagem também complica a implementação do HATEOAS da mesma forma que o mecanismo de controle de versão do URI.

Observação

Alguns navegadores da Web mais antigos e proxies da Web não armazenam em cache respostas para solicitações que incluem uma cadeia de caracteres de consulta no URI. Respostas não em cache podem degradar o desempenho de aplicativos Web que usam uma API Web e são executadas em um navegador da Web mais antigo.

Versionamento de cabeçalho

Em vez de acrescentar o número de versão como um parâmetro de cadeia de caracteres de consulta, você pode implementar um cabeçalho personalizado que indica a versão do recurso. Essa abordagem requer que o aplicativo cliente adicione o cabeçalho apropriado a qualquer solicitação. No entanto, o código que manipula a solicitação do cliente pode usar um valor padrão, como a versão 1, se o cabeçalho da versão for omitido.

Os exemplos a seguir utilizam um cabeçalho personalizado chamado Custom-Header. O valor desse cabeçalho indica a versão da API da Web.

Versão 1:

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

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Versão 2:

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

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Semelhante ao controle de versão do URI e ao controle de versão da cadeia de caracteres de consulta, você deve incluir o cabeçalho personalizado apropriado em todos os links para implementar HATEOAS.

Controle de versão de tipo de mídia

Quando um aplicativo cliente envia uma solicitação HTTP GET para um servidor Web, ele deve usar um cabeçalho Accept para especificar o formato do conteúdo que ele pode manipular. Normalmente, a finalidade do cabeçalho Accept é permitir que o aplicativo cliente especifique se o corpo da resposta deve ser XML, JSON ou algum outro formato comum que o cliente possa analisar. No entanto, é possível definir tipos de mídia personalizados que incluem informações que permitem que o aplicativo cliente indique qual versão de um recurso ele espera.

O exemplo a seguir mostra uma solicitação que especifica um cabeçalho Accept com o valor application/vnd.contoso.v1+json. O vnd.contoso.v1 elemento indica ao servidor Web que ele deve retornar a versão 1 do recurso. O json elemento especifica que o formato do corpo da resposta deve ser JSON:

GET https://api.contoso.com/customers/3
Accept: application/vnd.contoso.v1+json

O código que manipula a solicitação é responsável por processar o cabeçalho Accept e honrá-lo o máximo possível. O aplicativo cliente pode especificar vários formatos no cabeçalho Aceitar, o que permite que o servidor Web escolha o formato mais apropriado para o corpo da resposta. O servidor Web confirma o formato dos dados no corpo da resposta usando o cabeçalho Content-Type:

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

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Se o cabeçalho Accept não especificar tipos de mídia conhecidos, o servidor Web poderá gerar uma mensagem de resposta HTTP 406 (Não Aceitável) ou retornar uma mensagem com um tipo de mídia padrão.

Esse mecanismo de controle de versão é simples e adequado para HATEOAS, que pode incluir o tipo MIME de dados relacionados em links de recursos.

Observação

Quando você seleciona uma estratégia de controle de versão, implicações, especialmente em relação ao cache do servidor Web. O controle de versão do URI e os esquemas de controle de versão da cadeia de caracteres de consulta são amigáveis para cache porque a mesma combinação de cadeia de caracteres de consulta ou URI se refere aos mesmos dados todas as vezes.

Os mecanismos de versionamento por cabeçalho e versionamento por tipo de mídia normalmente exigem uma lógica mais complexa para examinar os valores no cabeçalho personalizado ou no cabeçalho Accept. Em um ambiente de grande escala, muitos clientes usando versões diferentes de uma API da Web podem resultar em uma quantidade significativa de dados duplicados em um cache do lado do servidor. Esse problema pode se tornar agudo se um aplicativo cliente se comunicar com um servidor Web por meio de um proxy que implementa o cache e encaminha apenas uma solicitação para o servidor Web se ele não contiver atualmente uma cópia dos dados solicitados em seu cache.

APIs da web multilocatários

Uma solução de API Web multilocatário é compartilhada por vários locatários, como organizações distintas que têm seus próprios grupos de usuários.

A multilocação afeta significativamente o design da API Web porque determina como os recursos são acessados e descobertos em vários locatários em uma única API Web. Projete uma API com multilocação em mente para ajudar a evitar a necessidade de refatoração futura ao implementar isolamento, escalabilidade ou customizações específicas para locatários.

Uma API bem arquiteta deve definir claramente como os locatários são identificados em solicitações, seja por meio de subdomínios, caminhos, cabeçalhos ou tokens. Essa estrutura garante uma experiência consistente, mas flexível, para todos os usuários dentro do sistema. Para obter mais informações, consulte Mapear solicitações para locatários em uma solução multilocatário.

A multitenância afeta a estrutura do endpoint, o tratamento de solicitações, a autenticação e a autorização. Essa abordagem também influencia como gateways de API, balanceadores de carga e serviços de back-end roteiam e processam solicitações. As estratégias a seguir são maneiras comuns de obter multitenância em uma API da web.

Use isolamento baseado em subdomínio ou domínio (locação em nível de DNS)

Essa abordagem roteia solicitações usando domínios específicos do locatário. Domínios curinga usam subdomínios para flexibilidade e simplicidade. Os domínios personalizados, que permitem que os locatários usem seus próprios domínios, fornecem maior controle e podem ser adaptados para atender a necessidades específicas. Ambos os métodos dependem da configuração DNS adequada, incluindo A e CNAME registros, para direcionar o tráfego para a infraestrutura apropriada. Domínios curinga simplificam a configuração, mas domínios personalizados fornecem uma experiência mais voltada para a marca.

Preserve o nome do host entre o proxy reverso e os serviços de back-end para ajudar a evitar problemas como o redirecionamento de URL e impedir a exposição de URLs internas. Esse método garante o roteamento correto do tráfego específico do locatário e ajuda a proteger a infraestrutura interna. A resolução de DNS é crucial para alcançar a residência de dados e garantir a conformidade regulatória.

GET https://adventureworks.api.contoso.com/orders/3

Passar cabeçalhos HTTP específicos do locatário

As informações do locatário podem ser passadas por cabeçalhos HTTP personalizados, como X-Tenant-ID ou X-Organization-ID por meio de cabeçalhos baseados em host, como Host ou X-Forwarded-Host, ou podem ser extraídos de declarações JWT (Token Web JSON). A escolha depende dos recursos de roteamento do seu gateway de API ou proxy reverso, com soluções baseadas em cabeçalho exigindo um gateway de Camada 7 (L7) para inspecionar cada solicitação. Esse requisito adiciona sobrecarga de processamento, o que aumenta os custos de computação à medida que o tráfego cresce. No entanto, o isolamento baseado em cabeçalho fornece os principais benefícios. Ele habilita a autenticação centralizada, que simplifica o gerenciamento de segurança entre APIs multilocatários. Usando SDKs ou clientes de API, o contexto do tenant é gerenciado dinamicamente em tempo de execução, o que reduz a complexidade da configuração do lado do cliente. Além disso, manter o contexto de locatário em cabeçalhos resulta em um design de API mais limpo e RESTful, evitando dados específicos do locatário no URI.

É importante considerar que o roteamento baseado em cabeçalho complica o cache, especialmente quando as camadas de cache dependem exclusivamente de chaves baseadas em URI e não levam em conta os cabeçalhos. Como a maioria dos mecanismos de cache otimiza para pesquisas de URI, depender de cabeçalhos pode levar a entradas de cache fragmentadas. As entradas fragmentadas reduzem as ocorrências de cache e aumentam a carga de back-end. Mais criticamente, se uma camada de cache não diferencia respostas por cabeçalhos, ela pode fornecer dados armazenados em cache destinados a um locatário para outro e criar um risco de vazamento de dados.

GET https://api.contoso.com/orders/3
X-Tenant-ID: adventureworks

ou

GET https://api.contoso.com/orders/3
Host: adventureworks

ou

GET https://api.contoso.com/orders/3
Authorization: Bearer <JWT-token including a tenant-id: adventureworks claim>

Passe informações específicas do locatário pelo caminho do URI

Essa abordagem adiciona identificadores de locatário na hierarquia de recursos e depende do gateway de API ou do proxy reverso para determinar o locatário apropriado com base no segmento do caminho. O isolamento baseado em caminho é eficaz, mas compromete o design RESTful da API Web e introduz uma lógica de roteamento mais complexa. Geralmente, ele requer correspondência de padrões ou expressões regulares para analisar e canonizar o caminho do URI.

Em contraste, o isolamento baseado em cabeçalho transmite informações do locatário por meio de cabeçalhos HTTP como pares chave-valor. Ambas as abordagens permitem o compartilhamento eficiente de infraestrutura para reduzir os custos operacionais e melhorar o desempenho em APIs Web multilocatários em larga escala.

GET https://api.contoso.com/tenants/adventureworks/orders/3

Habilitar o rastreamento distribuído e o contexto de rastreamento em APIs

À medida que os sistemas distribuídos e as arquiteturas de microsserviço se tornam padrão, a complexidade das arquiteturas modernas aumenta. Usar cabeçalhos, como Correlation-ID, X-Request-IDou X-Trace-ID, para propagar o contexto de rastreamento em solicitações de API é uma prática recomendada para obter visibilidade de ponta a ponta. Essa abordagem permite o acompanhamento contínuo de solicitações à medida que elas fluem do cliente para os serviços de back-end. Ele facilita a identificação rápida de falhas, monitora a latência e mapeia as dependências da API entre serviços.

As APIs que dão suporte à inclusão de informações de rastreamento e contexto aprimoram o nível de observabilidade e os recursos de depuração. Ao habilitar o rastreamento distribuído, essas APIs permitem uma compreensão mais granular do comportamento do sistema e facilitam o acompanhamento, o diagnóstico e a resolução de problemas em ambientes complexos e de vários serviços.

GET https://api.contoso.com/orders/3
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e
HTTP/1.1 200 OK
...
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e

{...}

Modelo de maturidade da API Web

Em 2008, Leonard Richardson propôs o que agora é conhecido como Modelo de Maturidade Richardson (RMM) para APIs Web. O RMM define quatro níveis de maturidade para APIs Web e baseia-se nos princípios do REST como uma abordagem arquitetônica para a criação de serviços Web. No RMM, à medida que o nível de maturidade aumenta, a API se torna mais RESTful e segue mais de perto os princípios do REST.

Os níveis são:

  • Nível 0: Defina um URI e todas as operações são solicitações POST para esse URI. Os serviços Web do Protocolo de Acesso a Objetos Simples normalmente estão nesse nível.
  • Nível 1: Crie URIs separadas para recursos individuais. Esse nível ainda não é RESTful, mas começa a se alinhar com o design RESTful.
  • Nível 2: Use métodos HTTP para definir operações em recursos. Na prática, muitas APIs Web publicadas se alinham aproximadamente a esse nível.
  • Nível 3: Use hypermedia (HATEOAS). Esse nível é realmente uma API RESTful, de acordo com a definição de Fielding.

Iniciativa OpenAPI

A Iniciativa OpenAPI foi criada por um consórcio do setor para padronizar as descrições da API REST entre fornecedores. A especificação de padronização foi chamada de Swagger antes de ser integrada à Iniciativa OpenAPI e renomeada para a Especificação OpenAPI (OAS).

Talvez você queira adotar o OpenAPI para suas APIs Web RESTful. Considere os seguintes pontos:

  • O OAS vem com um conjunto de diretrizes opinativas para o design da API REST. As diretrizes são vantajosas para interoperabilidade, mas exigem que você garanta que seu design esteja em conformidade com as especificações.

  • O OpenAPI promove uma abordagem de primeiro contrato em vez de uma abordagem de implementação em primeiro lugar. "Contrato-primeiro significa que você projeta o contrato de API (a interface) primeiro e, em seguida, escreve o código que implementa o contrato."

  • Ferramentas como o Swagger (OpenAPI) podem gerar bibliotecas de clientes ou documentação de contratos de API. Para obter um exemplo, consulte a documentação da API Web do ASP.NET Core com Swagger/OpenAPI.

Próximas etapas