Como uma API REST deve manipular solicitações PUT para recursos parcialmente modificáveis?

41

Suponha que uma API REST, em resposta a uma solicitação HTTP GET , retorne alguns dados adicionais em um subobjeto owner :

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

É evidente que não queremos que ninguém possa PUT voltar

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

e que tenham sucesso. De fato, nós provavelmente não iremos implementar uma maneira para que, até mesmo, tenha sucesso, neste caso.

Mas esta questão não é apenas sobre sub-objetos: o que, em geral, deve ser feito com dados que não devem ser modificáveis em uma requisição PUT?

Deverá estar faltando na solicitação PUT?

Deve ser descartado silenciosamente?

Deve ser verificado e, se for diferente do valor antigo desse atributo, retornar um código de erro HTTP na resposta?

Ou devemos usar patches JSON RFC 6902 em vez de enviar todo o JSON?

    
por Robin Green 14.08.2013 / 17:19
fonte

2 respostas

38

Não existe uma regra, seja na especificação W3C ou nas regras não oficiais do REST, que diz que um PUT deve usar o mesmo esquema / modelo que o correspondente GET .

É legal se eles forem semelhantes , mas não é incomum que PUT faça as coisas de maneira um pouco diferente. Por exemplo, vi muitas APIs que incluem algum tipo de ID no conteúdo retornado por GET , por conveniência. Mas com um PUT , esse ID é determinado exclusivamente pelo URI e não tem significado no conteúdo. Qualquer ID encontrado no corpo será silenciosamente ignorado.

O REST e a Web em geral estão strongmente vinculados ao Princípio da Robustez : "Seja conservador no que você do [send], seja liberal no que você aceita. " Se você concordar filosoficamente com isso, a solução é óbvia: Ignore quaisquer dados inválidos em PUT requests. Isso se aplica tanto a dados imutáveis, como no seu exemplo, quanto a bobagens reais, por exemplo campos desconhecidos.

PATCH é potencialmente outra opção, mas você não deve implementar PATCH , a menos que realmente ofereça suporte a atualizações parciais. PATCH means atualiza apenas os atributos específicos que incluo no conteúdo ; ele faz não significa substituir toda a entidade, mas exclui alguns campos específicos . O que você está realmente falando não é realmente uma atualização parcial, é uma atualização completa, idempotente e tudo, é apenas que parte do recurso é somente leitura.

Uma boa coisa a fazer se você escolher essa opção seria enviar de volta um 200 (OK) com a entidade atualizada real na resposta, para que os clientes possam ver claramente que a somente leitura os campos não foram atualizados.

Certamente há algumas pessoas que pensam de outra forma - que deve ser um erro tentar atualizar uma leitura apenas uma parte de um recurso. Há alguma justificativa para isso, principalmente com base no fato de que você definitivamente retornaria um erro se o recurso inteiro fosse somente leitura e o usuário tentasse atualizá-lo. Isso definitivamente vai contra o princípio da robustez, mas você pode considerá-lo mais "autodocumentado" para os usuários da sua API.

Existem duas convenções para isso, ambas correspondem às suas ideias originais, mas vou expandi-las. A primeira é proibir que os campos somente leitura apareçam no conteúdo e retornem um HTTP 400 (Bad Request), se o fizerem. APIs desse tipo também devem retornar um HTTP 400 se houver algum outro campo não reconhecido / não utilizável. A segunda é exigir que os campos somente leitura sejam idênticos ao conteúdo atual e retornar 409 (Conflito) se os valores não corresponderem.

Eu realmente não gosto da verificação de igualdade com o 409 porque ele invariavelmente exige que o cliente faça um GET para recuperar os dados atuais antes de poder fazer um PUT . Isso não é legal e provavelmente vai levar a um mau desempenho, para alguém, em algum lugar. Eu também realmente não gosto de 403 (Proibido) por isso, pois implica que o recurso inteiro está protegido, não apenas uma parte dele. Então, minha opinião é, se você deve validar em vez de seguir o princípio de robustez, valide todas suas solicitações e retorne um 400 para qualquer um que tenha campos extras ou não-graváveis.

Verifique se o seu 400/409 / o inclui informações sobre o problema específico e como corrigi-lo.

