Como as buscas se encaixam em uma interface RESTful?

115

Ao projetar uma interface RESTful, a semântica dos tipos de solicitação é considerada vital para o design.

  • GET - lista coleção ou recuperar elemento
  • PUT - Substituir coleção ou elemento
  • POST - Criar coleção ou elemento
  • EXCLUIR - Bem, erm, excluir coleção ou elemento

No entanto, isso não parece cobrir o conceito de "pesquisa".

Por exemplo Ao projetar um conjunto de serviços da Web que oferecem suporte a um site de pesquisa de emprego, você pode ter os seguintes requisitos:

  • Obter anúncio de emprego individual
    • GET para domain/Job/{id}/
  • Criar anúncio de emprego
    • POST para domain/Job/
  • Atualizar anúncio de emprego
    • PUT para domain/Job/
  • Excluir anúncio de emprego
    • EXCLUIR para domain/Job/

"Obter todos os trabalhos" também é simples:

  • GET para domain/Jobs/

No entanto, como o trabalho "pesquisa" se enquadra nessa estrutura?

Você poderia reivindicar que é uma variação da "coleção de listas" e implementar como:

  • GET para domain/Jobs/

No entanto, as pesquisas podem ser complexas e é inteiramente possível produzir uma pesquisa que gere uma longa cadeia GET. Isto é, referenciando uma pergunta SO aqui , existem problemas usando strings GET com mais de 2000 caracteres.

Um exemplo pode estar em uma pesquisa facetada - continuando com o exemplo "job".

Eu posso permitir a pesquisa em facetas - "Tecnologia", "Cargo", "Disciplina", bem como palavras-chave de texto livre, idade do trabalho, localização e salário.

Com uma interface de usuário fluida e um grande número de tecnologias e cargos, é possível que uma pesquisa abranja um grande número de opções de facetas.

Ajuste este exemplo para CVs, ao invés de jobs, traga ainda mais facetas, e você pode facilmente imaginar uma pesquisa com cem facetas selecionadas, ou mesmo apenas 40 facetas, cada uma com 50 caracteres (eg Job Titles, Nomes Universitários, Nomes dos Empregadores).

Nessa situação, pode ser desejável mover um PUT ou POST para garantir que os dados da pesquisa sejam enviados corretamente. Por exemplo:

  • POST para domain/Jobs/

Mas semanticamente é uma instrução para criar uma coleção.

Você poderia também dizer que você expressará isso como a criação de uma pesquisa:

  • POST para domain/Jobs/Search/

ou (como sugerido por burninggramma abaixo)

  • POST para domain/JobSearch/

Semanticamente pode parecer fazer sentido, mas você não está realmente criando nada, você está fazendo uma solicitação de dados.

Então, semanticamente é um GET , mas GET não tem garantia de suporte ao que você precisa.

Então, a questão é: Tentando manter o máximo possível o design RESTful, assegurando que eu esteja mantendo as limitações do HTTP, qual é o design mais apropriado para uma pesquisa?

    
por Rob Baillie 21.03.2014 / 11:59
fonte

7 respostas

75

Você não deve esquecer que as solicitações GET têm algumas vantagens superiores em relação a outras soluções:

1) solicitações GET podem ser copiadas da barra de URL, elas são digeridas pelos mecanismos de busca, elas são "amigáveis". Onde "amigável" significa que normalmente uma solicitação GET não deve modificar qualquer coisa dentro de sua aplicação (idempotente) . Este é o caso padrão para uma pesquisa.

2) Todos esses conceitos são muito importantes não apenas do usuário e do mecanismo de pesquisa, mas de um ponto de vista de arquitetura, design de API .

3) Se você criar uma solução alternativa com POST / PUT você terá problemas que você não está pensando agora. Por exemplo, no caso de um navegador, o botão voltar / atualizar página / histórico. Isso pode ser resolvido, é claro, mas vai ser outra solução alternativa, depois outra e outra ...

Considerando tudo isso, meu conselho seria:

a) Você deve ser capaz de se encaixar dentro de seu GET com usando estrutura inteligente de parâmetros . Em casos extremos, você pode até optar por táticas como essa pesquisa no google onde eu defini muitos parâmetros ainda é uma URL super curta.

