Exclua vários registros usando REST

95

Qual é a maneira REST completa de excluir vários itens?

Meu caso de uso é que tenho uma coleção de backbone em que preciso ser capaz de excluir vários itens de uma vez. As opções parecem ser:

  1. Envie uma solicitação DELETE para cada registro (o que parece uma má ideia se houver potencialmente dezenas de itens);
  2. Envie um DELETE onde os IDs a serem excluídos estão agrupados na URL (ou seja, "/ records / 1; 2; 3");
  3. De forma não REST, envie um objeto JSON personalizado contendo os IDs marcados para exclusão.

Todas as opções são menos do que ideais.

Esta parece ser uma área cinzenta da convenção REST.

Donald Taylor
fonte
3
Possível duplicata de Restful way para excluir um monte de itens
Luka Žitnik

Respostas:

89
  1. É uma escolha RESTful viável, mas obviamente possui as limitações que você descreveu.
  2. Não faça isso. Isso seria interpretado pelos intermediários como significando “EXCLUIR o (único) recurso em /records/1;2;3” - Portanto, uma resposta 2xx a isso pode fazer com que eles limpem o cache /records/1;2;3; não purga /records/1, /records/2ou /records/3; proxy para uma resposta 410 /records/1;2;3, ou outras coisas que não fazem sentido do seu ponto de vista.
  3. Essa escolha é a melhor e pode ser feita RESTLY. Se você está criando uma API e deseja permitir mudanças em massa nos recursos, pode usar REST para fazer isso, mas exatamente como não é imediatamente óbvio para muitos. Um método é criar um recurso de 'solicitação de mudança' (por exemplo, postando um corpo como records=[1,2,3]to /delete-requests) e pesquisar o recurso criado (especificado pelo Locationcabeçalho da resposta) para descobrir se sua solicitação foi aceita, rejeitada ou está em andamento ou foi concluído. Isso é útil para operações de longa duração. Outra forma é enviar uma PATCHsolicitação ao recurso de lista ,/records, cujo corpo contém uma lista de recursos e ações a serem executadas nesses recursos (em qualquer formato que você deseja oferecer suporte). Isso é útil para operações rápidas em que o código de resposta da solicitação pode indicar o resultado da operação.

Tudo pode ser conseguido mantendo-se dentro das restrições de REST, e geralmente a resposta é transformar o "problema" em um recurso e fornecer a ele uma URL.
Portanto, operações em lote, como excluir aqui, ou POSTAR vários itens em uma lista, ou fazer a mesma edição em uma faixa de recursos, podem ser manipuladas criando uma lista de "operações em lote" e POSTANDO sua nova operação nela.

Não se esqueça, REST não é a única maneira de resolver qualquer problema. “REST” é apenas um estilo arquitetônico e você não precisa aderir a ele (mas você perde alguns benefícios da Internet se não o fizer). Eu sugiro que você dê uma olhada nesta lista de arquiteturas de API HTTP e escolha aquela que mais se adequa a você. Apenas fique ciente do que você perderá se escolher outra arquitetura e tome uma decisão informada com base em seu caso de uso.

Existem algumas respostas ruins para esta pergunta sobre Padrões para manipulação de operações em lote em serviços da Web REST? que têm muitos votos positivos, mas devem ser lidos também.

Nicholas Shanks
fonte
2
Não é o seu servidor que você precisa se preocupar, são intermediários, CDNs, proxies de cache, etc. A Internet é um sistema em camadas. É por isso que funciona tão bem. Roy determinou quais aspectos do sistema eram necessários para seu sucesso e os chamou de REST. Se você emitir uma DELETEsolicitação, o que quer que esteja entre o solicitante e o servidor pensará que um único recurso, no URL especificado, está sendo excluído. Strings de consulta são partes opacas da URL para esses dispositivos, portanto, não importa como você especifica sua API, elas não têm acesso a esse conhecimento, portanto, não podem se comportar de maneira diferente.
Nicholas Shanks de
3
/ records / 1; 2; 3 não funcionará se você tiver muitos recursos para excluir devido a restrições de comprimento de URI
dukethrash
3
Observe que se considerar DELETE e um corpo definindo os recursos a serem eliminados, alguns intermediários podem não encaminhar o corpo. Além disso, alguns clientes HTTP não podem adicionar um corpo a um DELETE. Consulte stackoverflow.com/questions/299628/…
Luke Puplett
3
@LukePuplett Eu simplesmente declararia que transmitir um corpo de solicitação com uma DELETEsolicitação é proibido. Não faça isso. Se você fizer isso, comerei seus filhos. Nom nom nom.
Nicholas Shanks
3
O problema com o argumento para # 3 é que ele carrega a mesma penalidade do contra-argumento contra # 2. A criação de recursos para exclusão não é algo que os proxies upstream saberão como lidar - o mesmo contra-argumento levantado contra a abordagem nº 2.
LB2
16

Se GET /records?filteringCriteriaretornar a matriz de todos os registros que correspondem aos critérios, DELETE /records?filteringCriteriapoderá excluir todos esses registros.

