Alternativas RESTful para EXCLUIR corpo de solicitação

93

Embora a especificação HTTP 1.1 pareça permitir corpos de mensagem em solicitações DELETE , parece indicar que os servidores devem ignorá-la, pois não há semântica definida para ela.

4.3 Corpo da Mensagem

Um servidor DEVE ler e encaminhar o corpo da mensagem em qualquer solicitação; se o método de solicitação não inclui semântica definida para um corpo de entidade, então o corpo da mensagem DEVERÁ ser ignorado ao lidar com a solicitação.

Já revisei várias discussões relacionadas a este tópico no SO e além, como:

A maioria das discussões parece concordar que fornecer um corpo de mensagem em um DELETE pode ser permitido , mas geralmente não é recomendado.

Além disso, notei uma tendência em várias bibliotecas de cliente HTTP, onde mais e mais melhorias parecem estar sendo registradas para essas bibliotecas para oferecer suporte a corpos de solicitação em DELETE. A maioria das bibliotecas parece obrigar, embora ocasionalmente com um pouco de resistência inicial.

Meu caso de uso exige a adição de alguns metadados necessários em um DELETE (por exemplo, o "motivo" da exclusão, junto com alguns outros metadados necessários para a exclusão). Considerei as seguintes opções, nenhuma das quais parece completamente apropriada e alinhada com as especificações HTTP e / ou práticas recomendadas REST:

  • Corpo da mensagem - a especificação indica que os corpos da mensagem em DELETE não têm valor semântico; não é totalmente suportado por clientes HTTP; prática não padrão
  • Cabeçalhos HTTP personalizados - exigir cabeçalhos personalizados é geralmente contra as práticas padrão ; usá-los é inconsistente com o resto da minha API, nenhum dos quais requer cabeçalhos personalizados; além disso, nenhuma boa resposta HTTP disponível para indicar valores de cabeçalho personalizados incorretos (provavelmente uma pergunta separada)
  • Cabeçalhos HTTP padrão - Nenhum cabeçalho padrão é apropriado
  • Parâmetros de consulta - Adicionar parâmetros de consulta realmente altera o URI de solicitação que está sendo excluído; contra as práticas padrão
  • Método POST - (por exemplo POST /resourceToDelete { deletemetadata }) POST não é uma opção semântica para exclusão; O POST realmente representa a ação oposta desejada (ou seja, o POST cria subordinados de recursos, mas preciso excluir o recurso)
  • Métodos múltiplos - dividir a solicitação DELETE em duas operações (por exemplo, PUT excluir metadados e, em seguida, DELETE) divide uma operação atômica em duas, deixando potencialmente um estado inconsistente. O motivo da exclusão (e outros metadados relacionados) não fazem parte da representação do recurso em si.

Minha primeira preferência provavelmente seria usar o corpo da mensagem, em segundo lugar para cabeçalhos HTTP personalizados; no entanto, conforme indicado, existem algumas desvantagens para essas abordagens.

Existem recomendações ou melhores práticas em linha com os padrões REST / HTTP para incluir esses metadados necessários em solicitações DELETE? Existem outras alternativas que não considerei?

Shelley
fonte
2
Certas implementações como Jerseynão permitem corpo para deletesolicitações.
basiljames de

Respostas:

44

Apesar de algumas recomendações para não usar o corpo da mensagem para solicitações DELETE, essa abordagem pode ser apropriada em certos casos de uso. Essa é a abordagem que acabamos usando após avaliar as outras opções mencionadas nas perguntas / respostas e após colaborar com os consumidores do serviço.

Embora o uso do corpo da mensagem não seja ideal, nenhuma das outras opções também se encaixava perfeitamente. O corpo da solicitação DELETE nos permitiu adicionar de forma fácil e clara semântica em torno de dados / metadados adicionais que eram necessários para acompanhar a operação DELETE.

Eu ainda estaria aberto a outros pensamentos e discussões, mas gostaria de encerrar o ciclo dessa questão. Agradeço as opiniões e discussões de todos sobre este tópico!

