Digamos que eu tenho três recursos que estão relacionados assim:
Grandparent (collection) -> Parent (collection) -> and Child (collection)
A descrição acima mostra a relação entre esses recursos da seguinte maneira: Cada avô pode mapear para um ou vários pais. Cada pai pode mapear para um ou vários filhos. Quero a capacidade de oferecer suporte à pesquisa no recurso filho, mas com os critérios de filtro:
Se meus clientes me fornecerem uma referência de identificação para um avô, quero pesquisar apenas crianças descendentes diretas desse avô.
Se meus clientes me passarem uma referência de identificação para os pais, desejo pesquisar apenas os filhos descendentes diretos dos meus pais.
Eu pensei em algo assim:
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
e
GET /myservice/api/v1/parents/{parentID}/children?search={text}
para os requisitos acima, respectivamente.
Mas eu também poderia fazer algo assim:
GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}
Nesse design, eu podia permitir que meu cliente me passasse um ou outro na sequência de consultas: grandparentID ou parentID, mas não os dois.
Minhas perguntas são:
1) Qual design de API é mais RESTful e por quê? Semântica, eles significam e se comportam da mesma maneira. O último recurso no URI é "filhos", implicando efetivamente que o cliente está operando no recurso filho.
2) Quais são os prós e os contras de cada um em termos de compreensão da perspectiva de um cliente e manutenção da perspectiva do designer.
3) Para que as strings de consulta são realmente usadas, além de "filtrar" seu recurso? Se você seguir a primeira abordagem, o parâmetro filter será incorporado no próprio URI como um parâmetro de caminho em vez de um parâmetro de sequência de caracteres de consulta.
Obrigado!
I want to only search against children who are INdirect descendants of that grandparent.
? De acordo com sua estrutura, o avô não tem filhos diretos.potential design flaw
e se você tiver informações sobre uma pessoa, mas nenhuma informação sobre os pais, elas se qualificam comochild
? (por exemplo, Adão e Eva) :)Respostas:
Primeiro
Conforme RFC 3986 §3.4 (identificadores uniformes de recursos § (componentes de sintaxe)) |
Os componentes de consulta são para recuperação de dados não hierárquicos; existem poucas coisas de natureza mais hierárquica do que uma árvore genealógica! Ergo - independentemente de você achar que é "REST-y" ou não - para estar em conformidade com os formatos, protocolos e estruturas de e para o desenvolvimento de sistemas na Internet, você não deve usar a string de consulta para identificar essas informações.
O REST não tem nada a ver com essa definição.
Antes de abordar suas perguntas específicas, seu parâmetro de consulta de "pesquisa" não recebe um nome adequado. Melhor seria tratar seu segmento de consulta como um dicionário de pares de valores-chave.
Sua string de consulta pode ser definida de maneira mais apropriada como
?first_name={firstName}&last_name={lastName}&birth_date={birthDate}
etc.Para responder suas perguntas específicas
Eu não acho que isso seja tão claro quanto você parece acreditar.
Nenhuma dessas interfaces de recursos é RESTful. A principal pré-condição para o estilo de arquitetura RESTful é que as transições do Estado do Aplicativo devem ser comunicadas do servidor como hipermídia. As pessoas trabalharam na estrutura dos URIs para torná-los de alguma forma "URIs RESTful", mas a literatura formal sobre o REST realmente tem muito pouco a dizer sobre isso. Minha opinião pessoal é que grande parte da meta-desinformação sobre o REST foi publicada com a intenção de quebrar velhos e maus hábitos. (Construir um sistema verdadeiramente "RESTful" é, na verdade, bastante trabalhoso. A indústria procurou o "REST" e preencheu algumas preocupações ortogonais com qualificações e restrições sem sentido.)
O que a literatura do REST diz é que, se você usará o HTTP como protocolo de aplicativo, deverá seguir os requisitos formais das especificações do protocolo e não poderá "inventar o http à medida que avança e ainda declarar que está usando o http" ; Se você usar URIs para identificar seus recursos, deverá seguir os requisitos formais das especificações relacionadas a URI / URLs.
Sua pergunta é direcionada diretamente pelo RFC3986 §3.4, que vinculei acima. A linha de fundo sobre este assunto é que mesmo que um URI conforme é insuficiente para considerar uma API "RESTful", se você quiser que seu sistema realmente ser "RESTful" e você estiver usando HTTP e URIs, então você não pode identificar dados hierárquicos através do string de consulta porque:
...É simples assim.
Os "profissionais" dos dois primeiros é que eles estão no caminho certo . Os "contras" do terceiro é que parece estar completamente errado.
No que diz respeito à sua compreensibilidade e capacidade de manutenção, essas são definitivamente subjetivas e dependem do nível de compreensão do desenvolvedor do cliente e dos aspectos de design do designer. A especificação de URI é a resposta definitiva sobre como os URIs devem ser formatados. Dados hierárquicos devem ser representados no caminho e com seus parâmetros. Dados não hierárquicos devem ser representados na consulta. O fragmento é mais complicado, porque sua semântica depende especificamente do tipo de mídia da representação que está sendo solicitada. Portanto, para abordar o componente de "compreensibilidade" da sua pergunta, tentarei traduzir exatamente o que seus dois primeiros URIs estão realmente dizendo. Em seguida, tentarei representar o que você diz que está tentando realizar com URIs válidos.
Tradução dos seus URIs verbatim ao seu significado semântico
/myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
Isto diz para os pais dos avós, que o filho deles tenhasearch={text}
O que você disse com o seu URI é coerente apenas se procurar pelos irmãos de um avô. Com seus "avós, pais, filhos", você encontrou um "avô" subiu uma geração com os pais e depois voltou à geração "avós" olhando para os filhos dos pais./myservice/api/v1/parents/{parentID}/children?search={text}
Isso indica que, para o pai identificado por {parentID}, encontre o filho dele.?search={text}
Isso está mais próximo de corrigir o que você deseja e representa um relacionamento pai - filho que provavelmente pode ser usado para modelar toda a sua API. Para modelá-lo dessa maneira, o ônus é imputado ao cliente ao reconhecer que, se ele tiver um "grandparentId", existe uma camada de indireção entre o ID que possui e a parte do gráfico de família que deseja ver. Para encontrar um "filho" por "grandparentId", você pode ligar para o seu/parents/{parentID}/children
serviço e, em seguida, procurar um filho retornado, procurar o identificador pessoal de seus filhos.Implementação de seus requisitos como URIs Se você deseja modelar um identificador de recurso mais extensível que possa percorrer a árvore, posso pensar em várias maneiras de conseguir isso.
1) O primeiro, eu já aludi. Represente o gráfico de "Pessoas" como uma estrutura composta. Cada pessoa tem uma referência à geração acima dela através do caminho dos Pais e a uma geração abaixo dela através do caminho dos Filhos.
/Persons/Joe/Parents/Mother/Parents
seria uma maneira de pegar os avós maternos de Joe./Persons/Joe/Parents/Parents
seria uma maneira de pegar todos os avós de Joe./Persons/Joe/Parents/Parents?id={Joe.GrandparentID}
pegaria o avô de Joe com o identificador que você tem em mãos.e tudo isso faria sentido (observe que pode haver uma penalidade de desempenho aqui, dependendo da tarefa, forçando um dfs no servidor devido à falta de identificação de ramificação no padrão "Pais / Pais / Pais".) Você também se beneficia de ter a capacidade de suportar qualquer número arbitrário de gerações. Se, por algum motivo, você desejar procurar 8 gerações, poderá representar isso como
/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}
mas isso leva à segunda opção dominante para representar esses dados: através de um parâmetro path.
2) Use parâmetros de caminho para "consultar a hierarquia". Você pode desenvolver a seguinte estrutura para ajudar a aliviar a carga sobre os consumidores e ainda ter uma API que faça sentido.
Olhando para trás 147 gerações, representar esse identificador de recurso com parâmetros de caminho permite que você faça
/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}
Para localizar Joe de seu bisavô, você pode procurar no gráfico um número conhecido de gerações pela identificação de Joe.
/Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}
O ponto principal a ser observado nessas abordagens é que, sem mais informações no identificador e na solicitação, você deve esperar que o primeiro URI recupere uma Pessoa 147 gerações acima de Joe com o identificador de Joe.NotableAncestor. Você deve esperar que o segundo recupere Joe. Suponha que o que você realmente deseja é que o cliente que está chamando possa recuperar todo o conjunto de nós e seus relacionamentos entre a Pessoa raiz e o contexto final do seu URI. Você pode fazer isso com o mesmo URI (com alguma decoração adicional) e definindo um Accept of
text/vnd.graphviz
em sua solicitação, que é o tipo de mídia registrado pela IANA para a.dot
representação gráfica. Com isso, altere o URI para/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor.Id}#directed
com um cabeçalho de solicitação HTTP
Accept: text/vnd.graphviz
e é possível que os clientes comuniquem claramente que desejam o gráfico direcionado da hierarquia geracional entre Joe e 147 gerações anteriores, quando essa 147ª geração ancestral contiver uma pessoa identificada como "Antepassado Notável" de Joe.Não tenho certeza se text / vnd.graphviz tem alguma semântica predefinida para seu fragmento; não encontrei nenhuma em busca de instruções. Se esse tipo de mídia realmente tiver informações de fragmentos predefinidas, sua semântica deve ser seguida para criar um URI em conformidade. Porém, se essas semânticas não forem predefinidas, a especificação do URI indicará que a semântica do identificador de fragmentos é irrestrita e, em vez disso, é definida pelo servidor, tornando esse uso válido.
Acredito que já superei isso completamente, mas as cadeias de consulta não servem para "filtrar" recursos. Eles são para identificar seu recurso a partir de dados não hierárquicos. Se você detalhou sua hierarquia com seu caminho
/person/{id}/children/
e deseja identificar um filho específico ou um conjunto específico de filhos, você usaria algum atributo que se aplica ao conjunto que está identificando e o incluiria na consulta.fonte
É aqui que você entende errado:
Em sistemas REST, o cliente nunca deve ser incomodado com IDs. Os únicos identificadores de recursos que o cliente deve conhecer devem ser URIs. Este é o princípio da "interface uniforme".
Pense em como os clientes interagem com seu sistema. Digamos que o usuário comece a navegar por uma lista de avós, ele escolheu um dos filhos dos avós, que o leva a ele
/grandparent/123
. Se o cliente conseguir pesquisar os filhos de/grandparent/123
, de acordo com "HATEOAS", o que for retornado ao fazer uma consulta/grandparent/123
deve retornar um URL para a interface de pesquisa. Esse URL já deve ter os dados necessários para filtrar pelo avô atual incorporado nele.Se o link se parece com
/grandparent/123?search={term}
ou/parent?grandparent=123&search={term}
ou/parent?grandparentTerm=someterm&someothergplocator=blah&search={term}
são irrelevantes acordo com REST. Observe como todos esses URLs têm o mesmo número de parâmetros, ou seja{term}
, mesmo que eles usem critérios diferentes. Você pode alternar entre qualquer um desses URLs ou misturá-los, dependendo dos avós específicos e o cliente não quebraria, porque o relacionamento lógico entre os recursos é o mesmo, embora a implementação subjacente possa diferir significativamente.Se, em vez disso, você tivesse criado o serviço de tal forma que ele requer
/grandparent/{grandparentID}?search={term}
quando você segue um caminho, mas/children?parent={parentID}&search={term}
a} quando segue outro caminho, isso é muito acoplamento, porque o cliente precisaria saber para interpolar coisas diferentes em diferentes relações que são conceitualmente semelhantes.Se você realmente acompanha
/grandparent/123?search={term}
ou/parent?grandparent=123&search={term}
é uma questão de gosto e qual a implementação mais fácil para você agora. O importante é não exigir que o cliente seja modificado se você alterar sua estratégia de URL ou se usar estratégias diferentes em diferentes relações entre pais e filhos.fonte
Não sei por que as pessoas pensam que colocar os valores de ID na URL significa que, de alguma forma, é uma API REST, REST trata de lidar com verbos, passando recursos.
Portanto, se você quiser COLOCAR um novo usuário, terá que enviar uma boa quantidade de dados e uma solicitação HTTP POST é ideal; portanto, embora você possa enviar a chave (por exemplo, ID do usuário), você enviará os dados do usuário (por exemplo, nome, endereço) como dados do POST.
Agora, é um idioma comum colocar o identificador de recurso no URI, mas isso é mais convencional do que qualquer forma de canônico "não é REST se não estiver no URI". Lembre-se de que a tese original do REST realmente não menciona http, é um idioma para manipular dados em um cliente-servidor, não algo que seja uma extensão do http (embora, obviamente, http seja nossa forma principal de implementar o REST) .
Por exemplo, Fielding usa a solicitação de um artigo acadêmico como exemplo. Você deseja recuperar o recurso "Artigo do Dr. John sobre o consumo de cerveja", mas também pode querer a versão inicial ou a versão mais recente, para que o identificador de recurso não seja algo que seja facilmente referenciado como um único ID que possa ser colocado no URI. O REST permite isso e uma intenção declarada por trás disso é:
Portanto, não há nada que o impeça de usar um URI estático para recuperar seus pais, passando um termo de pesquisa na string de consulta para identificar o usuário exato que você procura. Nesse caso, o 'recurso' que você está identificando é o conjunto de avós (portanto, o URI contém 'avós' como parte do URI. O REST inclui o conceito de 'dados de controle' projetado para determinar qual representação de seu O recurso deve ser recuperado - o exemplo dado a estes é o controle de cache, mas também o controle de versão - para que minha solicitação do excelente artigo do Dr. John possa ser refinada passando a versão como dados de controle, não como parte do URI.
Eu acho que um exemplo de interface REST que geralmente não é mencionado é SMTP . Ao construir uma mensagem de email, você envia verbos (FROM, TO etc) com os dados do recurso para cada parte da mensagem. Isso é RESTful, mesmo que não use os verbos http, ele usa seu próprio conjunto.
Então ... embora você precise ter alguma identificação de recurso em seu URI, ele não precisa ser sua referência de ID. Felizmente, isso pode ser enviado como dados de controle, na string de consulta ou mesmo nos dados do POST. O que você realmente está identificando na API REST é que está atrás de um filho, que você já possui em seu URI.
Então, na minha opinião, ao ler a definição de REST, você está solicitando um recurso filho, passando dados de controle (na forma de string de consulta) para determinar qual você deseja retornar. Como resultado, você não pode fazer uma solicitação de URI para um avô ou pai. Você deseja que os filhos sejam devolvidos, para que o termo pai / avô ou id definitivamente não deva estar em um URI. As crianças devem ser.
fonte
Muitas pessoas já falaram sobre o que significa REST, etc. etc. Mas nenhuma parece abordar o problema real: seu design.
Por que os avós são diferentes de um pai? ambos têm filhos que podem ter filhos que podem ...
Eventualmente, eles são todos 'humanos'. Você provavelmente também tem isso no seu código. Então use isso:
Retornará algumas informações úteis sobre o ser humano. (Nome e outras coisas)
obviamente retornará uma matriz de filhos. Se não houver filhos, provavelmente uma matriz vazia é o caminho a percorrer.
Para facilitar, você pode, por exemplo, adicionar uma bandeira 'hasChildren'. ou 'hasGrandChildren'.
fonte
O seguinte é mais RESTfull porque cada grandparentID obtém seu próprio URL. Dessa forma, o recurso é identificado de uma maneira única.
A pesquisa de parâmetros de consulta é uma boa maneira de executar uma pesquisa a partir do contexto desse recurso.
Quando uma família está ficando muito grande, você pode usar iniciar / limitar como opções de consulta, por exemplo:
Como desenvolvedor, é bom ter recursos diferentes com um URL / URI exclusivo. Eu acho que você deve usar o parâmetro de consulta apenas quando eles também podem ser deixados de fora.
Talvez seja uma boa leitura http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling e, caso contrário, a tese original de doutorado de Roy T Fielding https://www.ics.uci.edu /~fielding/pubs/dissertation/fielding_dissertation.pdf que explica os conceitos muito bem e completos.
fonte
Eu vou com
Nesse caso, todo prefixo é um recurso válido:
/myservice/api/v1/people
: todas as pessoas/myservice/api/v1/people/{personID}
: uma pessoa com informações completas (incluindo ancestrais, irmãos, descendentes)/myservice/api/v1/people/{personID}/descendants
: descendentes de uma pessoa/myservice/api/v1/people/{personID}/descendants/2
: netos de uma pessoaPode não ser a melhor resposta, mas pelo menos faz sentido para mim.
fonte
muitos antes de mim já apontaram que o formato da URL é irrelevante no contexto do serviço RESTful ... meus dois centavos seriam ... um aspecto importante do REST, conforme preconizado na redação original, era o conceito de "Recursos". .quando você cria seu URL, um aspecto que você deve ter em mente é que um 'Recurso' não é uma linha em uma única tabela (embora possa ser). .e pode ser usado para fazer alterações no estado dessa representação (e efetivamente no subjacente meio de armazenamento) .. e um determinado recurso pode ser significativo apenas no seu contexto de negócios .. por exemplo;
No seu exemplo / myservice / api / v1 / grandparents / {grandparentID} / pais / filhos? Search = {text}
Poderia ser um recurso mais significativo se você reduzir este / myservice / api / v1 / siblingsOfGrandParents? Search = ..
nesse caso, você pode declarar 'siblingsOfGrandParents' como recurso .. isso simplifica a URL
Como outros apontaram, existe uma noção equivocada generalizada de que você precisa se encaixar em todos os tipos de relacionamento hierárquico entre objetos de domínios de forma mais explícita em um URL. recursos e representam todos os seus relacionamentos ... a questão deveria ser, creio eu ... existe a necessidade de expor esse relacionamento via URLs ... especificamente segmentos de caminho ... talvez não.
fonte