Design de URL RESTful para pesquisa

427

Estou procurando uma maneira razoável de representar pesquisas como URLs RESTful.

A configuração: eu tenho dois modelos, carros e garagens, onde os carros podem estar em garagens. Então, meus URLs se parecem com:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

Um carro pode existir por si só (daí o / car) ou em uma garagem. Qual é a maneira correta de representar, digamos, todos os carros em uma determinada garagem? Algo como:

/garage/yyy/cars     ?

Que tal a união de carros na garagem yyy e zzz?

Qual é o caminho certo para representar uma pesquisa de carros com determinados atributos? Diga: mostre-me todos os sedãs azuis com 4 portas:

/car/search?color=blue&type=sedan&doors=4

ou deveria ser / carros?

O uso de "pesquisa" parece inadequado por lá - qual é a melhor maneira / termo? Deveria ser apenas:

/cars/?color=blue&type=sedan&doors=4

Os parâmetros de pesquisa devem fazer parte do PATHINFO ou QUERYSTRING?

Em resumo, estou procurando orientações para o design de URL REST entre modelos e para pesquisa.

[Atualização] Gosto da resposta de Justin, mas ele não cobre o caso de pesquisa em vários campos:

/cars/color:blue/type:sedan/doors:4

ou algo assim. Como é que vamos

/cars/color/blue

para o caso de múltiplos campos?

Parand
fonte
16
Embora pareça melhor em inglês, misturar /carse /carnão é semântico e, portanto, uma má ideia. Sempre use o plural quando houver mais de um item nessa categoria.
Zaz
4
Essas são respostas ruins. A pesquisa deve usar cadeias de consulta. As strings de consulta são 100% RESTful quando usadas corretamente (ou seja, para pesquisa).
pbreitenbach

Respostas:

435

Para a pesquisa, use cadeias de consulta. Isso é perfeitamente RESTful:

/cars?color=blue&type=sedan&doors=4

Uma vantagem para as cadeias de consulta regulares é que elas são padrão e amplamente compreendidas e que podem ser geradas a partir do form-get.

Pbreitenbach
fonte
42
Isto está correto. O ponto principal das cadeias de consulta é fazer coisas como pesquisa.
aehlke
22
Na verdade, isso está correto, pois, de acordo com o RFC3986 , o caminho e a string de consulta identificam o recurso. Além do mais, nomeação adequada seria simplesmente /cars?color=whatever.
Lloeki
35
E os casos em que você deseja comparadores (>, <, <=,> =)? / carros? classificação <= 3?
Jesse
3
E se você quiser acessar recursos aninhados abaixo da string de consulta? Por exemplo, /cars?color=blue&type=sedan&doors=4/enginesnão vai funcionar
Abe Voelker
9
O @mjs /cars?param=valueserve para filtragem simples na lista de carros e /cars/search?param=valueé para criar uma pesquisa (com ou sem persistência) em que o resultado pode conter pontuação, categorização, etc. Você também pode criar / excluir uma pesquisa nomeada como /cars/search/mysearch. Veja isso: stackoverflow.com/a/18933902/1480391
Yves M.
121

O design do URL bonito RESTful é sobre a exibição de um recurso com base em uma estrutura (estrutura semelhante a diretório, data: articles / 2005/5/13, objeto e seus atributos, etc.), a barra /indica a estrutura hierárquica, use o-id lugar.

Estrutura hierárquica

Eu pessoalmente preferiria:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

Se um usuário remove a /car-idpeça, ela traz a carsvisualização - intuitiva. O usuário sabe exatamente onde está na árvore e para o que está olhando. Ele sabe desde o primeiro momento que garagens e carros estão relacionados. /car-idtambém denota que ele pertence junto ao contrário /car/id.

Procurando

A consulta de pesquisa está OK, pois existe apenas a sua preferência, o que deve ser levado em consideração. A parte engraçada vem ao ingressar nas pesquisas (veja abaixo).

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

Ou basicamente qualquer coisa que não seja uma barra, como explicado acima.
A fórmula:, /cars[?;]color[=-:]blue[,;+&]* embora eu não usasse o &sinal, pois é irreconhecível no texto à primeira vista.

** Você sabia que a passagem do objeto JSON no URI é RESTful? **

Listas de opções

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

recursos possíveis?

Negar cadeias de pesquisa (!)
Para pesquisar carros, mas não em preto e vermelho :
?color=!black,!red
color:(!black,!red)