Nesse caso, a resposta à sua pergunta seria DELETE /records?id=1&id=2&id=3.

Martin Ždila
fonte
1
Eu também cheguei a esta conclusão: basta inverter o verbo para o que você deseja fazer. Não entendo como o que vale para GET não vale para DELETE.
Luke Puplett
9
GET /records?id=1&id=2&id=3que não significa “conseguir os três registros com IDs 1, 2 & 3”, que significa “tirar o único recurso com o caminho URL / registros? id = 1 & id = 2 & id = 3”, que pode ser uma imagem de um nabo, um texto simples documento contendo o número "42" em chinês, ou pode não existir.
Nicholas Shanks
Considere o seguinte: duas solicitações sequenciais para /records?id=1e /records?id=2são enviadas e suas respostas armazenadas em cache por algum intermediário (por exemplo, seu navegador ou ISP). Se a Internet sabia o que seu aplicativo queria dizer com isso, então é lógico que uma solicitação de /records?id=1&id=2poderia ser retornada pelo cache simplesmente mesclando (de alguma forma) os dois resultados que já possui, sem ter que perguntar ao servidor de origem. Mas isso não é possível. /records?id=1&id=2pode ser inválido (apenas 1 ID permitido por solicitação) ou pode retornar algo completamente diferente (um nabo).
Nicholas Shanks
Este é um problema básico de cache de recursos. Se meu DBA alterou o estado diretamente, os caches agora estão fora de sincronia. Você dá um exemplo 410 retornado pelo intermediário, mas 410 é para remoções permanentes, após DELETE um cache pode limpar seu slot para aquele URL, mas não enviará um 410 ou 404, pois não sabe se é um DBA não apenas colocou o recurso imediatamente de volta à origem.
Luke Puplett
4
@NicholasShanks Eu realmente discordo. Se os resultados forem armazenados em cache, isso é culpa do servidor. E se você está falando sobre o design da API, provavelmente é você quem está escrevendo o código para o servidor. Quer você use id[]=1&id[]=2ou id=1&id=2na string de consulta para representar uma matriz de valores, essa string de consulta representa exatamente isso. E eu acho que é extremamente comum e uma boa prática ter a string de consulta representando um filtro. Além disso, se você permitir exclusões e atualizações, não armazene em cache as GETsolicitações. Se você fizer isso, os clientes ficarão obsoletos.
Joseph Nields
8

Acho que o Mozilla Storage Service SyncStorage API v1.5 é uma boa maneira de excluir vários registros usando REST.

Exclui uma coleção inteira.

DELETE https://<endpoint-url>/storage/<collection>

Exclui vários BSOs de uma coleção com uma única solicitação.

DELETE https://<endpoint-url>/storage/<collection>?ids=<ids>

ids : exclui BSOs da coleção cujos ids estão na lista separada por vírgulas fornecida. Um máximo de 100 ids podem ser fornecidos.

Exclui o BSO no local fornecido.

DELETE https://<endpoint-url>/storage/<collection>/<id>

http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions

bootsoon
fonte
Esta parece ser uma boa solução. Eu acho que se Mozilla acha que está correto, então deve estar? A única questão então é o tratamento de erros. Suponha que eles passem? Ids = 1,2,3 e id 3 não existe, você exclui 1 e 2 e responde com 200 porque o solicitante deseja que 3 desapareça e ele não esteja lá, então não importa? ou e se eles estiverem autorizados a excluir 1, mas não 2 ... você não exclui nada e responde com um erro ou você exclui o que pode e deixa os outros ...
tempcke
Normalmente, retornarei uma resposta bem-sucedida porque o estado final é o mesmo, independentemente. Isso simplifica a lógica no cliente também, pois eles não precisam mais lidar com esse estado de erro. Quanto ao caso de autorização, eu simplesmente reprovaria a solicitação inteira ... mas realmente depende do seu caso de uso.
Nathan Phetteplace
3

Esta parece ser uma área cinzenta da convenção REST.

Sim, até agora só encontrei um guia de design da API REST que menciona operações em lote (como uma exclusão de lote): o guia de design da API do Google .

Este guia menciona a criação de métodos "personalizados" que podem ser associados por meio de um recurso usando dois-pontos, por exemplo https://service.name/v1/some/resource/name:customVerb, também menciona explicitamente operações em lote como caso de uso:

Um método personalizado pode ser associado a um recurso, coleção ou serviço. Pode levar uma solicitação arbitrária e retornar uma resposta arbitrária e também oferece suporte a solicitação e resposta de streaming. Os métodos [...] personalizados devem usar o verbo HTTP POST, uma vez que tem a semântica mais flexível [...] Para métodos críticos de desempenho, pode ser útil fornecer métodos de lote personalizados para reduzir a sobrecarga por solicitação .

Portanto, você pode fazer o seguinte de acordo com o guia de API do Google:

POST /api/path/to/your/collection:batchDelete

... para deletar vários itens do seu recurso de coleção.

