Uma maneira relaxante de excluir um monte de itens

97

No artigo wiki para REST , é indicado que se você usar http://example.com/resources DELETE, isso significa que você está excluindo a coleção inteira.

Se você usar http://example.com/resources/7HOU57Y DELETE, significa que está excluindo esse elemento.

Estou fazendo um SITE, observe que NÃO É SERVIÇO NA WEB.

Tenho uma lista com 1 caixa de seleção para cada item da lista. Depois de selecionar vários itens para exclusão, permitirei que os usuários pressionem um botão denominado EXCLUIR SELEÇÃO. Se o usuário pressionar o botão, uma caixa de diálogo js aparecerá pedindo ao usuário para confirmar a exclusão. se o usuário confirmar, todos os itens serão excluídos.

Então, como devo tratar da exclusão de vários itens de forma RESTFUL?

OBSERVAÇÃO, atualmente para DELETE em uma página da web, o que eu faço é usar a tag FORM com POST como ação, mas incluir um _method com o valor DELETE, pois isso é o que foi indicado por outros no SO sobre como fazer RESTful delete para a página da web .

Kim Stacks
fonte
1
É fundamental que essas exclusões sejam realizadas atomicamente? Deseja realmente reverter a exclusão dos primeiros 30 itens se o 31º não puder ser excluído?
Darrel Miller
@darrelmiller boa pergunta. Eu pensei que se as exclusões forem feitas atomicamente, será menos eficiente. Portanto, estou inclinado para DELETE FROM tablename WHERE ID IN ({lista de ids}). Se alguém puder me apontar se isso é uma boa ideia ou me corrigir. isso seria muito apreciado. Também não exijo o reverso da exclusão para os primeiros 20 itens se o 21º for excluído. Mais uma vez, agradeço se alguém puder me mostrar a diferença na abordagem em que preciso reverter e onde NÃO preciso reverter
Kim Stacks
1
Nota: pode haver limites para a cláusula "IN"; por exemplo, no Oracle você pode colocar no máximo 1000 ids.
roubo de
O guia de design de API do Google oferece uma solução para criar operações personalizadas (em lote) em uma API REST, veja minha resposta aqui: stackoverflow.com/a/53264372/2477619
B12Toaster

Respostas:

53

Acho que a resposta do rojoca é a melhor até agora. Uma ligeira variação pode ser, acabar com o javascript confirm na mesma página, e em vez disso, criar a seleção e redirecionar para ela, mostrando uma mensagem de confirmação nessa página. Em outras palavras:

De:
http://example.com/resources/

faça um

POSTAR com uma seleção de IDs para:
http://example.com/resources/selections

que, se bem-sucedido, deve responder com:

HTTP / 1.1 201 criado e um cabeçalho de localização para:
http://example.com/resources/selections/DF4XY7

Nesta página, você verá uma caixa de confirmação (javascript), que se você confirmar fará uma solicitação de:

DELETE http://example.com/resources/selections/DF4XY7

que, se bem-sucedido, deve responder com: HTTP / 1.1 200 Ok (ou o que for apropriado para uma exclusão bem-sucedida)

Dabbler decente
fonte
Eu gosto dessa ideia porque você não precisa de nenhum redirecionamento. Incorporando AJAX, você pode fazer tudo isso sem sair da página.
rojoca
Após EXCLUIR example.com/resources/selections/DF4XY7 , eu seria redirecionado de volta para example.com/resources?
Kim Stacks de
7
@fireeyeboy Esta abordagem em duas etapas parece ser uma forma comumente sugerida de realizar uma exclusão múltipla, mas por quê? Por que você não simplesmente envia uma solicitação DELETE para um uri como http://example.com/resources/selections/e na carga útil (corpo) da solicitação você envia os dados para quais itens deseja excluir. Pelo que eu posso dizer, não há nada que o impeça de fazer isso, mas eu sempre recebo "mas não é RESTfull".
thecoshman
6
DELETE pode potencialmente ter o corpo ignorado pela infraestrutura HTTP: stackoverflow.com/questions/299628/…
Luke Puplett
DELETE pode ter um corpo, mas muitas de suas implementações proibiram seu corpo por padrão
dmitryvim
54

