REST - colocar IDs no corpo ou não?

95

Digamos que eu queira ter um recurso RESTful para pessoas, onde o cliente possa atribuir ID.

Uma pessoa se parece com isto: {"id": <UUID>, "name": "Jimmy"}

Agora, como o cliente deve salvá-lo (ou "COLOCAR")?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - agora temos essa duplicação desagradável que temos que verificar o tempo todo: O ID no corpo corresponde ao do caminho?
  2. Representação assimétrica:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID retorna {"id": <UUID>, "name": "Jimmy"}
  3. Nenhum ID no corpo - ID apenas no local:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID retorna {"name": "Jimmy"}
  4. Nada POSTparece uma boa ideia, já que o ID é gerado pelo cliente.

Quais são os padrões comuns e maneiras de resolvê-lo? IDs apenas na localização parece a maneira mais dogmaticamente correta, mas também torna a implementação prática mais difícil.

Konrad Garus
fonte

Respostas:

62

Não há nada de errado em ter diferentes modelos de leitura / gravação: o cliente pode escrever uma representação de recurso onde depois que o servidor pode retornar outra representação com elementos adicionados / calculados nela (ou mesmo uma representação completamente diferente - não há nada em qualquer especificação contra isso , o único requisito é que PUT deve criar ou substituir o recurso).

Portanto, eu iria para a solução assimétrica em (2) e evitar a "verificação de duplicação desagradável" no lado do servidor ao escrever:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}
Jørn Wildt
fonte
1
E se você aplicar a digitação (estática ou dinâmica), não pode ter modelos sem ID sem dor ... Portanto, é muito mais fácil remover o ID da URL para solicitações PUT. Não será "repousante", mas será correto.
Ivan Kleshnin
1
Mantenha TO adicional sem idjunto com TO com id e entidade e conversores adicionais e sobrecarga muito grande para programadores.
Grigory Kislin
E se eu obtiver a ID de BODY, ex .: PUT / person {"id": 1, "name": "Jimmy"}. Isso seria uma prática ruim?
Bruno Santos
Colocar a identificação no corpo seria ótimo. Use um GUID para ID em vez de um inteiro - caso contrário, você corre o risco de IDs duplicados.
Jørn Wildt
Isto está errado. Veja minha resposta. PUT deve conter todo o recurso. Use PATCH se quiser excluir o id e atualizar apenas partes do registro.
CompEng88
26

Se for uma API pública, você deve ser conservador ao responder, mas aceite liberalmente.

Com isso, quero dizer que você deve oferecer suporte a 1 e 2. Concordo que 3 não faz sentido.

A maneira de oferecer suporte a 1 e 2 é obter o id do url se nenhum for fornecido no corpo da solicitação e, se estiver no corpo da solicitação, validar se ele corresponde ao id no url. Se os dois não corresponderem, retorne uma resposta 400 Bad Request.

Ao retornar um recurso person seja conservador e sempre inclua o id no json, mesmo que seja opcional na put.

Jay Pete
fonte
3
Esta deve ser a solução aceita. Uma API sempre deve ser amigável. Deve ser opcional no corpo. Eu não deveria receber um ID de um POST e então ter que torná-lo indefinido em um PUT. Além disso, o ponto de resposta 400 feito está certo.
Michael
Cerca de 400 códigos, consulte também softwareengineering.stackexchange.com/questions/329229/… . Em suma, um código 400 não é impróprio, apenas menos específico, compare com 422.
Grigory Kislin,
8

Uma solução para esse problema envolve o conceito um tanto confuso de "Hipertexto como o motor do estado do aplicativo" ou "HATEOAS". Isso significa que uma resposta REST contém os recursos ou ações disponíveis a serem executadas como hiperlinks. Usando esse método, que fazia parte da concepção original do REST, os identificadores / IDs exclusivos dos recursos são eles próprios hiperlinks. Então, por exemplo, você poderia ter algo como:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Então, se você quiser atualizar esse recurso, você pode fazer (pseudocódigo):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Uma vantagem disso é que o cliente não precisa ter nenhuma ideia sobre a representação interna do servidor de IDs de usuário. Os IDs podem mudar e até os próprios URLs podem mudar, desde que o cliente tenha uma maneira de descobri-los. Por exemplo, ao obter uma coleção de pessoas, você pode retornar uma resposta como esta:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Você pode, é claro, também retornar o objeto pessoa completa para cada pessoa, dependendo das necessidades do aplicativo).

Com esse método, você pensa em seus objetos mais em termos de recursos e locais e menos em termos de ID. A representação interna do identificador único é, portanto, desacoplada da lógica do cliente. Esse foi o ímpeto original por trás do REST: criar arquiteturas cliente-servidor mais fracamente acopladas do que os sistemas RPC que existiam antes, usando os recursos de HTTP. Para obter mais informações sobre o HATEOAS, consulte o artigo da Wikipedia e também este breve artigo .

bthecohen
fonte
4

Em uma inserção, você não precisa adicionar o id na URL. Dessa forma, se você enviar um ID em um PUT, poderá ser interpretado como um UPDATE para alterar a chave primária.

  1. INSERIR:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. ATUALIZAR

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

A API JSON usa esse padrão e resolve alguns problemas ao retornar o objeto inserido ou atualizado com um link para o novo objeto. Algumas atualizações ou inserções podem incluir alguma lógica de negócios que irá alterar campos adicionais

