15

我正在寻找一种在 REST api 中为搜索查询建模的强大方法。

在我的 api 中,您可以使用查询参数在资源的 URI 中指定搜索条件。

例如:

/cars?search=color,blue;AND;doors,4 --> Returns a list of blue cars with 4 doors

/cars?search=color,blue;OR;doors,4 --> Returns a list of cars that are blue or have 4 doors

在服务器端,搜索字符串被映射到所需的底层技术。根据其余资源,这可以是 SQL 查询、Hibernate Criteria api、另一个 Web 服务调用,......

这 2 个示例足够简单,可以支持,但我还需要更复杂的搜索功能,例如子字符串搜索、日期之前/之后搜索、NOT、...

这是我认为的普遍问题。是否有我可以使用的库(或模式):

  • 将指定为字符串的搜索查询映射到通用 Criteria 模型。搜索格式不必与我上面列出的相同。
  • 允许我将该 Criteria 模型映射到我需要使用的任何技术。
  • 为 Hibernate/JPA/SQL 提供映射支持,但这是一个奖励;)

亲切的问候,

格伦

4

5 回答 5

6

每当我遇到这些问题时,我都会问自己“如果我正在创建一个传统的网页,我将如何向用户展示这个”?简单的答案是我不会在一个页面中呈现这些选项。界面过于复杂;但是我可以做的是提供一个界面,允许用户在多个页面上构建越来越复杂的查询,这就是我认为在这种情况下应该采用的解决方案。

HATEOAS 约束规定我们必须在响应中包含超媒体控件(链接和表单)。所以假设我们有一个/cars带有搜索选项的分页汽车集合,这样当你得到/cars它时会返回类似(顺便说一句,我在这里使用自定义媒体类型,但表单和链接应该非常明显。让我知道是否不是):

<cars href="/cars">
    <car href="/cars/alpha">...</car>
    <car href="/cars/beta">...</car>
    <car href="/cars/gamma">...</car>
    <car href="/cars/delta">...</car>
    ...
    <next href="/cars?page=2"/>
    <search-color href="/cars" method="GET">
        <color type="string" cardinality="required"/>
        <color-match type="enumeration" cardinality="optional" default="substring">
            <option name="exact"/>
            <option name="substring"/>
            <option name="regexp"/>
        </color-match>
        <color-logic type="enumeration" cardinality="optional" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </color-logic>
    </search>
    <search-doors href="/cars" method="GET">
        <doors type="integer" cardinality="required"/>
        <door-logic type="enumeration" cardinality="required" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </door-logic>
    </search>
</cars>

所以只要说我们搜索白色汽车,我们会得到/cars?color=white,我们可能会得到类似的东西:

<cars href="/cars?color=white">
    <car href="/cars/beta">...</car>
    <car href="/cars/delta">...</car>
    ...
    <next href="/cars?color=white&page=2"/>
    <search-color href="/cars?color=white" method="GET">
        <color2 type="string" cardinality="required"/>
        <color2-match type="enumeration" cardinality="optional" default="substring">
            <option name="exact"/>
            <option name="substring"/>
            <option name="regexp"/>
        </color2-match>
        <color2-logic type="enumeration" cardinality="optional" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </color2-logic>
    </search>
    <search-doors href="/cars?color=white" method="GET">
        <doors type="integer" cardinality="required"/>
        <door-logic type="enumeration" cardinality="required" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </door-logic>
    </search>
</cars>

这个结果然后让我们细化我们的查询。所以只要说我们想要白色汽车而不是“灰白色”汽车,我们就可以 GET '/cars?color=white&color2=off-white&color2-logic=not',它可能会返回

<cars href="/cars?color=white&color2=off-white&color2-logic=not">
    <car href="/cars/beta">...</car>
    <car href="/cars/delta">...</car>
    ...
    <next href="/cars?color=white&color2=off-white&color2-logic=not&page=2"/>
    <search-color href="/cars?color=white&color2=off-white&color2-logic=not" method="GET">
        <color3 type="string" cardinality="required"/>
        <color3-match type="enumeration" cardinality="optional" default="substring">
            <option name="exact"/>
            <option name="substring"/>
            <option name="regexp"/>
        </color3-match>
        <color3-logic type="enumeration" cardinality="optional" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </color3-logic>
    </search>
    <search-doors href="/cars?color=white&color2=off-white&color2-logic=not" method="GET">
        <doors type="integer" cardinality="required"/>
        <door-logic type="enumeration" cardinality="required" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </door-logic>
    </search>
</cars>

然后我们可以进一步细化我们的查询,但重点是,在此过程中的每一步,超媒体控件都会告诉我们什么是可能的。

现在,如果我们考虑汽车的搜索选项,颜色、门、品牌和型号并不是无限的,因此我们可以通过提供枚举使选项更加明确。例如

<cars href="/cars">
    ...
    <search-doors href="/cars" method="GET">
        <doors type="enumeration" cardinality="required">
            <option name="2"/>
            <option name="3"/>
            <option name="4"/>
            <option name="5"/>
        </doors>
        <door-logic type="enumeration" cardinality="required" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </door-logic>
    </search>