Ambas as abordagens são válidas, mas eu prefiro a primeira em conformidade com o princípio da robustez. Se você já trabalhou com com uma grande API REST, você apreciará o valor da compatibilidade com versões anteriores. Se você decidir remover um campo existente ou torná-lo somente leitura, será uma alteração compatível com versões anteriores se o servidor ignorar esses campos e os clientes antigos continuarem funcionando. No entanto, se você fizer uma validação rigorosa do conteúdo, ele não é mais compatível com versões anteriores e os clientes antigos deixarão de funcionar. O primeiro geralmente significa menos trabalho para o mantenedor de uma API e seus clientes.

    
por 14.09.2013 / 03:49
fonte
9

Potência Idem

Após o RFC, um PUT teria que entregar o objeto completo ao recurso. A principal razão disso é que o PUT deve ser idempotente. Isso significa que uma solicitação repetida deve ser avaliada com o mesmo resultado no servidor.

Se você permitir atualizações parciais, ele não poderá mais ser mais idem. Se você tem dois clientes. Cliente A e B, o seguinte cenário pode evoluir:

O cliente A obtém uma imagem das imagens de recursos. Contém uma descrição da imagem, que ainda é válida. O cliente B coloca uma nova imagem e atualiza a descrição de acordo. A imagem mudou. Cliente A vê, ele não tem que mudar a descrição, porque é como ele deseja e coloca apenas a imagem.

Isso levará a uma inconsistência, a imagem tem os metadados errados anexados!

Ainda mais irritante é que qualquer intermediário pode repetir a solicitação. Caso decida de alguma forma o PUT falhou.

O significado de PUT não pode ser alterado (embora você possa usá-lo incorretamente).

Outras opções

Por sorte, há outra opção, isso é PATCH. PATCH é um método que permite atualizar parcialmente uma estrutura. Você pode simplesmente enviar uma estrutura parcial. Para aplicações simples, tudo bem. Esse método não é garantido como idem potente. O cliente deve enviar uma solicitação da seguinte forma:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

E o servidor pode responder com 204 (Sem conteúdo) para sinalizar o sucesso. No erro, você não pode atualizar uma parte da estrutura. O método PATCH é atômico.

A desvantagem desse método é que nem todos os navegadores suportam isso, mas essa é a opção mais natural em um serviço REST.

Exemplo de solicitação de patch: link

Patch Json

A opção json parece ser bastante abrangente e uma opção interessante. Mas pode ser difícil implementar para terceiros. Você tem que decidir se sua base de usuários pode lidar com isso.

Também é um pouco confuso, porque você precisa construir um pequeno interpretador que converta os comandos em uma estrutura parcial, que você usará para atualizar seu modelo. Esse interpretador também deve verificar se os comandos fornecidos fazem sentido. Alguns comandos cancelam um ao outro. (escreva fielda, exclua fielda). Eu acho que você quer relatar isso de volta ao cliente para limitar o tempo de depuração do seu lado.

Mas se você tiver tempo, esta é uma solução realmente elegante. Você ainda deve validar os campos, é claro. Você pode combinar isso com o método PATCH para permanecer no modelo REST. Mas acho que o POST seria aceitável aqui.

Vai mal

Se você decidir ir com a opção PUT, o que é um pouco arriscado. Então você deve pelo menos não descartar o erro. O usuário tem uma certa expectativa (os dados serão atualizados) e se você quebrar isso, você não dará bons momentos a alguns desenvolvedores.

Você pode escolher para sinalizar de volta: 409 Conflito ou 403 Proibido. Depende de como você olha para o processo de atualização. Se você vê isso como um conjunto de regras (centrado no sistema), então o conflito será melhor. Algo como, esses campos não são atualizáveis. (Em conflito com as regras). Se você vê isso como um problema de autorização (centrado no usuário), você deve retornar proibido. Com: você não está autorizado a alterar esses campos.

Você ainda deve forçar os usuários a enviar todos os campos modificáveis.

Uma opção razoável para impor isso é configurá-lo para um sub-recurso, que oferece apenas os dados modificáveis.

Opinião pessoal

Pessoalmente, eu iria (se você não precisa trabalhar com navegadores) para o modelo PATCH simples e depois estendê-lo com um processador de correção JSON. Isso pode ser feito diferenciando os mimetypes: O tipo mime do patch json:

aplicativo / json-patch

E json:    aplicação / json-patch

facilita a implementação em duas fases.

    
por 15.08.2013 / 00:38
fonte