Você também verá que pode evitar obter após a inserção e atualização.

Borjab
fonte
3

Isso já foi perguntado antes - vale a pena dar uma olhada na discussão:

Uma resposta RESTful GET deve retornar o ID de um recurso?

Esta é uma daquelas questões em que é fácil se prender a debates sobre o que é e o que não é "RESTful" .

Pelo que vale a pena, tento pensar em termos de recursos consistentes e não alterar o design deles entre métodos. No entanto, IMHO, a coisa mais importante de uma perspectiva de usabilidade é que você seja consistente em toda a API!

Ben Morris
fonte
3

Apenas para sua informação, as respostas aqui estão erradas.

Vejo:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

COLOCAR

Use APIs PUT principalmente para atualizar o recurso existente (se o recurso não existir, a API pode decidir criar um novo recurso ou não). Se um novo recurso tiver sido criado pela API PUT, o servidor de origem DEVE informar o agente do usuário por meio da resposta HTTP código de resposta 201 (Criado) e se um recurso existente for modificado, seja 200 (OK) ou 204 (Sem Conteúdo) os códigos de resposta DEVEM ser enviados para indicar a conclusão bem-sucedida da solicitação.

Se a solicitação passar por um cache e o URI de Solicitação identificar uma ou mais entidades atualmente armazenadas em cache, essas entradas DEVEM ser tratadas como obsoletas. As respostas a esse método não podem ser armazenadas em cache.

Use PUT quando quiser modificar um recurso singular que já faz parte da coleção de recursos. PUT substitui o recurso em sua totalidade. Use PATCH se a solicitação atualizar parte do recurso.

FRAGMENTO

As solicitações HTTP PATCH devem fazer uma atualização parcial em um recurso. Se você vir que as solicitações PUT também modificam uma entidade de recurso, para deixar mais claro - o método PATCH é a escolha correta para atualizar parcialmente um recurso existente e PUT só deve ser usado se você estiver substituindo um recurso por completo.

Portanto, você deve usá-lo desta forma:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Práticas RESTful indicam que não deve importar o que você COLOQUE em / {id} - o conteúdo do registro deve ser atualizado para aquele fornecido pela carga útil - mas GET / {id} ainda deve vincular ao mesmo recurso.

Em outras palavras, PUT / 3 pode ser atualizado para a carga útil para 4, mas GET / 3 ainda deve se vincular à mesma carga útil (e retornar aquele com a id definida para 4).

Se você está decidindo que sua API requer o mesmo identificador no URI e na carga útil, é sua tarefa garantir que ele corresponda, mas definitivamente use PATCH em vez de PUT se você estiver excluindo o id na carga útil que deveria estar lá em sua totalidade . É aqui que a resposta aceita errou. PUT deve substituir todo o recurso, onde o patch pode ser parcial.

CompEng88
fonte
2

Embora seja normal ter diferentes representações para diferentes operações, uma recomendação geral para PUT é conter TODA a carga útil . Isso significa que iddeve estar lá também. Caso contrário, você deve usar PATCH.

Dito isso, acho que PUT deve ser usado principalmente para atualizações e idsempre deve ser passado na URL. Como resultado disso, usar PUT para atualizar o identificador de recurso é uma má ideia. Isso nos deixa em uma situação indesejável quando ido URL pode ser diferente do idcorpo.

Então, como podemos resolver esse conflito? Basicamente, temos 2 opções:

  • lançar uma exceção 4XX
  • adicione um cabeçalho Warning( X-API-Warnetc).

Isso é o mais perto que posso chegar de responder a essa pergunta porque o assunto em geral é uma questão de opinião.

yuranos
fonte
1

Não há nada de ruim em usar abordagens diferentes. mas acho que a melhor forma é a solução com o 2º .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

é mais usado dessa forma, mesmo a estrutura de entidade usa essa técnica quando a entidade é adicionada em dbContext a classe sem o ID gerado é o ID gerado por referência no Entity Framework.

Shan Khan
fonte
1

Estou vendo isso do ponto de vista JSON-LD / Semantic Web porque essa é uma boa maneira de alcançar a conformidade REST real, conforme delineei nestes slides . Olhando por essa perspectiva, não há dúvida de ir para a opção (1.), pois o ID (IRI) de um recurso da Web deve ser sempre igual ao URL que posso usar para pesquisar / desreferenciar o recurso. Acho que a verificação não é realmente difícil de implementar nem computacionalmente intensa; portanto, não considero isso um motivo válido para escolher a opção (2.). Acho que a opção (3.) não é realmente uma opção, pois POST (criar novo) tem uma semântica diferente de PUT (atualizar / substituir).

casa vantajosa
fonte
0

Pode ser necessário examinar os tipos de solicitação PATCH / PUT.

As solicitações PATCH são usadas para atualizar um recurso parcialmente, enquanto nas solicitações PUT, você deve enviar o recurso inteiro para que seja substituído no servidor.

No que diz respeito a ter um ID no url, acho que você deve sempre tê-lo, pois é uma prática padrão identificar um recurso. Até mesmo a API Stripe funciona dessa maneira.

Você pode usar uma solicitação PATCH para atualizar um recurso no servidor com ID para identificá-lo, mas não atualize o ID real.

Noman Ur Rehman
fonte