Shelley
fonte
12
Esta é uma má ideia. Um ponto em que isso o colocará em apuros é se, posteriormente, você decidir usar um serviço de aceleração HTTP como o Akamai EdgeConnect. Eu sei que o EdgeConnect retira corpos de solicitações HTTP DELETE (uma vez que eles consomem largura de banda são provavelmente inválidos). Também é provável que serviços semelhantes façam o mesmo (consulte o recurso de aceleração do Kindle e outros serviços semelhantes ao CDN). Você provavelmente deve redesenhar para não usar verbos HTTP para o seu serviço. A maioria das APIs faz pouco sentido usando HTTP-verbos / classical-REST e problemas de transporte de verbos HTTP são muito difíceis de solucionar.
Gabe
3
Concordo com @Gabe, enviar um corpo com métodos que não têm corpo por definição é uma maneira infalível de perder dados aleatoriamente conforme seus bits atravessam os canais da Internet, e você terá muita dificuldade em depurá-los.
Nicholas Shanks de
3
Esses comentários contra o uso do DELETE são irrelevantes até que abordem as questões muito válidas que o OP tem. Estou tentando o meu melhor para aderir ao espírito do REST, mas uma exclusão sem simultaneidade otimista e uma exclusão que não tem uma operação de lote atômica não é prática em situações da vida real. Esta é uma deficiência séria com o padrão REST.
Quarkly
Estou com @Quarkly nisso. Não entendo qual é a ideia RESTFUL de como devemos verificar a simultaneidade, etc. As verificações de simultaneidade não pertencem ao cliente.
Dirk Wessels
13

O que você parece querer é uma de duas coisas, nenhuma das quais é pura DELETE:

  1. Você tem duas operações, uma PUTdo motivo da exclusão seguida por umDELETE do recurso. Depois de excluído, o conteúdo do recurso não fica mais acessível a ninguém. O 'motivo' não pode conter um hiperlink para o recurso excluído. Ou,
  2. Você está tentando alterar um recurso de state=activepara state=deletedusando o DELETEmétodo. Recursos com state = deleted são ignorados por sua API principal, mas ainda podem ser lidos por um administrador ou alguém com acesso ao banco de dados. Isso é permitido - DELETEnão precisa apagar os dados de apoio de um recurso, apenas para remover o recurso exposto naquele URI.

Qualquer operação que requer um corpo de mensagem em uma DELETEsolicitação pode ser dividida em sua forma mais geral, a POSTpara fazer todas as tarefas necessárias com o corpo da mensagem, e a DELETE. Não vejo razão para quebrar a semântica do HTTP.

Nicholas Shanks
fonte
2
O que acontecerá se a PUTrazão for bem-sucedida e o DELETErecurso falhar? Como pode o estado inconsistente ser evitado?
Lightman
1
@Lightman o PUT especifica apenas a intenção. Ele pode existir sem um DELETE correspondente, o que indicaria que alguém queria excluir, mas falhou ou eles mudaram de ideia. Reverter a ordem das chamadas também permitiria que DELETEs ocorressem sem um motivo - fornecer um motivo seria então considerado meramente como uma anotação. É por esses dois motivos que eu recomendaria usar a opção 2 acima, ou seja, alterar o estado do registro subjacente para fazer com que o recurso HTTP desapareça de seu URL atual. Um coletor de lixo / administrador pode então limpar os registros
Nicholas Shanks
7

Dada a situação que você tem, eu usaria uma das seguintes abordagens:

  • Enviar um PUT ou PATCH : Estou deduzindo que a operação de exclusão é virtual, pela natureza da necessidade de um motivo de exclusão. Portanto, acredito que atualizar o registro por meio de uma operação PUT / PATCH é uma abordagem válida, embora não seja uma operação DELETE em si.
  • Use os parâmetros de consulta : O recurso uri não está sendo alterado. Na verdade, acho que essa também é uma abordagem válida. A pergunta que você vinculou estava falando sobre não permitir a exclusão se o parâmetro de consulta estivesse faltando. No seu caso, eu teria apenas um motivo padrão se o motivo não for especificado na string de consulta. O recurso ainda será resource/:id. Você pode torná-lo detectável com cabeçalhos de link no recurso para cada motivo (com uma reltag em cada um para identificar o motivo).
  • Use um endpoint separado por motivo : Usando um URL semelhante resource/:id/canceled. Isso realmente altera o URI de solicitação e definitivamente não é RESTful. Novamente, os cabeçalhos dos links podem tornar isso detectável.

