API RESTful. Devo estar retornando o objeto que foi criado / atualizado?

36

Estou projetando um serviço Web RESTful usando WebApi e queria saber quais respostas HTTP e corpos de resposta devem retornar ao atualizar / criar objetos.

Por exemplo, eu posso usar o método POST para enviar algum JSON para o serviço da web e criar um objeto. É uma prática recomendada definir o status HTTP como criado (201) ou ok (200) e simplesmente retornar uma mensagem como "Novo funcionário adicionado" ou retornar o objeto que foi enviado originalmente?

O mesmo vale para o método PUT. Qual status HTTP é melhor usar e preciso retornar o objeto que foi criado ou apenas uma mensagem? Considerando o fato de que o usuário sabe qual objeto está tentando criar / atualizar de qualquer maneira.

Alguma ideia?

Exemplo:

Adicionar novo funcionário:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Atualizar funcionário existente:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Respostas:

Resposta com o objeto criado / atualizado

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Resposta com apenas uma mensagem:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Resposta com apenas código de status:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT
iswinky
fonte
2
Boa pergunta, mas usar o termo "melhores práticas" é um tipo de tabu neste site meta.programmers.stackexchange.com/questions/2442/… Você pode apenas reformular a pergunta. meta.programmers.stackexchange.com/questions/6967/…
Snoop
3
Como um acompanhamento, seria uma boa ideia ter um sinalizador na solicitação para que (por exemplo) um aplicativo móvel possa recuperar todo o objeto quando estiver no Wi-Fi, mas apenas o ID ao usar dados de celular? Existe um cabeçalho que deve ser usado para evitar poluir o JSON?
Andrew diz Reinstate Monica
@AndrewPiliser Idéia interessante, embora eu pessoalmente ache que é melhor escolher uma abordagem e cumpri-la. Então, como sua aplicação se desenvolve ou se torna mais popular, otimizá-lo
iswinky
@AndrewPiliser, sua ideia é muito semelhante à UPDATE/INSERT ... RETURNINGvariante Postgresql para SQL. É extremamente útil, especialmente porque mantém atômica a submissão de novos dados e a solicitação da versão atualizada.
beldaz

Respostas:

31

Como na maioria das coisas, isso depende. Sua troca é a facilidade de uso versus o tamanho da rede. Pode ser muito útil para os clientes verem o recurso criado. Pode incluir campos preenchidos pelo servidor, como o horário da última criação. Como você parece incluir o idinvés de usar hateoas, os clientes provavelmente desejam ver o ID do recurso que acabaram de POSTeditar.

Se você não incluir o recurso criado, não crie uma mensagem arbitrária. Os campos 2xx e Local são informações suficientes para que os clientes tenham certeza de que sua solicitação foi tratada adequadamente.

Eric Stein
fonte
+1 O objetivo de não permitir que o cliente componha as URLs também pode ser alcançado, permitindo que o cliente preencha modelos de URL fornecidos pelo servidor com IDs específicos. Sim, o cliente "compõe", mas apenas da maneira "preencher os espaços em branco". Embora o HATEOAS não seja puro, ele atinge o objetivo e torna o trabalho com objetos que possuem um número (grande) de uri de "ação" um pouco menos sensível à largura de banda, sem mencionar quando você coloca esses objetos em uma lista (grande).
Marjan Venema
3
+1 no conselho "por favor não crie uma mensagem arbitrária"
HairOfTheDog
A mensagem "no arbitrary message" não se concentra em mensagens de string ou em qualquer valor de retorno que não seja o recurso criado? Estou focando nos casos em que retornamos o ID do recurso criado (mas não o próprio recurso) e fiquei imaginando onde isso se encaixa.
Flater
12

Pessoalmente, eu sempre volto apenas 200 OK.

Para citar sua pergunta

Considerando o fato de que o usuário sabe qual objeto está tentando criar / atualizar de qualquer maneira.

Por que adicionar largura de banda extra (que talvez precise ser paga) para informar ao cliente o que ele já sabe?

Mawg
fonte
11
Isso é o que eu estava pensando, se for inválido você pode retornar mensagens de validação, mas se é válido e é criado / atualizado em seguida, verificar o código de status HTTP e mostrar ao usuário uma mensagem por exemplo, "Hooray" com base em que
iswinky
3
Consulte stackoverflow.com/a/827045/290182 sobre 200/ 204 No Contentpara evitar confundir jQuery e similares.
beldaz
10

@iswinky Eu sempre enviava de volta a carga no caso de POST e PUT.

No caso do POST, você pode criar a entidade com um ID interno ou um UUID. Portanto, faz sentido enviar de volta a carga útil.

Da mesma forma, no caso de PUT, você pode ignorar alguns campos do usuário (valores imutáveis, por exemplo) ou, no caso de um PATCH, os dados também podem ter sido alterados por outros usuários.

Enviar os dados de volta como foram mantidos é sempre uma boa ideia e definitivamente não prejudica. Se o chamador não precisar desses dados retornados, ele não os processará, mas consumirá apenas o statusCode. Caso contrário, eles podem usar esses dados como algo para atualizar a interface do usuário.

É apenas no caso de um DELETE que eu não enviaria a carga novamente e faria um 200 com um conteúdo de resposta ou um 204 sem um conteúdo de resposta.

Edit: Graças a alguns comentários abaixo, estou reformulando minha resposta. Ainda permaneço pela maneira como desenvolvo minhas APIs e envio respostas de volta, mas acho que faz sentido qualificar alguns dos meus pensamentos sobre design.