Uma opção é criar uma "transação" de exclusão. Então você vai POSTpara algo como http://example.com/resources/deletesum novo recurso que consiste em uma lista de recursos a serem excluídos. Então no seu aplicativo basta fazer o delete. Ao fazer a postagem, você deve retornar um local da transação criada, por exemplo http://example.com/resources/deletes/DF4XY7,. Um GETpode retornar o status da transação (concluída ou em andamento) e / ou uma lista de recursos a serem excluídos.

rojoca
fonte
2
Nada a ver com seu banco de dados. Por transação, quero dizer apenas uma lista de operações a serem realizadas. Neste caso, é uma lista de exclusões. O que você faz é criar uma nova lista (de exclusões) como um recurso em seu aplicativo. Seu aplicativo da web pode processar essa lista da maneira que você quiser. Esse recurso tem um URI, por exemplo, example.com/resources/deletes/DF4XY7 . Isso significa que você pode verificar o status da exclusão por meio de GET para esse URI. Isso seria útil se, ao fazer uma exclusão, você tivesse que excluir imagens do Amazon S3 ou algum outro CDN e essa operação pudesse demorar para ser concluída.
rojoca
2
+1 esta é uma boa solução. Em vez de enviar um DELETE para cada recurso, @rojoca propõe a criação de uma instância de um novo tipo de recurso cuja única tarefa é excluir uma lista de recursos. Por exemplo, você tem uma coleção de recursos do usuário e deseja excluir os usuários Bob, Dave e Amy de sua coleção, então cria um novo recurso de exclusão POSTANDO Bob, Dave e Amy como os parâmetros de criação. O recurso Exclusão é criado e representa o processo assíncrono de exclusão de Bob, Dave e Amy da coleção Usuários.
Mike Tunnicliffe
1
Sinto muito. Ainda tenho alguma dificuldade em compreender alguns problemas. o DF4XY7. como diabos você gera essa string? Este recurso de exclusão. Eu preciso inserir algum dado no banco de dados? Peço desculpas se repetir algumas perguntas. É um pouco estranho para mim.
Kim Stacks de
1
Presumo que DF4XY7 seja um id único gerado, talvez seja mais natural usar apenas o id gerado quando salvo no banco de dados, por exemplo, example.com/resources/deletes/7. Minha opinião seria criar o modelo de Exclusão e salvá-lo no banco de dados, você pode fazer com que o processo assíncrono excluindo os outros registros atualize o modelo de Exclusão com o status de conclusão e quaisquer erros relevantes.
Mike Tunnicliffe,
2
@rojoca sim, acho que o problema é que o HTTP é muito 'DELETE é para remover um único recurso'. O que quer que você faça, obter várias exclusões é um pouco complicado. Você ainda pode retornar um 'trabalho' ao cliente dizendo que esta tarefa está sendo trabalhada (e pode levar algum tempo), mas use este URI para verificar o progresso. Eu li a especificação e concluí que DELETE pode ter um corpo, assim como outras solicitações.
thecoshman
33

Aqui está o que a Amazon fez com sua API REST S3.

Solicitação de exclusão individual:

DELETE /ObjectName HTTP/1.1
Host: BucketName.s3.amazonaws.com
Date: date
Content-Length: length
Authorization: authorization string (see Authenticating Requests (AWS Signature Version 4))

Solicitação de exclusão de vários objetos :

POST /?delete HTTP/1.1
Host: bucketname.s3.amazonaws.com
Authorization: authorization string
Content-Length: Size
Content-MD5: MD5

<?xml version="1.0" encoding="UTF-8"?>
<Delete>
    <Quiet>true</Quiet>
    <Object>
         <Key>Key</Key>
         <VersionId>VersionId</VersionId>
    </Object>
    <Object>
         <Key>Key</Key>
    </Object>
    ...
</Delete>           

Mas a API Graph do Facebook , a API REST do Parse Server e a API REST do Google Drive vão ainda mais longe, permitindo que você "agrupe" operações individuais em uma solicitação.

Aqui está um exemplo do Parse Server.

Solicitação de exclusão individual:

curl -X DELETE \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  https://api.parse.com/1/classes/GameScore/Ed1nuqPvcm

Solicitação em lote:

curl -X POST \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
        "requests": [
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1337,
              "playerName": "Sean Plott"
            }
          },
          {
            "method": "POST",
            "path": "/1/classes/GameScore",
            "body": {
              "score": 1338,
              "playerName": "ZeroCool"
            }
          }
        ]
      }' \
  https://api.parse.com/1/batch
Luka Žitnik
fonte
13

