Estou escrevendo um serviço RESTful para um sistema de gerenciamento de clientes e estou tentando encontrar a melhor prática para atualizar parcialmente os registros. Por exemplo, desejo que o chamador possa ler o registro completo com uma solicitação GET. Mas, para atualizá-lo, apenas certas operações no registro são permitidas, como alterar o status de ENABLED para DISABLED. (Eu tenho cenários mais complexos que isso)
Não quero que o chamador envie o registro inteiro apenas com o campo atualizado por razões de segurança (também parece um exagero).
Existe uma maneira recomendada de construir os URIs? Ao ler os livros REST, as chamadas no estilo RPC parecem desaprovadas.
Se a chamada a seguir retornar o registro completo do cliente com o ID 123
GET /customer/123
<customer>
{lots of attributes}
<status>ENABLED</status>
{even more attributes}
</customer>
como devo atualizar o status?
POST /customer/123/status
<status>DISABLED</status>
POST /customer/123/changeStatus
DISABLED
...
Atualização : Para aumentar a pergunta. Como alguém incorpora 'chamadas de lógica de negócios' em uma API REST? Existe uma maneira acordada de fazer isso? Nem todos os métodos são CRUD por natureza. Alguns são mais complexos, como ' sendEmailToCustomer (123) ', ' mergeCustomers (123, 456) ', ' countCustomers () '
POST /customer/123?cmd=sendEmail
POST /cmd/sendEmail?customerId=123
GET /customer/count
POST
próprio Roy Fielding: roy.gbiv.com/untangled/2009/it-is-okay-to-use-post onde a idéia básica é: se não houver é um método (comoGET
ouPUT
) ideal para o seu uso operacionalPOST
.Respostas:
Você basicamente tem duas opções:
Use
PATCH
(mas observe que você precisa definir seu próprio tipo de mídia que especifica exatamente o que acontecerá)Use
POST
para um sub-recurso e retorne 303 Consulte Outro com o cabeçalho Localização apontando para o recurso principal. A intenção do 303 é informar ao cliente: "Eu executei seu POST e o efeito foi que algum outro recurso foi atualizado. Consulte o cabeçalho do local para qual recurso foi". O POST / 303 destina-se a adições iterativas aos recursos para construir o estado de algum recurso principal e é um ajuste perfeito para atualizações parciais.fonte
Você deve usar o POST para atualizações parciais.
Para atualizar os campos do cliente 123, faça um POST para / customer / 123.
Se você deseja atualizar apenas o status, também pode COLOCAR em / customer / 123 / status.
Geralmente, as solicitações GET não devem ter efeitos colaterais, e PUT é para gravar / substituir todo o recurso.
Isso segue diretamente do HTTP, como pode ser visto aqui: http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods
fonte
/customer/123
deveria criar a coisa óbvia que está logicamente sob o cliente 123. Talvez um pedido? PUT/customer/123/status
parece fazer mais sentido, assumindo que o POST/customers
criou implicitamente umstatus
(e supondo que seja REST legítimo).POST
não precisam ser não-idempotentes. E, como mencionado,PUT
deve substituir um recurso inteiro.Você deve usar PATCH para atualizações parciais - usando documentos json-patch (consulte http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08 ou http://www.mnot.net/ blog / 2012/09/05 / patch ) ou a estrutura de patches XML (consulte http://tools.ietf.org/html/rfc5261 ). Na minha opinião, porém, o json-patch é o mais adequado para o seu tipo de dados corporativos.
PATCH com documentos de correção JSON / XML possui semântica direta muito direta para atualizações parciais. Se você começar a usar o POST, com cópias modificadas do documento original, para atualizações parciais, logo encontrará problemas nos quais você deseja que valores ausentes (ou melhor, valores nulos) representem "ignorar esta propriedade" ou "definir essa propriedade como valor vazio "- e isso leva a um buraco de soluções invadidas que, no final, resultam no seu próprio tipo de formato de patch.
Você pode encontrar uma resposta mais detalhada aqui: http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html .
fonte
Estou com um problema semelhante. PUT em um sub-recurso parece funcionar quando você deseja atualizar apenas um único campo. No entanto, às vezes você deseja atualizar várias coisas: Pense em um formulário da Web representando o recurso com a opção de alterar algumas entradas. O envio do formulário pelo usuário não deve resultar em várias PUTs.
Aqui estão duas soluções em que consigo pensar:
PUT com todo o recurso. No lado do servidor, defina a semântica que uma PUT com todo o recurso ignora todos os valores que não foram alterados.
faça um PUT com um recurso parcial. No lado do servidor, defina a semântica disso como uma mesclagem.
2 é apenas uma otimização de largura de banda de 1. Às vezes 1 é a única opção se o recurso define que alguns campos são campos obrigatórios (pense em proto buffers).
O problema com essas duas abordagens é como limpar um campo. Você precisará definir um valor nulo especial (especialmente para proto-buffers, pois os valores nulos não são definidos para proto-buffers) que causarão a limpeza do campo.
Comentários?
fonte
Para modificar o status, acho que uma abordagem RESTful é usar um sub-recurso lógico que descreva o status dos recursos. Esse IMO é bastante útil e limpo quando você tem um conjunto reduzido de status. Isso torna sua API mais expressiva sem forçar as operações existentes para o recurso do cliente.
Exemplo:
O serviço POST deve retornar o cliente recém-criado com o ID:
O GET para o recurso criado usaria o local do recurso:
Um GET / customer / 123 / inativo deve retornar 404
Para a operação PUT, sem fornecer uma entidade Json, apenas atualizará o status
Fornecer uma entidade permitirá que você atualize o conteúdo do cliente e atualize o status ao mesmo tempo.
Você está criando um sub-recurso conceitual para o recurso do cliente. Também é consistente com a definição de Roy Fielding de um recurso: "... Um recurso é um mapeamento conceitual para um conjunto de entidades, não a entidade que corresponde ao mapeamento em qualquer ponto específico do tempo ..." Nesse caso, o o mapeamento conceitual é cliente ativo para cliente com status = ATIVO.
Leia operação:
Se você fizer essas chamadas uma após a outra, retornar o status 404, a saída bem-sucedida pode não incluir o status implícito. É claro que você ainda pode usar GET / customer / 123? Status = ACTIVE | INACTIVE para consultar diretamente o recurso do cliente.
A operação DELETE é interessante, pois a semântica pode ser confusa. Mas você tem a opção de não publicar essa operação para esse recurso conceitual ou usá-la de acordo com sua lógica de negócios.
Esse pode levar seu cliente a um status DELETED / DISABLED ou ao status oposto (ATIVO / INATIVO).
fonte
Itens a serem adicionados à sua pergunta aumentada. Acho que muitas vezes você pode projetar perfeitamente ações de negócios mais complicadas. Mas você precisa revelar o estilo de método / procedimento e pensar mais em recursos e verbos.
envios de correio
A implementação desse recurso + POST enviaria o email. se necessário, você pode oferecer algo como / customer / 123 / outbox e oferecer links de recursos para / customer / mails / {mailId}.
contagem de clientes
Você poderia lidar com isso como um recurso de pesquisa (incluindo metadados de pesquisa com informações de paginação e número encontrado, o que fornece a contagem de clientes).
fonte
Use PUT para atualizar o recurso incompleto / parcial.
Você pode aceitar o jObject como parâmetro e analisar seu valor para atualizar o recurso.
Abaixo está a função que você pode usar como referência:
fonte
Em relação à sua atualização.
Acredito que o conceito de CRUD tenha causado alguma confusão em relação ao design da API. CRUD é um conceito geral de baixo nível para operações básicas executarem dados, e verbos HTTP são apenas métodos de solicitação ( criados há 21 anos ) que podem ou não ser mapeados para uma operação CRUD. De fato, tente encontrar a presença do acrônimo CRUD na especificação HTTP 1.0 / 1.1.
Um guia muito bem explicado que aplica uma convenção pragmática pode ser encontrado na documentação da API da plataforma em nuvem do Google . Ele descreve os conceitos por trás da criação de uma API baseada em recursos, que enfatiza uma grande quantidade de recursos sobre as operações e inclui os casos de uso que você está descrevendo. Embora seja apenas um design de convenção para o produto deles, acho que faz muito sentido.
O conceito básico aqui (e um que gera muita confusão) é o mapeamento entre "métodos" e verbos HTTP. Uma coisa é definir quais "operações" (métodos) sua API fará sobre quais tipos de recursos (por exemplo, obtenha uma lista de clientes ou envie um email) e outra são os verbos HTTP. Deve haver uma definição de ambos, os métodos e os verbos que você planeja usar e um mapeamento entre eles .
Ele também diz que, quando uma operação não mapeia exatamente com um método padrão (
List
,Get
,Create
,Update
,Delete
neste caso), pode-se usar "métodos personalizados", comoBatchGet
, que recupera vários objetos com base em vários input object id, ouSendEmail
.fonte
RFC 7396 : JSON Merge Patch (publicado quatro anos após a publicação da pergunta) descreve as práticas recomendadas para um PATCH em termos de regras de formato e processamento.
Em poucas palavras, você envia um HTTP PATCH para um recurso de destino com o tipo de mídia application / merge-patch + json MIME e um corpo representando apenas as partes que você deseja alterar / adicionar / remover e, em seguida, siga as regras de processamento abaixo.
Regras :
Exemplos de casos de teste que ilustram as regras acima (como visto no apêndice dessa RFC):
fonte
Confira http://www.odata.org/
Ele define o método MERGE, portanto, no seu caso, seria algo como isto:
Somente a
status
propriedade é atualizada e os outros valores são preservados.fonte
MERGE
um verbo HTTP válido?MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE.
Veja docs.oasis-open.org/odata/new-in-odata/v4.0/cn01/…Não importa. Em termos de REST, você não pode fazer um GET, porque não é armazenável em cache, mas não importa se você usa POST ou PATCH ou PUT ou o que quer que seja, e não importa a aparência da URL. Se você estiver executando o REST, o que importa é que, quando você obtém uma representação de seu recurso no servidor, essa representação é capaz de fornecer ao cliente opções de transição de estado.
Se sua resposta GET teve transições de estado, o cliente só precisa saber como lê-las e o servidor pode alterá-las, se necessário. Aqui, uma atualização é feita usando POST, mas se ela foi alterada para PATCH ou se a URL for alterada, o cliente ainda saberá como fazer uma atualização:
Você pode ir até a lista de parâmetros obrigatórios / opcionais para o cliente devolver a você. Depende da aplicação.
No que diz respeito às operações de negócios, esse pode ser um recurso diferente vinculado ao recurso do cliente. Se você deseja enviar um email para o cliente, talvez esse serviço seja um recurso próprio para o qual você pode POST, portanto, você pode incluir a seguinte operação no recurso do cliente:
Alguns bons vídeos e exemplos da arquitetura REST do apresentador são estes. O Stormpath usa apenas GET / POST / DELETE, o que é bom, já que o REST não tem nada a ver com as operações que você usa ou com a aparência das URLs (exceto que as GETs devem ser armazenadas em cache):
https://www.youtube.com/watch?v=pspy1H6A3FM ,
https://www.youtube.com/watch?v=5WXYw4J4QOU ,
http://docs.stormpath.com/rest/quickstart/
fonte