Pesquisas Cadastrado
Pesquisar vermelhos ou azuis ou pretas carros com 3 portas em id garagens 1..20 ou 101..103 ou 999 , mas não 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Você pode então construir consultas de pesquisa mais complexas. (Veja a correspondência de atributo CSS3 para a ideia de correspondência de substrings. Por exemplo, pesquisando usuários que contenham "bar"user*=bar .)

Conclusão

De qualquer forma, essa pode ser a parte mais importante para você, porque você pode fazer o que quiser, afinal, lembre-se de que o UEST RESTful representa uma estrutura que é facilmente compreendida /directory/file, como /collection/node/itemdatas de diretório , datas /articles/{year}/{month}/{day}e quando você omitir em qualquer um dos últimos segmentos, você sabe imediatamente o que recebe.

Então .., todos esses caracteres são permitidos sem codificação :

  • não reservado: a-zA-Z0-9_.-~
    normalmente permitido tanto codificado quanto não, ambos os usos são equivalentes.
  • caracteres especiais: $-_.+!*'(),
  • reservado: ;/?:@=&
    pode ser usado sem codificação para a finalidade que eles representam, caso contrário, eles devem ser codificados.
  • inseguro: <>"#%{}|\^~[]`
    por que inseguro e por que deveria ser codificado: RFC 1738, consulte 2.2

    Consulte também RFC 1738 # página-20 para obter mais classes de caracteres.

RFC 3986, ver 2.2.
Apesar do que eu disse anteriormente, aqui está uma distinção comum de delímetros, o que significa que alguns "são" mais importantes que outros.

  • delímetros genéricos: :/?#[]@
  • subdelímetros: !$&'()*+,;=

Mais leitura:
Hierarquia: consulte 2.3 , consulte 1.2.3
sintaxe do parâmetro do caminho da URL
Atributo CSS3 que corresponde a
IBM: Serviços da Web RESTful - Noções básicas
Nota: O RFC 1738 foi atualizado pelo RFC 3986

Qwerty
fonte
3
Não acredito que não tenha pensado em usar JSON na string de consulta. É a resposta para um problema que eu estava enfrentando - estrutura de pesquisa complexa sem usar POST. Além disso, outras idéias que você deu na sua resposta também são altamente apreciáveis. Muito obrigado!
Gustavohenke #
4
@ Qwerty: ótimo post! Fiquei me perguntando: a única razão para usar ;em oposição a &é legibilidade? Porque se sim, acho que prefiro o &delimitador mais comum ... certo? :) Obrigado!
Flo
3
@ Flo Sim exatamente :), mas tenha em mente que, &como delimitador, é conhecido apenas pelos desenvolvedores. Pais, avós e a população que não é educada em TI aceitam delimitadores, conforme usado no texto escrito comum.
Qwerty
17
Por que criar um esquema fora do padrão quando as strings de consulta são bem entendidas e padrão?
pbreitenbach
1
@Qwerty nada que impeça você de / search carros = vermelho, azul, verde & garagens = 1,2,3 Ou se você usar um formulário <multiselect>: / search carros = red & carros = blue & garagens = 1 & garagens = 2?
pbreitenbach
36

Embora ter os parâmetros no caminho tenha algumas vantagens, existem, na IMO, alguns fatores compensadores.

  • Nem todos os caracteres necessários para uma consulta de pesquisa são permitidos em um URL. A maioria dos caracteres de pontuação e Unicode precisaria ser codificada em URL como um parâmetro de sequência de caracteres de consulta. Estou lutando com o mesmo problema. Gostaria de usar o XPath na URL, mas nem toda a sintaxe do XPath é compatível com um caminho de URI. Portanto, para caminhos simples, /cars/doors/driver/lock/combinationseria apropriado localizar o combinationelemento ' ' no documento XML da porta do motorista. Mas /car/doors[id='driver' and lock/combination='1234']não é tão amigável.

  • Há uma diferença entre filtrar um recurso com base em um de seus atributos e especificar um recurso.

    Por exemplo, desde

    /cars/colors retorna uma lista de todas as cores para todos os carros (o recurso retornado é uma coleção de objetos de cores)

    /cars/colors/red,blue,green retornaria uma lista de objetos de cores vermelhos, azuis ou verdes, não uma coleção de carros.

    Para devolver carros, o caminho seria

    /cars?color=red,blue,green ou /cars/search?color=red,blue,green

  • Os parâmetros no caminho são mais difíceis de ler porque os pares nome / valor não são isolados do resto do caminho, que não são pares nome / valor.

Um último comentário. Prefiro /garages/yyy/cars(sempre plural) a /garage/yyy/cars(talvez tenha sido um erro de digitação na resposta original) porque evita alterar o caminho entre o singular e o plural. Para palavras com 's' adicionados, a alteração não é tão ruim, mas mudar /person/yyy/friendspara /people/yyyparece complicado.