b) Crie outra entidade em seu aplicativo, como JobSearch . Supondo que você tenha tantas opções, é provável que você também precise armazenar essas buscas e gerenciá-las, de modo que esteja apenas limpando seu aplicativo. Você pode trabalhar com os objetos JobSearch como uma entidade inteira, ou seja, você pode testá-lo / usá-lo mais fácil .

Pessoalmente eu tentaria lutar com todas as minhas garras para fazê-lo com a) e quando toda a esperança estivesse perdida, eu me arrastaria para trás com lágrimas nos olhos para a opção b) .

    
por 21.03.2014 / 13:44
fonte
10

No REST, a definição de recursos é muito ampla. É realmente no entanto que você deseja agrupar alguns dados.

  • É útil pensar em um recurso de pesquisa como um recurso de coleta. Os parâmetros de consulta, às vezes chamados de parte pesquisável do URI, restringem o recurso aos itens em que o cliente está interessado.

Por exemplo, o URI principal do Google aponta para um recurso de coleção de "links para todos os sites da Internet". Os parâmetros de consulta restringem isso aos sites que você deseja ver.

(URI = identificador universal de recursos, do qual URL = localizador universal de recursos, onde o familiar "http: //" é o formato padrão para um URI. Então URL é um localizador, mas em REST é bom generalizar isso para um identificador de recursos, embora as pessoas as usem de forma intercambiável.)

  • Como o recurso que você está pesquisando no seu exemplo é a coleção de trabalhos, faz sentido pesquisar com

GET site/jobs?type=blah&location=here&etc=etc

(return) {jobs: [{job: ...}]}

E, em seguida, use POST, que é o acréscimo ou verbo de processo para adicionar novos itens a essa coleção:

POST site/jobs

{job: ...}

  • Observe que é a mesma estrutura para o objeto job em cada caso. Um cliente pode obter uma coleção de tarefas, usando parâmetros de consulta para restringir a pesquisa e, em seguida, usar o mesmo formato para um dos itens para POSTAR um novo trabalho. Ou pode pegar um desses itens e colocar em seu URI para atualizar esse item.

  • Para strings de consulta realmente longas ou complicadas, a convenção faz com que seja OK enviá-las como solicitações POST. Agrupe os parâmetros de consulta como pares nome / valor ou objetos aninhados em uma estrutura JSON ou XML e envie-os no corpo da solicitação. Por exemplo, se sua consulta tiver dados aninhados em vez de vários pares de nome / valor. A especificação de HTTP para POST descreve-o como o acréscimo ou verbo de processo. (Se você quiser navegar em um navio de guerra através de uma brecha no REST, use POST.)

Eu usaria isso como o plano de fallback, no entanto.

O que você perde quando você faz isso é: a) GET é nulo - isto é, não muda nada - o POST não é. Portanto, se a chamada falhar, o middleware não tentará nem armazenará novamente os resultados automaticamente, e 2) com os parâmetros de pesquisa no corpo, você não poderá mais cortar e colar o URI. Ou seja, o URI não é um identificador específico para a pesquisa desejada.

Diferenciar entre "criar" e "pesquisar". Existem algumas opções que são consistentes com a prática REST:

  • Você poderia fazer isso no URI adicionando algo ao nome da coleção, como procura de emprego em vez de tarefas. Isso significa que você está tratando a coleção de pesquisa como um recurso separado.

  • Como a semântica do POST é anexada ou processada, você pode identificar os corpos de pesquisa com a carga útil. Como {job: ...} vs. {search: ...}. Cabe à lógica do POST postar ou processá-lo adequadamente.

Isso é praticamente uma preferência de design / implementação. Eu não acho que haja uma convenção clara.

Então, como você já explicou, a idéia é definir um recurso de coleção para jobs

site/jobs

Pesquise com params de consulta GET + para restringir a pesquisa. Consultas de dados longas ou estruturadas entram no corpo de um POST (possivelmente em uma coleção de pesquisa separada). Crie com o POST para anexar à coleção. E atualize com PUT para um URI específico.

