Quais padrões de design comprovados existem para operações em lote de recursos em um serviço da web estilo REST?
Estou tentando encontrar um equilíbrio entre ideais e realidade em termos de desempenho e estabilidade. Agora temos uma API em que todas as operações são recuperadas de um recurso da lista (por exemplo: GET / user) ou em uma única instância (PUT / user / 1, DELETE / user / 22, etc.).
Existem alguns casos em que você deseja atualizar um único campo de um conjunto inteiro de objetos. Parece muito desperdício enviar a representação inteira de cada objeto para frente e para trás para atualizar o campo único.
Em uma API de estilo RPC, você pode ter um método:
/mail.do?method=markAsRead&messageIds=1,2,3,4... etc.
Qual é o equivalente REST aqui? Ou está certo comprometer-se de vez em quando. Isso arruina o design para adicionar algumas operações específicas, onde realmente melhora o desempenho, etc.? O cliente em todos os casos agora é um navegador da Web (aplicativo javascript no lado do cliente).
fonte
PATCH
- não há necessidade de criatividade nesse caso.Nem um pouco - acho que o equivalente ao REST é (ou pelo menos uma solução é) quase exatamente isso - uma interface especializada projetada acomoda uma operação exigida pelo cliente.
Lembro-me de um padrão mencionado no livro Ajax in Action, de Crane e Pascarello (um excelente livro, aliás - altamente recomendado), no qual eles ilustram a implementação de um tipo de objeto CommandQueue, cuja tarefa é enfileirar solicitações em lotes e depois publique-os no servidor periodicamente.
O objeto, se bem me lembro, basicamente apenas continha uma matriz de "comandos" - por exemplo, para estender seu exemplo, cada um um registro contendo um comando "markAsRead", um "messageId" e talvez uma referência a um retorno de chamada / manipulador função - e, de acordo com algum agendamento ou ação do usuário, o objeto de comando seria serializado e publicado no servidor, e o cliente trataria do conseqüente pós-processamento.
Por acaso não tenho os detalhes à mão, mas parece que uma fila de comandos desse tipo seria uma maneira de lidar com o seu problema; reduziria substancialmente a propriedade geral e abstraia a interface do lado do servidor de uma maneira que você possa achar mais flexível no futuro.
Atualização : Aha! Encontrei um trecho desse livro on-line, completo com exemplos de código (embora eu ainda sugira pegar o livro real!). Dê uma olhada aqui , começando com a seção 5.5.3:
Aqui estão duas funções pertinentes - uma responsável por adicionar comandos à fila (
addCommand
) e uma responsável por serializar e depois enviá-las ao servidor (fireRequest
):Isso deve fazer você ir. Boa sorte!
fonte
Embora eu ache que o @Alex está no caminho certo, conceitualmente acho que deve ser o inverso do que é sugerido.
O URL está em vigor "os recursos que estamos direcionando", portanto:
significa obter o registro do correio com o ID 1 e
significa corrigir o registro de mensagens com o ID 1. A querystring é um "filtro", filtrando os dados retornados da URL.
Então, aqui estamos solicitando todos os emails já marcados como lidos. Portanto, [PATCH] para esse caminho seria dizer "corrigir os registros já marcados como verdadeiros" ... o que não é o que estamos tentando alcançar.
Portanto, um método em lote, seguindo este pensamento, deve ser:
é claro que não estou dizendo que isso é verdadeiro REST (que não permite manipulação de registros em lote), mas segue a lógica já existente e em uso pelo REST.
fonte
[GET]
formato a ser feito[PATCH] mail?markAsRead=true data: [{"id": 1}, {"id": 2}, {"id": 3}]
(ou mesmo apenasdata: {"ids": [1,2,3]}
)? Outro benefício dessa abordagem alternativa é que você não encontrará erros "414 Request URI too long" se estiver atualizando centenas / milhares de recursos na coleção.Seu idioma, " Parece muito inútil ...", para mim indica uma tentativa de otimização prematura. A menos que seja possível demonstrar que o envio de toda a representação de objetos é um grande problema de desempenho (estamos falando inaceitáveis para usuários com mais de 150 ms), não faz sentido tentar criar um novo comportamento de API não padrão. Lembre-se, quanto mais simples a API, mais fácil é usar.
Para exclusões, envie o seguinte, pois o servidor não precisa saber nada sobre o estado do objeto antes que a exclusão ocorra.
O próximo pensamento é que, se um aplicativo estiver enfrentando problemas de desempenho relacionados à atualização em massa de objetos, deve-se considerar a possibilidade de dividir cada objeto em vários objetos. Dessa forma, a carga útil JSON é uma fração do tamanho.
Como exemplo, ao enviar uma resposta para atualizar os status "lido" e "arquivado" de dois emails separados, você deverá enviar o seguinte:
Eu dividiria os componentes mutáveis do email (lidos, arquivados, importantes, marcadores) em um objeto separado, pois os outros (para, de, assunto, texto) nunca seriam atualizados.
Outra abordagem a ser adotada é aproveitar o uso de um PATCH. Para indicar explicitamente quais propriedades você pretende atualizar e que todas as outras devem ser ignoradas.
As pessoas afirmam que PATCH deve ser implementado fornecendo uma matriz de alterações contendo: ação (CRUD), caminho (URL) e alteração de valor. Isso pode ser considerado uma implementação padrão, mas se você analisar a totalidade de uma API REST, é uma ocorrência não intuitiva. Além disso, a implementação acima é como o GitHub implementou o PATCH .
Para resumir, é possível aderir aos princípios RESTful com ações em lote e ainda ter um desempenho aceitável.
fonte
A API do Google Drive possui um sistema realmente interessante para resolver esse problema ( veja aqui ).
O que eles fazem é basicamente agrupar solicitações diferentes em uma
Content-Type: multipart/mixed
solicitação, com cada solicitação individual individual separada por algum delimitador definido. Os cabeçalhos e o parâmetro de consulta da solicitação em lote são herdados para solicitações individuais (ou sejaAuthorization: Bearer some_token
), a menos que sejam substituídos na solicitação individual.Exemplo : (retirado dos documentos )
Solicitação:
Resposta:
fonte
Eu ficaria tentado em uma operação como a do seu exemplo a escrever um analisador de intervalo.
Não é muito complicado criar um analisador que possa ler "messageIds = 1-3,7-9,11,12-15". Certamente aumentaria a eficiência das operações gerais que cobrem todas as mensagens e é mais escalável.
fonte
Ótimo post. Estou procurando uma solução há alguns dias. Eu vim com uma solução de usar passar uma string de consulta com um ID de grupo separados por vírgulas, como:
... passando isso para uma
WHERE IN
cláusula no meu SQL. Funciona muito bem, mas se pergunta o que os outros pensam dessa abordagem.fonte
DELETE /books/delete?id=1,2,3
quando o livro nº 3 não existe - oWHERE IN
silenciosamente ignorará os registros, enquanto eu normalmente esperariaDELETE /books/delete?id=3
404 se 3 não existir.Do meu ponto de vista, acho que o Facebook tem a melhor implementação.
Uma única solicitação HTTP é feita com um parâmetro em lote e um para um token.
No lote, um json é enviado. que contém uma coleção de "solicitações". Cada solicitação possui uma propriedade de método (get / post / put / delete / etc ...) e uma propriedade relative_url (uri do terminal), além disso, os métodos post e put permitem uma propriedade "body" na qual os campos são atualizados são enviadas .
mais informações em: API de lote do Facebook
fonte