API RESTful: verbos HTTP com URLs compartilhados ou específicos?

25

Ao criar uma API RESTful , devo usar verbos HTTP no mesmo URL (quando possível) ou devo criar um URL específico por ação?

Por exemplo:

GET     /items      # Read all items
GET     /items/:id  # Read one item
POST    /items      # Create a new item
PUT     /items/:id  # Update one item
DELETE  /items/:id  # Delete one item

Ou com URLs específicos, como:

GET     /items            # Read all items
GET     /item/:id         # Read one item
POST    /items/new        # Create a new item
PUT     /item/edit/:id    # Update one item
DELETE  /item/delete/:id  # Delete one item
53777A
fonte

Respostas:

46

No seu último esquema, você mantém verbos nos URLs dos seus recursos. Isso deve ser evitado, pois os verbos HTTP devem ser usados ​​para esse fim. Aceite o protocolo subjacente em vez de ignorá-lo, duplicá-lo ou substituí-lo.

Basta olhar DELETE /item/delete/:id, você coloca a mesma informação duas vezes na mesma solicitação. Isso é supérfluo e deve ser evitado. Pessoalmente, eu ficaria confuso com isso. A API realmente suporta DELETEsolicitações? E se eu colocar deletena URL e usar um verbo HTTP diferente? Será que vai combinar alguma coisa? Em caso afirmativo, qual será escolhido? Como cliente de uma API projetada corretamente, não deveria ter que fazer essas perguntas.

Talvez você precise de alguma forma oferecer suporte a clientes que não podem emitir DELETEou PUTsolicitar. Se for esse o caso, eu passaria essas informações em um cabeçalho HTTP. Algumas APIs usam um X-HTTP-Method-Overridecabeçalho para esse fim específico (que, de qualquer maneira, acho feio). Eu certamente não colocaria os verbos nos caminhos.

Ir para

GET     /items      # Read all items
GET     /items/:id  # Read one item
POST    /items      # Create a new item
PUT     /items/:id  # Update one item
DELETE  /items/:id  # Delete one item

O que é importante sobre os verbos é que eles já estão bem definidos na especificação HTTP e permanecer alinhados com essas regras permite usar caches, proxies e possivelmente outras ferramentas externas ao seu aplicativo que entendem a semântica do HTTP, mas não a semântica do aplicativo . Observe que o motivo pelo qual você deve evitar tê-los em seus URLs não é sobre APIs RESTful que exigem URLs legíveis. Trata-se de evitar ambiguidade desnecessária.

Além disso, uma API RESTful pode mapear esses verbos (ou qualquer subconjunto deles) para qualquer conjunto de semânticas de aplicativos, desde que não atenda à especificação HTTP. Por exemplo, é perfeitamente possível construir uma API RESTful que apenas usos requisições GET, se todas as operações que ele permite são ambos segura e idempotent . O mapeamento acima é apenas um exemplo que se ajusta ao seu caso de uso e é compatível com as especificações. Não precisa necessariamente ser assim.

Lembre-se também de que uma API verdadeiramente RESTful nunca deve exigir que um programador leia documentação extensa de URLs disponíveis, desde que você esteja em conformidade com o princípio HATEOAS (Hipertexto como o mecanismo do estado do aplicativo), que é uma das principais premissas do REST . Os links podem ser totalmente incompreensíveis para os seres humanos, desde que o aplicativo cliente possa entendê-los e usá-los para descobrir possíveis transições de estado do aplicativo.

toniedzwiedz
fonte
4
Na ausência de PUTe DELETE, eu preferiria adicioná-lo ao caminho, sem diferenciá-lo com uma string de consulta. Não é uma modificação de cadeia de consulta para uma operação existente; é uma operação separada.
Robert Harvey
4
@RobertHarvey, neste caso, eu chamaria de hack de qualquer maneira. Como você diz, é uma operação e isso não é algo que eu colocaria no caminho ao projetar uma API que visa ser RESTful. Colocá-lo na string de consulta parece menos invasivo. Isso evita o armazenamento em cache, mas não acho que as respostas a esse tipo de solicitação sejam armazenadas em cache de qualquer maneira. Também permite que o consumidor da API indique facilmente o método sem analisar ou construir o URL. Idealmente, uma API verdadeiramente RESTful deve fornecer os hiperlinks sem exigir que os clientes criem URLs eles mesmos.
toniedzwiedz
Se você não possui todos os verbos, ele não é completamente RESTful, não é?
9788 Robert
@RobertHarvey true, mas eu os trato como um substituto, não como o design pretendido. Eu imagino que a API deva suportar métodos HTTP reais e, se algum cliente não puder implementá-los por qualquer motivo, eles poderão substituir seu uso por esses parâmetros de consulta. Um proxy pode até pegar esses itens rapidamente e transformar as solicitações em solicitações usando verbos HTTP originais, para que o servidor nem precise se preocupar. Poucas APIs ao redor são verdadeiramente RESTful. Quando se trata de APIs genéricas da Web, é realmente uma questão de gosto. Pessoalmente, eu usaria URLs limpos. Mais fácil de entender IMHO.
toniedzwiedz
1
@RobertHarvey, como explicado, dificilmente é a maneira pretendida de usá-los. Eu acho isso o menor dos dois males para quando você precisa superar as limitações do cliente. Lembro-me de ler uma documentação para essa API, mas terei que fazer algumas escavações no histórico / favoritos do navegador para encontrá-la. Agora que penso nisso, um cabeçalho pode ser melhor nesse caso. Você concordaria?
toniedzwiedz
14