Eu diria DELETE http://example.com/resources/id1,id2,id3,id4 ou DELETE http://example.com/resources/id1+id2+id3+id4 . Como "REST é uma arquitetura (...) [não] protocolo", para citar este artigo da Wikipedia, não existe, eu acredito, uma única maneira de fazer isso.

Estou ciente de que acima não é possível sem JS com HTML, mas tenho a sensação de que REST era:

  • Criado sem pensar em pequenos detalhes como transações. Quem precisaria operar em mais de um único item? Isso é de alguma forma justificado no protocolo HTTP, uma vez que não se destina a servir por meio dele qualquer outra coisa além de páginas da web estáticas.
  • Não é necessário ajustar bem os modelos atuais - mesmo de HTML puro.
Maciej Piechotka
fonte
thx - e se você quisesse excluir a coleção inteira - os IDs deveriam ser omitidos?
BKSpurgeon
“Tenho a sensação de que o REST foi ... criado sem pensar em pequenos detalhes, como transações” - não acho que seja verdade. Se bem entendi, em REST, as transações são representadas por recursos, não por um método. Há uma boa discussão que culmina neste comentário nesta postagem do blog .
Paul D. Waite,
10

Curiosamente, acho que o mesmo método se aplica ao PATCHing várias entidades e requer pensar sobre o que queremos dizer com nossa URL, parâmetros e método REST.

  1. retornar todos os elementos 'foo':

    [GET] api/foo

  2. retornar elementos 'foo' com filtragem para ids específicos:

    [GET] api/foo?ids=3,5,9

Onde o URL e o filtro determinam "com quais elementos estamos lidando?", E o método REST (neste caso "GET") diz "o que fazer com esses elementos?"

  1. Portanto, faça PATCH em vários registros para marcá-los como lidos

    [PATCH] api/foo?ids=3,5,9

..com os dados foo [ler] = 1

  1. Finalmente, para excluir vários registros, este endpoint é mais lógico:

    [DELETE] api/foo?ids=3,5,9

Por favor, entenda que não acredito que haja quaisquer "regras" sobre isso - para mim, apenas "faz sentido"

fezfox
fonte
Na verdade, em relação ao PATCH: já que significa atualização parcial se você pensar na lista de entidades como uma entidade em si (mesmo se do tipo array), enviando uma matriz parcial (apenas os ids que deseja atualizar) de entidades parciais, então você pode omitir a string de consulta, portanto, não tendo um URL representando mais de uma entidade.
Szabolcs Páll
2

Como diz a resposta do Decent Dabbler e a resposta do rojocas , o mais canônico é usar recursos virtuais para excluir uma seleção de recursos, mas acho que isso é incorreto do ponto de vista REST, porque a execução de um DELETE http://example.com/resources/selections/DF4XY7deve remover o próprio recurso de seleção, não os recursos selecionados.

Tomando a anwser Maciej Piechotka ou a resposta fezfox , só tenho uma objeção: há uma maneira mais canônica de passar um array de ids e está usando o operador array:

DELETE /api/resources?ids[]=1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d&ids[]=7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b

Desta forma, você está atacando o endpoint Delete Collection, mas filtrando a exclusão com uma string de consulta da maneira correta.

Mangelsnc
fonte
-1

Como não existe uma maneira 'adequada' de fazer isso, o que fiz no passado é:

envie DELETE para http://example.com/something com dados codificados em xml ou json no corpo.

ao receber a solicitação, verifique se há DELETE, se verdadeiro, então leia o corpo dos que serão excluídos.

user103219
fonte
Esta é a abordagem que faz sentido para mim, você simplesmente envia os dados em uma solicitação, mas sempre recebo "mas não é RESTfull". Você tem alguma fonte sugerindo que este é um método viável e 'RESTfull' para fazer isso?
thecoshman
10
O problema com essa abordagem é que as operações DELETE não esperam um corpo e, portanto, alguns dos roteadores intermediários na Internet podem removê-lo para você sem seu controle ou conhecimento. Portanto, usar o body para DELETE não é seguro!
Alex White
Referência para o comentário de Alex: stackoverflow.com/questions/299628/…
Luke Puplett
1
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.de tools.ietf.org/html/rfc7231#section-4.3.5
cottton
-1

Eu tive a mesma situação ao excluir vários itens. Isso é o que acabei fazendo. Usei a operação DELETE e os ids dos itens que deveriam ser excluídos faziam parte do cabeçalho HTTP.

Sherin Syriac
fonte