REST 和复杂的搜索查询

2022-09-03 14:59:44

我正在寻找一种在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 查询、休眠条件 API、另一个 Web 服务调用...

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

我认为这是一个常见的问题。有没有一个库(或模式)我可以使用它:

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

亲切问候

格伦


答案 1

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

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>

因此,假设我们搜索白色汽车,我们会GET,我们可能会得到这样的东西:/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>

然后,让我们优化此结果。因此,只要说我们想要白色汽车而不是“灰白色”汽车,然后我们可以得到“/汽车?颜色=白色&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个左右的城市,那么枚举它们可能会非常有帮助。

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


答案 2

嗯......这是一个棘手的领域。没有为您的问题量身定制的框架。即使是@p0wl提到的 ODATA,在字段映射到域模型的方式方面也存在一定的局限性。在一点之后,它变得很奇怪。

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

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