B12Toaster
fonte
É uma solução viável que a lista de itens seja comunicada por meio de uma matriz formatada em JSON?
Daniele
sim claro. você pode POSTAR uma carga útil na qual os ids são enviados por meio de uma matriz json.
B12Toaster
É interessante que o guia da API do Google disse If the HTTP verb used for the custom method does not accept an HTTP request body (GET, DELETE), the HTTP configuration of such method must not use the body clause at all,no capítulo Método personalizado. Mas a GET accounts.locations.batchGetAPI é o método GET com corpo. Isso é estranho. developers.google.com/my-business/reference/rest/v4/…
鄭元傑
@ 鄭元傑 concordo, parece um pouco estranho à primeira vista, mas se você olhar de perto, é na verdade um POSTmétodo http usado e apenas o método personalizado é nomeado batchGet. Acho que o Google faz isso para (a) seguir a regra de que todos os métodos personalizados precisam ser POST(veja minha resposta) e (b) para tornar mais fácil para as pessoas colocarem um "filtro" no corpo para que você não precise escape ou codifique o filtro como nas strings de consulta. a desvantagem, é claro, é que isso não é mais armazenável em cache ...
B12Toaster
https://service.name/v1/some/resource/name:customVerbnão é RESTful por definição.
deamon
2

Permiti uma substituição total de uma coleção, por exemplo, PUT ~/people/123/shoesonde o corpo é a representação da coleção inteira.

Isso funciona para pequenas coleções filho de itens em que o cliente deseja revisar os itens e remover alguns e adicionar outros e, em seguida, atualizar o servidor. Eles podem PUT uma coleção vazia para deletar tudo.

Isso significaria GET ~/people/123/shoes/9que ainda permaneceria no cache mesmo que um PUT o excluísse, mas isso é apenas um problema de cache e seria um problema se outra pessoa excluísse o sapato.

Minhas APIs de dados / sistemas sempre usam ETags em vez de tempos de expiração para que o servidor seja atingido em cada solicitação e eu exijo os cabeçalhos de versão / simultaneidade corretos para alterar os dados. Para APIs que são somente leitura e alinhadas com visualização / relatório, eu uso tempos de expiração para reduzir ocorrências na origem, por exemplo, um placar pode durar 10 minutos.

Para coleções muito maiores, como ~/people, tendo a não precisar de várias exclusões, o caso de uso tende a não surgir naturalmente e, portanto, um único DELETE funciona bem.

No futuro, e por experiência com a construção de APIs REST e enfrentando os mesmos problemas e requisitos, como auditoria, eu estaria inclinado a usar apenas verbos GET e POST e projetar em torno de eventos, por exemplo, POST um evento de mudança de endereço, embora eu suspeite que virá com seu próprio conjunto de problemas :)

Eu também permitiria que os desenvolvedores de front-end criassem suas próprias APIs que consomem APIs de back-end mais rígidas, uma vez que muitas vezes há razões práticas e válidas do lado do cliente pelas quais eles não gostam de designs de API REST estritos "fanáticos por Fielding" e para produtividade e motivos de camadas de cache.

Luke Puplett
fonte
Adorei essa resposta até ler a última frase :) Nunca vi um caso de uso em que a aplicação de REST estrito tivesse um efeito negativo na rede. Claro que pode fazer com que mais código seja escrito em ambas as extremidades, mas você acaba com um sistema mais seguro, limpo e menos acoplado.
Nicholas Shanks
Haha. Na verdade, tornou-se um padrão! O back-end para front-end é chamado de radar de tecnologia da ThoughtWorks. Ele também permite que mais lógica de aplicativo seja escrita, o que seria complicado, digamos, em JavaScript, e, obviamente, pode ser atualizado sem um cliente, portanto, atualize, digamos, para um aplicativo iOS.
Luke Puplett
Lendo rapidamente os quatro primeiros resultados do Google, parece que essa técnica de melhor qualidade só pode funcionar quando os clientes estão sob seu controle . Os desenvolvedores do cliente desenvolvem a API que desejam, mapeando chamadas para APIs de microsserviço que são o back end real. Neste diagrama: samnewman.io/patterns/architectural/bff/#bff Eu colocaria a linha "Perímetro" abaixo das caixas de BFF - cada caixa é simplesmente parte do cliente. Ele pode até residir fora do data center que hospeda os microsserviços. Também não vejo como REST não se aplica a ambas as interfaces (cliente / BFF e BFF / microsserviço).
Nicholas Shanks
1
Sim, é um bom ponto. Geralmente é para quando você tem microsserviços de criação de equipe e uma equipe fazendo um aplicativo angular, por exemplo, e essa equipe de desenvolvimento é mais do tipo front-end que não gosta de ter que trabalhar contra um monte de pequenos serviços puristas. Embora eu não veja nenhuma razão pela qual você não possa usar o mesmo padrão para abstrair microsserviços e agregação em uma fachada mais utilizável para seus clientes, de forma que os microsserviços possam ser alterados sem afetar a fachada.
Luke Puplett
Um endpoint de API deve modelar as necessidades do domínio e do negócio. Codifique para resolver esses problemas e evitar o excesso de engenharia para aderir às especificações rígidas e muitas vezes inflexíveis. REST nada mais é do que diretrizes de qualquer maneira.
Victorio Berra,