如何在API的查询字符串中设计通用过滤运算符?

Ian*_*lor 18 sql database api rest

我正在构建一个包含内容和可由用户定义的模式的通用API.我想为API响应添加过滤逻辑,以便用户可以查询它们存储在API中的特定对象.例如,如果用户正在存储事件对象,他们可以执行以下操作:

  • 数组包含:是否properties.categories包含Engineering
  • 更重要的是:是否properties.created_at比年龄大2016-10-02
  • 不相等:是否properties.address.city不是Washington
  • 平等:无论properties.nameMeetup
  • 等等

我正在尝试设计过滤到API响应的查询字符串,并提出一些选项,但我不确定它的哪种语法最好...


1.作为嵌套密钥的运算符

/events?properties.name=Harry&properties.address.city.neq=Washington
Run Code Online (Sandbox Code Playgroud)

此示例仅使用嵌套对象来特定运算符(neq如图所示).这很好,因为它非常简单,易于阅读.

但是,如果事件的属性可以由用户定义,则会遇到一个问题,即address.city.neq使用普通等于运算符命名的属性与address.city使用不等于运算符命名的属性之间可能存在冲突.

示例:Stripe的API


2.运营商作为关键后缀

/events?properties.name=Harry&properties.address.city+neq=Washington
Run Code Online (Sandbox Code Playgroud)

此示例与第一个示例类似,不同之处在于它使用+分隔符(相当于空格)进行操作,而不是.因为没有混淆,因为我的域中的键不能包含空格.

一个缺点是它稍微难以阅读,尽管这可以说是有争议的,因为它可能被解释为更清楚.另一个可能是解析起来稍微困难,但不是那么多.


3.运算符作为值前缀

/events?properties.name=Harry&properties.address.city=neq:Washington
Run Code Online (Sandbox Code Playgroud)

此示例与前一个示例非常相似,只是它将运算符语法移动到参数的值而不是键.这有利于消除解析查询字符串的一些复杂性.

但这是以不再能够区分检查文字字符串的相等运算符和检查字符串neq:Washington的不相等运算符为代价的Washington.

示例:Sparkpay的API


4.自定义过滤器参数

/events?filter=properties.name==Harry;properties.address.city!=Washington
Run Code Online (Sandbox Code Playgroud)

此示例使用单个顶级查询参数filter来命名所有过滤逻辑.这很好,因为您永远不必担心顶级命名空间冲突.(虽然在我的情况下,所有自定义都嵌套properties.在一起,所以这首先不是问题.)

但是,当您想要进行基本的相等过滤时,需要输入更难的查询字符串,这可能会导致必须在大多数时间检查文档.并且依赖于运算符的符号可能会导致诸如"近"或"内"或"包含"之类的非明显操作的混淆.

示例:Google Analytics的API


5.自定义详细过滤参数

/events?filter=properties.name eq Harry; properties.address.city neq Washington
Run Code Online (Sandbox Code Playgroud)

此示例使用与前一个类似的顶级filter参数,但它使用单词拼写运算符而不是使用符号定义它们,并且它们之间有空格.这可能稍微更具可读性.

但这需要花费更长的URL,以及需要编码的大量空间?

示例:OData的API


6.对象过滤器参数

/events?filter[1][key]=properties.name&filter[1][eq]=Harry&filter[2][key]=properties.address.city&filter[2][neq]=Washington
Run Code Online (Sandbox Code Playgroud)

此示例还使用顶级filter参数,但不是为模拟编程的模型创建完全自定义语法,而是使用更标准的查询字符串语法构建过滤器的对象定义.这有利于带来更多的"标准".

但它的代价是打字非常冗长,难以解析.

示例Magento的API


鉴于所有这些示例或不同的方法,哪种语法最好?理想情况下,构造查询参数很容易,因此在URL栏中玩游戏是可行的,但也不会给将来的互操作性带来问题.

我倾向于#2,因为它看起来很清晰,但也没有其他方案的一些缺点.

小智 6

我可能不会回答"哪一个最好"的问题,但我至少可以给你一些见解和其他例子来考虑.

首先,您谈论的是"带有内容的通用API和可以由用户定义的模式".

这听起来很像solr/elasticsearch,它们都是Apache Lucene的高级包装器,基本上是索引和聚合文档.

这两个采用完全不同的方法来处理其他API,我碰巧与它们一起工作.

Elasticsearch:

他们制作了完整的基于JSON的Query DSL,目前看起来像这样:

GET /_search
{
  "query": { 
    "bool": { 
      "must": [
        { "match": { "title":   "Search"        }}, 
        { "match": { "content": "Elasticsearch" }}  
      ],
      "filter": [ 
        { "term":  { "status": "published" }}, 
        { "range": { "publish_date": { "gte": "2015-01-01" }}} 
      ]
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

取自他们目前的文件.我很惊讶你实际上可以将数据放入GET中 ...它实际上看起来更好了,在早期版本中它更加层次化.

根据我的个人经验,这个DSL功能强大,但很难学习和使用流利(特别是旧版本).要实际获得一些结果,您需要的不仅仅是玩URL.从许多客户端甚至不支持GET请求中的数据这一事实开始.

SOLR:

他们将所有内容放入查询参数中,基本上看起来像这样(取自doc):

q=*:*&fq={!cache=false cost=5}inStock:true&fq={!frange l=1 u=4 cache=false cost=50}sqrt(popularity)
Run Code Online (Sandbox Code Playgroud)

使用它更直接.但这只是我的个人品味.


现在谈谈我的经历.我们在这两个之上实现了另一层,我们采用了#4号方法.实际上,我认为应该同时支持#4#5.为什么?因为无论你选择什么,人们都会抱怨,因为无论如何你将拥有自己的"微型DSL",你可能也会为你的关键字支持更多的别名.

为什么不#2?拥有单个过滤器参数和内部查询可让您完全控制DSL.在我们制作资源半年后,我们得到了"简单"的功能请求 - 逻辑OR和括号().查询参数基本上是一个AND操作列表,逻辑上OR就像city=London OR age>25不适合那里.另一方面,括号引入嵌套到DSL结构中,这也是扁平查询字符串结构中的问题.

那些是我们偶然发现的问题,你的情况可能会有所不同.但是仍然值得考虑一下,这个API未来的期望是什么.