(FWIW a convenção de estilo com URIs é usar todas as letras minúsculas com palavras separadas por hifens. Mas isso não significa que você tenha que fazer isso dessa maneira.)

(Além disso, eu devo dizer que a partir da sua pergunta, está claro que você está muito longe disso. Eu soletrei as coisas explicitamente apenas para alinhá-las, mas sua pergunta já havia abordado a maior parte da semântica. questões nesta resposta.Eu estava apenas entrelaçando-o com alguma convenção e prática.)

    
por 21.03.2014 / 15:52
fonte
8

Eu geralmente uso consultas OData, elas operam como uma chamada GET, mas permitem restringir as propriedades que são retornadas e filtrá-las.

Você usa tokens como $select= e $filter= , portanto, você terá um URI semelhante a este:

/users?$select=Id,Name$filter=endswith(Name, 'Smith')

Você também pode fazer paging usando $skip e $top e ordenação.

Para mais informações, consulte OData.org . Você não especificou qual idioma está usando, mas se for ASP.NET, a plataforma WebApi suporta consultas OData - para outros (PHP, etc) provavelmente existem bibliotecas que você pode usar para traduzi-las em consultas de banco de dados.

    
por 21.03.2014 / 13:17
fonte
8

TL; DR: GET para filtragem, POST para pesquisa

Eu faço uma distinção entre filtrar os resultados de listar uma coleção versus uma pesquisa complexa. O teste decisivo que eu uso é basicamente se eu precisar de mais do que filtrar (positivo, negativo ou intervalo) Eu considero uma pesquisa mais complexa que requer POST.

Isso tende a ser reforçado quando se pensa no que será retornado. Eu normalmente uso somente GET se um recurso tiver um ciclo de vida quase completo (PUT, DELETE, GET, coleção GET) . Normalmente, em uma coleção GET, estou retornando uma lista de URIs que são os recursos REST que compõem essa coleção. Em uma consulta complexa, eu posso estar puxando de múltiplos recursos para construir a resposta (pense em juntar SQL) para que eu não esteja enviando de volta URIs, mas dados reais. O problema é que os dados não serão representados em um recurso, então sempre terei que retornar dados. Isto parece-me um caso claro de exigir um POST.

    
por 24.03.2014 / 08:10
fonte
5

Uma abordagem a considerar é tratar o conjunto de possíveis consultas como um recurso de coleta, por exemplo, /jobs/filters .

As solicitações

POST para este recurso, com os parâmetros de consulta no corpo, criarão um novo recurso ou identificarão um filtro equivalente existente e retornarão um URL contendo seu ID: /jobs/filters/12345 .

O id pode então ser usado em uma solicitação GET para jobs: /jobs?filter=12345 . As solicitações GET subseqüentes no recurso de filtro retornarão a definição do filtro.

Essa abordagem tem a vantagem de liberar você do formato de parâmetro de consulta para a definição de filtro, potencialmente fornecendo mais poder para definir filtros complexos. As condições OR são um exemplo que eu acho difícil de realizar com strings de consulta.

Uma desvantagem dessa abordagem é que você perde a legibilidade do URL (embora isso possa ser atenuado recuperando a definição através de uma solicitação GET para o recurso de filtro). Por esse motivo, você também pode querer oferecer suporte ao mesmo ou a um subconjunto dos parâmetros de consulta no recurso /jobs , da mesma forma que você ofereceria suporte a um recurso de filtro. Isso pode ser usado para consultas mais curtas. Se esse recurso for fornecido, para manter a capacidade de armazenamento entre os dois tipos de filtragem, ao usar parâmetros de consulta no recurso /jobs , a implementação deve criar / reutilizar internamente um recurso de filtro e retornar um 302 ou 303 status indicando o URL na forma de /jobs?filter=12345 .

    
por 27.03.2014 / 15:30
fonte
5

Esta é uma resposta antiga, mas ainda posso contribuir um pouco para a discussão. Eu observei muitas vezes um mal-entendido de REST, RESTful e Architecture. O RESTful nunca mencionou nada sobre NÃO construir uma pesquisa, não há nada no RESTful sobre arquitetura, é um conjunto de princípios ou critérios de design.