</cars>

然而,我们唯一拥有的白色汽车可能是 2 门和 4 门,在这种情况下,GETing/cars?color=white可能会给我们

<cars href="/cars?color=white">
    ...
    <search-doors href="/cars?color=white" method="GET">
        <doors type="enumeration" cardinality="required">
            <option name="2"/>
            <option name="4"/>
        </doors>
        <door-logic type="enumeration" cardinality="required" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </door-logic>
    </search>
</cars>

同样,当我们细化颜色时,我们可能会发现它们只是几个选项,在这种情况下,我们可以从提供字符串搜索切换到提供枚举搜索。例如,GETing/cars?color=white可能会给我们

<cars href="/cars?color=white">
    ...
    <search-color href="/cars?color=white" method="GET">
        <color2 type="enumeration" cardinality="required">
            <option name="white"/>
            <option name="off-white"/>
            <option name="blue with white racing stripes"/>
        </color2>
        <color2-logic type="enumeration" cardinality="optional" default="and">
            <option name="and"/>
            <option name="or"/>
            <option name="not"/>
        </color2-logic>
    </search>
    ...
</cars>

您可以对其他搜索类别执行相同操作。例如,最初您不想枚举所有品牌,因此您将提供某种文本搜索。一旦对集合进行了细化,并且只有几个模型可供选择,那么提供枚举是有意义的。相同的逻辑适用于其他集合。例如,您不想枚举世界上所有的城市,但是一旦您将区域细化到 10 个左右的城市,那么枚举它们会很有帮助。

有图书馆可以为你做这件事吗?据我所知没有。我见过的大多数甚至不支持超媒体控件(即,您必须自己添加链接和表单)。有没有可以使用的模式?是的,我相信以上是解决此类问题的有效模式。

于 2012-10-29T11:26:18.527 回答
4

嗯...这是一个棘手的领域。没有针对您的问题量身定制的框架。即使@p0wl 提到的 ODATA 对字段映射到域模型的方式也有一定的限制。一点之后就变得奇怪了。

解决此问题的最简单方法是将查询作为字符串接受,然后使用 AST 或您想要的任何内容针对特定领域的语言进行验证。这使您可以灵活地接受查询,同时仍然验证它的正确性。您失去的是通过 URL 以更有意义的方式描述查询的能力。

看看Restful recipes 食谱的第 8 章。它突出了我提出的解决方案之一,还推荐了其他方法。根据客户所需的灵活性与查询的表达能力选择一种。你可以在某个地方取得平衡。

于 2012-10-28T15:53:59.763 回答
2

@格伦V

我不确定是否有与此直接相关的模式/规则,如果这是一个常见问题(我的意思是查询字符串中的逻辑运算符),但在设计这样的东西时我会问自己以下问题:

  • 客户将如何构建此类查询?

问题是 URI(作为一个整体,包括查询字符串部分)必须完全由服务提供者控制,并且客户端不能直接耦合到特定于您的问题域的 URI 构造逻辑。

客户端使用 URI 和构造它们的方式有多种:

  • 客户端可以通过“链接:<...>”标头或实体主体(如原子或 HTML 链接或类似的东西)跟踪包含在 HTTP 标头中的链接
  • 客户端可以通过媒体类型或其他规范定义的规则构造 URI。

引导式 URI 构建过程的几种众所周知的方法:

根据以上信息,您可以选择现有方法或设计您自己的方法,但请记住简单的规则,即服务必须协调客户端如何构造查询(即定义您自己的规范、自定义媒体类型或扩展现有的)。

另一件事是如何将 URI 映射到底层实现,就 REST 而言有两件非常重要的事情:

  • 连接器- 包括 HTTP + URI 规范,实际上只有连接器对客户端很重要。
  • 组件- 这是底层实现,除了您之外没有人关心。这部分需要对客户端完全隐藏,您可以在此处选择任何绑定方法,没有人可以告诉您确切的方式,这完全取决于特定要求。
于 2012-10-28T20:05:13.733 回答
1

您是否搜索像 ODATA ( Link ) 这样的库?

这将解决您的查询解析问题,您可以将查询转发到您选择的数据库。

于 2012-10-28T15:42:10.110 回答
1

如前所述,仍然没有通用的解决方案来做到这一点。可能,最好的选择(至少是我发现的最好的)是 Spring DATA REST(http://static.springsource.org/spring-data/rest/docs/1.1.0.M1/reference/htmlsingle/)。使用 CrudRepository,您可以获得 findOne、findAll 等方法。如果您扩展它,您可以添加自己的通用映射。例如,我们所做的是将其扩展为具有通用查询选项,例如:customer?country=notArgentina,以获取不属于阿根廷的客户。或者,例如:customer?country=like( Island ) 以获取所有具有“Island”之类的国家的客户(以 SQL 方式)。希望能帮助到你。

于 2013-03-25T00:15:07.803 回答