a) Quando digo devolver a carga útil, na verdade pretendi dizer devolver os dados do recurso, não a mesma carga que veio na solicitação. Ex: se você enviar uma carga útil de criação, no back-end posso criar outras entidades, como UUID e (talvez) carimbos de data e hora e algumas conexões (gráficas). Eu enviaria tudo isso de volta na resposta (não apenas a carga útil da solicitação como está - o que é inútil).

b) Eu não enviaria respostas de volta caso a carga útil fosse muito grande. Eu discuti isso nos comentários, mas o que eu gostaria de ressaltar é que eu tentaria o meu melhor para projetar minhas APIs ou meus recursos, de modo que não precise ter cargas úteis muito grandes. Eu tentaria dividir os recursos em entidades menores e gerenciáveis, de modo que cada recurso seja definido por 15 a 20 atributos JSON e não maiores.

No caso em que o objeto é muito grande ou o objeto pai está sendo atualizado, eu enviava de volta as estruturas aninhadas como hrefs.

O ponto principal é que eu definitivamente tentaria enviar de volta os dados que fazem sentido para o consumidor / interface do usuário processar imediatamente e ser realizado com uma ação de API atômica, em vez de precisar buscar mais 2-5 API apenas para atualizar a interface do usuário após criação / atualização dos dados.

As APIs de servidor para servidor podem pensar de maneira diferente sobre isso. Estou focando em APIs que proporcionam uma experiência do usuário.

ksprashu
fonte
Vejo muitas situações em que enviar de volta toda a carga útil é uma má ideia, quando a carga útil é grande.
beldaz
2
@beldaz concordo completamente. YMMV com base no design da API REST. Geralmente, evito objetos muito grandes e os divido em uma série de sub-recursos / PUTs. Se a carga útil for muito grande, existem maneiras melhores de fazer isso, e você deve fazer o HATEOAS (como Marjan diz acima), onde você retorna a referência ao objeto em vez do próprio objeto.
21418 ksprashu
@ksprashu: "Portanto, faz sentido enviar de volta a carga" - acho que é uma péssima ideia, pois assim um recurso pode ser obtido de várias maneiras: via GET, como resposta do POST, como resposta do PUT. Isso significa que o cliente obtém 3 recursos com estrutura potencialmente diferente . Onde, como se você retornasse apenas URI (local), sem corpo, a única maneira de obter um recurso seria GET. Isso garantiria que o cliente sempre obtivesse respostas consistentes.
mentallurg 3/09
@allallurg Sei que posso não ter escrito isso direito. Obrigado por apontar isso. Eu editei minha resposta. Deixe-me saber se isso faz mais sentido.
ksprashu 4/09
Contanto que você implemente um serviço para o seu trabalho em casa, isso realmente não importa. Faça como quiser. Economize seu tempo.
mentallurg 4/09
9

Fazendo referência aos padrões RFC do link , você deve retornar o status 201 (criado) ao armazenar com êxito o recurso de solicitação usando Post. Na maioria dos aplicativos, o ID do recurso é gerado pelo próprio servidor, portanto, é uma boa prática retornar o ID do recurso criado. Retornar o objeto inteiro é a sobrecarga para a solicitação Post. A prática ideal é retornar o local da URL do recurso recém-criado.

Por exemplo, você pode consultar o exemplo a seguir que salva o Objeto do funcionário no banco de dados e retorna a URL do objeto de recurso recém-criado como resposta.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Este ponto de extremidade restante retornará a resposta como:

Status 201 - CREATED

Localização do cabeçalho → http: // localhost: 8080 / funcionários / 1

Shubham Bondarde
fonte
Agradável e limpo - vai seguir isso de agora em diante
Hassan Tareq
0

Eu tornaria uma carga útil no corpo do retorno condicional a um parâmetro HTTP.

Na maioria das vezes, é melhor retornar algum tipo de conteúdo de volta ao consumidor da API para evitar viagens de ida e volta desnecessárias (uma das razões pelas quais o GraphQL existe).

De fato, à medida que nossos aplicativos se tornam mais intensivos em dados e distribuídos, tento observar esta diretriz:

Minha orientação :

Sempre que houver um caso de uso que exija um GET imediatamente após um POST ou PUT, é o caso em que seria melhor simplesmente retornar algo na resposta POST / PUT.

Como isso é feito e que tipo de conteúdo retornar de um PUT ou POST, isso é específico do aplicativo. Agora, seria interessante se o aplicativo pudesse parametrizar o tipo de "conteúdo" no corpo da resposta (queremos apenas a localização do novo objeto, ou alguns dos campos, ou o objeto inteiro, etc.)

Um aplicativo pode definir um conjunto de parâmetros que um POST / PUT pode receber para controlar o tipo de "conteúdo" a ser retornado no corpo da resposta. Ou poderia codificar algum tipo de consulta do GraphQL como parâmetro (posso ver isso útil, mas também se tornar um pesadelo de manutenção).

De qualquer forma, parece-me que:

  1. Não há problema (e provavelmente desejável) retornar algo em um corpo de resposta POST / PUT.
  2. Como isso é feito é específico da aplicação e quase impossível de generalizar.
  3. Você não deseja retornar um "contexto" de tamanho grande por padrão (ruído do tráfego que derrota todo o motivo de se afastar dos POST seguidos pelos GETs).

Então, 1) faça, mas 2) mantenha a simplicidade.

Outra opção que eu vi são pessoas criando pontos de extremidade alternativos (por exemplo, / customers para POST / PUT que não retornam nada no corpo e / customer_with_details para POST / PUT para / customers, mas que retornam algo no corpo de resposta).

Eu evitaria essa abordagem, no entanto. O que acontece quando você legitimamente precisa retornar diferentes tipos de conteúdo? Um ponto de extremidade por tipo de conteúdo? Não é escalável ou sustentável.

luis.espinal
fonte