Doug Domeny
fonte
2
sim, eu concordo ... além do mais, a estrutura do caminho dos URLs deve refletir as relações naturais entre entidades, algum tipo de mapa dos meus recursos, como uma garagem tem muitos carros, um carro pertence a uma garagem e assim ... e deixa os parâmetros do filtro, porque é disso que estamos falando, para a querystring ... o que você acha?
Opensas 30/05/09
31

Para expandir a resposta de Peter - você pode fazer da Search um recurso de primeira classe:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

O recurso de pesquisa teria campos para cores, modelo, status de garaged etc. e pode ser especificado em XML, JSON ou qualquer outro formato. Como o recurso Carro e garagem, você pode restringir o acesso às pesquisas com base na autenticação. Os usuários que executam frequentemente as mesmas pesquisas podem armazená-los em seus perfis para que não precisem ser recriados. Os URLs serão curtos o suficiente para que, em muitos casos, possam ser facilmente negociados por email. Essas pesquisas armazenadas podem ser a base de feeds RSS personalizados e assim por diante.

Existem muitas possibilidades de usar as pesquisas quando você as considera como recursos.

A ideia é explicada em mais detalhes neste Railscast .

Rich Apodaca
fonte
6
Essa abordagem não vai contra a idéia de trabalhar com um protocolo inquieto? Quero dizer, persistir uma pesquisa em um banco de dados é meio que ter uma conexão estável ... não é?
opensas 30/05/09
5
É mais como ter um serviço com estado. Também estamos alterando o estado do serviço toda vez que adicionamos um novo carro ou garagem. Uma Pesquisa é apenas outro recurso que pode ser usado com toda a gama de verbos HTTP.
Rich Apodaca
2
Como o exposto acima define uma convenção de URI?
Rich Apodaca
3
O REST não tem nada a ver com URIs bonitos ou aninhamento de URI, etc. Se você definir URIs como parte da sua API, não será REST.
aehlke 29/07/2009
2
Já argumentei isso antes. Isso não é de maneira alguma stateful, mas é uma coisa terrível. O 'excluir' da pesquisa não está perfeitamente claro, aqui você está dizendo que exclui esta entidade de pesquisa, mas eu gostaria de usá-lo para excluir os resultados encontrados por essa pesquisa. Por favor, não adicione 'pesquisas' como um recurso.
thecoshman
12

A resposta de Justin provavelmente é o caminho a seguir, embora em alguns aplicativos possa fazer sentido considerar uma pesquisa específica como um recurso por si só, como se você deseja oferecer suporte a pesquisas salvas nomeadas:

/search/{searchQuery}

ou

/search/{savedSearchName}
Peter Hilton
fonte
11
não. nunca faz sentido que uma ação seja um recurso.
Thecoshman
3
@thecoshman, como mencionado em um comentário acima, a pesquisa também é um substantivo.
andho
6

Eu uso duas abordagens para implementar pesquisas.

1) Caso mais simples, para consultar elementos associados e para navegação.

    /cars?q.garage.id.eq=1

Isso significa que consulta carros com ID de garagem igual a 1.

Também é possível criar pesquisas mais complexas:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Carros em todas as garagens do FirstStreet que não são vermelhos (3ª página, 100 elementos por página).

2) Consultas complexas são consideradas recursos regulares criados e podem ser recuperados.

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

O corpo do POST para criação de pesquisa é o seguinte:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

É baseado no Grails (critério DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

user2108278
fonte
5

Isso não é REST. Você não pode definir URIs para recursos dentro de sua API. A navegação de recursos deve ser orientada por hipertexto. Tudo bem se você deseja URIs bonitos e grandes quantidades de acoplamento, mas simplesmente não o chama REST, porque ele viola diretamente as restrições da arquitetura RESTful.

Veja este artigo pelo inventor do REST.

aehlke
fonte
28
Você está certo de que não é REST, é um design de URL para um sistema RESTful. Você também está, no entanto, incorreto ao dizer que viola a arquitetura RESTful. A restrição de hipertexto do REST é ortogonal ao bom design de URL para um sistema RESTful; Lembro-me de haver uma discussão com Roy T. Fielding na lista REST, há vários anos, em que participei de onde ele declarou explicitamente. Dito de outra maneira, é possível ter hipertexto e design de URL. O design de URL para sistemas RESTful é como um recuo na programação; não é obrigatório, mas uma idéia muito boa (ignorando Python, etc.)
MikeSchinkel
2
Me desculpe, você está correto. Acabei de ter a impressão do OP de que ele iria conscientizar os clientes sobre como criar URLs - ele faria "layouts" de URL parte de sua API. Isso seria uma violação do REST.
aehlke
@aehlke, atualize sua resposta para corresponder ao seu comentário.
James McMahon
1
É compatível com o modelo de maturidade Richardson de nível 2 . Você está se referindo ao nível 3. Apenas aceite REST como algo progressivamente adotável.
Jules Randolph
1
@Jules Randolph - desculpas, minha resposta foi escrita apenas alguns meses após o primeiro modelo de maturidade de Richardson e antes de Martin Fowler e outros autores o popularizarem :) De fato, é um modelo instrutivo a ser seguido. Sinta-se livre para editar a resposta.
aehlke
1