Para descrever melhor uma pesquisa, temos que falar sobre uma arquitetura em particular e a que melhor se adapta é a arquitetura orientada a recursos (ROA).

No RESTful existem princípios para projetar, idempotente não significa que o resultado não pode mudar conforme eu leio em algumas respostas, isso significa que o resultado de uma requisição independente não depende de quantas vezes é executado. Isso pode mudar, vamos imaginar que estou atualizando continuamente um banco de dados alimentando-o com alguns dados que são servidos por uma RESTful Api, executar o mesmo GET pode mudar o resultado mas não depende de quantas vezes ele foi executado. Se eu conseguir congelar o mundo, significa que não há estado, transformação, nada dentro do serviço quando solicito o recurso que leva a um resultado diferente.

By definition, a resource is anything that's important to be referenced as a thing by itself.

Em uma arquitetura orientada a recursos (vamos chamá-lo de ROA a partir de agora para fins de brevidade), nos concentramos no recurso que poderia ser um monte de coisas:

  • Uma versão de um documento
  • A última versão atualizada do documento
  • Um resultado proveniente de uma pesquisa
  • Uma lista de objetos
  • O primeiro artigo que comprei de um e-commerce

O que o torna único em termos de recursos é a capacidade de adição , o que significa que tem apenas um URI

Dessa forma, a pesquisa se encaixa perfeitamente no RESTful considerando o ROA . Nós temos que usar GET porque eu estou assumindo que sua busca é uma busca normal e isso não muda nada, então é idempotente (mesmo que retorne coisas diferentes dependendo dos novos elementos adicionados). Há uma confusão aqui dessa maneira, porque eu poderia manter o RESTful e não o ROA, isso significa que eu poderia seguir um padrão que cria uma pesquisa e retorna coisas diferentes com os mesmos parâmetros, porque não estou usando o princípio de endereçamento do ROA. Como é isso? Bem, se você enviar os filtros de pesquisa no corpo ou no cabeçalho, o recurso não será ADDRESSABLE.

Você pode encontrar os princípios do que é exatamente e o URI no documento original do W3:

link

Qualquer URL nesta arquitetura deve ser autodescritivo. É necessário se você seguir os princípios para resolver tudo no URI, isso significa que você pode usar / (barra) para separar o que for necessário ou consultar os parâmetros. Sabemos que existem limitações nisso, mas esse é o padrão de arquitetura.

Seguindo o padrão ROA no RESTful, uma pesquisa não é mais do que qualquer outro recurso, a única diferença é que os recursos vêm de um cálculo em vez de uma relação direta com o próprio objeto. Com base no princípio, eu poderia abordar e obter um serviço de cálculo aritmético simples baseado no seguinte padrão:

link

Onde sum, 1 e 2 podem ser modificados mas o resultado do cálculo é único e é adressable, toda vez que eu chamo com os mesmos parâmetros eu obtenho o mesmo e nada muda no serviço. O recurso / soma / 1/2 e / substract / 5/4 obedece perfeitamente aos princípios.

    
por 08.03.2017 / 06:18
fonte
3

GET é ok, se você tiver uma coleção estática que sempre retorna os mesmos resultados (representação) para um URI. Isso também implica que os dados que geram essas representações nunca são alterados. A fonte é um banco de dados somente leitura.

Ter GETs retornando resultados diferentes para um e o mesmo URI viola idempotência / segurança e o CoolURI princípio e, consequentemente, não é RESTful . É possível ter verbos idempotentes escritos em um banco de dados, mas eles nunca devem afetar a representação.

Uma pesquisa comum começa com uma solicitação POST que retorna uma referência ao resultado. Ele gera o resultado (é novo e pode ser obtido com um GET subsequente). Esse resultado pode ser hierárquico (outras referências com URIs que você pode GET), é claro, e pode reutilizar elementos de pesquisas anteriores, se isso fizer sentido para o aplicativo.

A propósito, eu sei que as pessoas fazem isso de forma diferente. Você não precisa me explicar como pode ser conveniente violar o REST.

    
por 11.09.2016 / 02:19
fonte