Lembre-se de que REST não é lei ou dogma. Pense nisso mais como orientação. Portanto, quando fizer sentido não seguir as orientações para o domínio do seu problema, não o faça. Apenas certifique-se de que seus consumidores de API sejam informados sobre a variação.

progressão de código
fonte
Em relação ao uso de parâmetros de consulta, meu entendimento é que a consulta faz parte do Request-URI por seção 3.2 e, portanto, usar esta abordagem (ou da mesma forma, os terminais separados) vai contra a definição do método DELETE , de modo que "recurso identificado pelo Request-URI "é excluído.
shelley de
O recurso é identificado pelo caminho uri. Portanto, um GET para /orders/:idretornaria o mesmo recurso que /orders/:id?exclude=orderdetails. A string de consulta está apenas dando dicas ao servidor - neste caso, para excluir detalhes do pedido na resposta (se houver suporte). Da mesma forma, se você estiver enviando DELETE para /orders/:idou /orders/:id?reason=canceledou /orders/:id?reason=bad_credit, ainda estará agindo no mesmo recurso subjacente. Para manter uma 'interface uniforme', eu teria um motivo padrão para que o envio do parâmetro de consulta não fosse necessário.
codeprogression de
@shelley Você está certo em suas preocupações sobre strings de consulta. A string de consulta faz parte do URI. Enviar uma solicitação DELETE para /foo?123significa que você está excluindo um recurso diferente do que se enviasse DELETE para /foo?456.
Nicholas Shanks
@codeprogression Desculpe, mas muito do que você diz está errado. O recurso é identificado por todo o URI, não apenas pelo caminho. Diferentes strings de consulta são recursos diferentes (no sentido HTTP da palavra 'recurso'). Além disso, um motivo padrão não é necessário para uma interface uniforme. Esse termo se refere ao uso de GET, PUT, POST, PATCH e DELETE da maneira que o HTTP os definiu. A semelhança é entre os fornecedores (fornecedores de agentes de usuário, fornecedores de API, fornecedores de proxy de cache, ISPs etc) e não dentro da própria API (embora isso também deva ser uniforme em design para a sanidade dos usuários!).
Nicholas Shanks
@Nicholas Não entendo sua necessidade de discutir uma discussão que terminou há três anos. A resposta e os comentários que fiz são válidos e corretos de uma visão centrada em REST. REST não é HTTP (nem qualquer implementação de HTTP do fornecedor). No contexto do REST, os recursos são os mesmos. E como eu disse em minha resposta, REST não é lei ou dogma, mas orientação.
codeprogression de
0

Eu sugiro que você inclua os metadados necessários como parte da própria hierarquia de URI. Um exemplo (ingênuo):

Se você precisar excluir entradas com base em um intervalo de datas, em vez de passar a data de início e a data de término no corpo ou como parâmetros de consulta, estruture o URI de forma que você passe as informações necessárias como parte do URI.

por exemplo

DELETE /entries/range/01012012/31122012 - Excluir todas as entradas entre 01 de janeiro de 2012 e 31 de dezembro de 2012

Espero que isto ajude.

Suresh Kumar
fonte
5
Não cobre casos como o envio de um motivo para exclusão, ou seja, o campo de comentários.
Kugel,
3
Uau. isso é uma ideia terrível. Se você tiver muitos metadados, isso aumentará as restrições de tamanho do URI.
Balaji Boggaram Ramanarayan
1
Essa abordagem não segue as práticas RESTful e não é recomendada, pois você terá uma estrutura URI complicada. Os endpoints ficam confusos com a identificação de recursos entrelaçados versus metadados e, com o tempo, se tornarão um pesadelo de manutenção conforme sua API muda. É muito mais preferível ter o rangeespecificado nos parâmetros de consulta ou carga útil, que é o cerne desta questão: para entender a abordagem das melhores práticas para o problema que eu diria que não é isso.
digitaldreamer
@digitaldreamer - Não entendo o que você quer dizer com estrutura URI complicada. Além disso, este é um HTTP DELETE, portanto, a carga útil não é uma opção, mas sim os parâmetros de consulta.
Suresh Kumar