Embora eu goste da resposta de Justin, sinto que ela representa mais precisamente um filtro do que uma pesquisa. E se eu quiser saber sobre carros com nomes que começam com cam?

Na minha opinião, você pode incorporá-lo à maneira como lida com recursos específicos:
/ cars / cam *

Ou simplesmente adicioná-lo ao filtro:
/ cars / doors / 4 / name / cam * / colors / red, azul, verde

Pessoalmente, eu prefiro o último, porém não sou especialista em REST (tendo ouvido falar dele há apenas duas semanas ...)


fonte
Assim:/cars?name=cam*
DanMan
1

O RESTful não recomenda o uso de verbos nas URLs / carros / pesquisa não é repousante. A maneira correta de filtrar / pesquisar / paginar suas APIs é através de Parâmetros de consulta. No entanto, pode haver casos em que você precisa quebrar a norma. Por exemplo, se você estiver pesquisando em vários recursos, precisará usar algo como / search? Q = query

Você pode acessar http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ para entender as práticas recomendadas para projetar APIs RESTful

java_geek
fonte
1
A pesquisa também é um substantivo j
jith912 14/06
1

Além disso, eu também sugeriria:

/cars/search/all{?color,model,year}
/cars/search/by-parameters{?color,model,year}
/cars/search/by-vendor{?vendor}

Aqui, Searché considerado como um recurso filho do Carsrecurso.

aux
fonte
1

Existem muitas boas opções para o seu caso aqui. Ainda assim, você deve considerar o uso do corpo do POST.

A string de consulta é perfeita para o seu exemplo, mas se você tiver algo mais complicado, por exemplo, uma lista longa arbitrária de itens ou condicionais booleanos, convém definir a postagem como um documento que o cliente envia pelo POST.

Isso permite uma descrição mais flexível da pesquisa, além de evitar o limite de comprimento da URL do servidor.

estani
fonte
-4

Meu conselho seria este:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

Editar:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

Espero que isso lhe dê a idéia. Essencialmente, sua API Rest deve ser facilmente detectável e permitir que você navegue pelos seus dados. Outra vantagem do uso de URLs e não de cadeias de consulta é que você pode tirar proveito dos mecanismos de cache nativos existentes no servidor da Web para tráfego HTTP.

Aqui está um link para uma página que descreve os males das cadeias de consulta no REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Usei o cache do Google porque a página normal não estava funcionando para mim, eis também o link: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Justin Bozonier
fonte
1
Obrigado pela resposta detalhada. No último, e se eu quiser pesquisar por cor e número de portas? / carros / cores / vermelho, azul, verde / portas / 4 Isso não parece certo.
Parand
2
As vírgulas no URL não parecem certas para mim, mas ainda são válidas. Eu acho que é apenas uma mudança de paradigma.
Justin Bozonier 16/10/08
21
Eu não gosto dessa sugestão. Como você saberia a diferença entre /cars/colors/red,blue,greene /cars/colors/green,blue,red? O elemento path do URI deve ser hierárquico, e eu realmente não vejo isso aqui. Eu acho que essa é uma situação em que a string de consulta é a escolha mais apropriada.
31449 troelskn
62
Esta é uma resposta ruim. De fato, a maneira correta de implementar a pesquisa é com cadeias de consulta. As cadeias de consulta não são nem um pouco ruins quando usadas corretamente. O artigo citado não se refere à pesquisa. Os exemplos fornecidos são claramente torturados e não se sustentariam bem com mais parâmetros.
pbreitenbach
4
querystrings foram feitas principalmente para resolver o problema de consultar um recurso, mesmo com vários parâmetros. Perverter o URI para habilitar uma API "RESTful" parece perigoso e míope - especialmente porque você teria que escrever seus próprios mapeamentos complexos apenas para lidar com as várias permutações de parâmetros no URI. Melhor ainda, use a noção já existente de usar ponto e vírgula em suas URIs: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G