O primeiro.

Um URI / URL é um identificador de recurso (dica no nome: identificador uniforme de recurso). Com a primeira convenção, o recurso mencionado quando você faz "GET / user / 123" e o recurso discutido quando você faz "DELETE / user / 123" é claramente o mesmo recurso, porque eles têm a mesma URL.

Com a segunda convenção, você não pode ter certeza de que "GET / user / 123" e "DELETE / user / delete / 123" são realmente o mesmo recurso e parece implicar que você esteja excluindo um recurso relacionado em vez do recurso por si só, portanto, seria bastante surpreendente que a exclusão /user/delete/123realmente exclua /user/123. Se todas as operações funcionarem em URLs diferentes, o URI não funcionará mais como um identificador de recurso.

Quando você diz DELETE /user/123, está dizendo "excluir 'registro do usuário com o ID 123'". Enquanto você diz DELETE /user/delete/123, o que parece estar implicando em "excluir 'registro de deletor de usuário com o ID 123'", o que provavelmente não é o que você deseja dizer. E mesmo se você usar o verbo mais correto nessa situação: "POST / user / delete / 123", que diz "faça a operação anexada a 'user deletor com id 123'", ainda é uma maneira indireta de excluir um registro (isso é semelhante à substantivo de um verbo em inglês).

Uma maneira de pensar sobre a URL é tratá-la como ponteiros para objetos e recursos como objetos na programação orientada a objetos. Quando você faz GET /user/123, DELETE /user/123você pode pensar pensar neles como métodos no objeto: [/user/123].get(), [/user/123].delete()onde a []é como um ponteiro dereferencing operador mas para URLs (se você sabe uma língua que tem ponteiros). Um dos princípios subjacentes do REST é a interface uniforme, ou seja, ter um conjunto pequeno e limitado de verbos / métodos que funciona para tudo em uma vasta rede de recursos / objetos.

Portanto, o primeiro é melhor.

PS: é claro, isso é olhar para o REST da maneira mais pura. Às vezes, a praticidade supera a pureza e você precisa fazer concessões para clientes com morte encefálica ou estrutura que dificulte o REST adequado.

Lie Ryan
fonte
+1 para o exemplo OOP :)
53777A 16/16/14
6

(desculpe, na primeira vez que perdi o / edit / e / delete / no (2) ...)

A idéia do URI é que ele é um identificador de um recurso endereçável , em vez de uma invocação de método . Portanto, o URI deve apontar para um recurso específico. E se você deferir o URI, sempre deverá obter o mesmo recurso.

Ou seja, você deve pensar em URIs da mesma maneira que pensa na Chave Primária de uma linha em um banco de dados. Ele identifica exclusivamente algo: Identificador Universal de Recursos.

Portanto, se você usa plural ou singular, o URI deve ser um identificador e não uma invocação . O que você está tentando fazer está no método, a saber: GET (get), PUT (criar / atualizar), DELETE (excluir) ou POST (tudo o resto).

Portanto, "/ item / delete / 123" interrompe o REST porque não aponta para um recurso, é mais uma invocação de método.

(Além disso, apenas semântica, você deve obter um URI, decidir que está desatualizado e EXCLUIR o mesmo URI - porque é um identificador. Se o GET URI não tiver "/ delete /" e DELETE, isso vai contra a semântica HTTP. Você está transmitindo 2 ou mais URIs por recurso, onde 1 fará.)

Agora, o truque é este: não existe uma definição clara e clara do que é e do que não é um recurso; portanto, o esquivo comum no REST é definir um "substantivo de processamento" e apontar o URI para ele. É praticamente um jogo de palavras, mas satisfaz a semântica.

Portanto, se, por exemplo, você realmente não puder usar isso por algum motivo:

DELETE /items/123

você pode declarar ao mundo que possui um recurso de processamento "deletor" e usar

POST /items/deletor  { id: 123 }

Agora, isso se parece muito com o RPC (Remote Procedure Call), mas cai na vasta lacuna da cláusula "processamento de dados" da especificação POST, nomeada na especificação HTTP.

No entanto, fazer isso é excepcional e, se você puder usar o PUT comum para criar / atualizar, DELETE para excluir e POST para anexar, criar e tudo mais, deverá fazê-lo, porque é um uso mais padrão do HTTP. Mas se você tiver um caso complicado como "confirmar" ou "publicar" ou "redigir", o caso para o uso de um nome de processador satisfaz os puristas do REST e ainda fornece a semântica de que você precisa.

Roubar
fonte