所以,我确定之前一定要问过,但我似乎找不到任何东西。问题在于,当我为网络应用程序编写搜索功能时,对我来说感觉不太对。
我使用Ruby on Rails,但我想这是一个适用于您使用RESTful MVC模式的任何情况的问题。
假设您要搜索资源(例如,用户,ToDos等)。一旦应用程序增长,再用简单的LIKE查询就不可行了,你开始使用索引(例如Solr,ElasticSearch,Lucene,......)。索引资源也往往是来自资源及其相关对象(用户位置,ToDos创建者......)的复合数据。
我们如何最好地代表这一点?
答案 0 :(得分:1)
通常不建议使用POST
进行搜索操作,因为您失去了GET
必须提供的所有优势 - 语义,幂等性,安全性(可缓存性),......
许多RESTful和类似REST的系统使用带有搜索参数的简单GET
查询作为query
或path
参数,以允许基于客户端和服务器的查询和结果缓存。从HTTP 1.1开始。除非指定了高速缓存标头correclty,否则缓存包含查询参数的GET请求不是问题。
但预定义查询会有一些LIKE
查询的气味,您试图避免这些查询。特别是ElasticSearch允许动态地向类型添加新字段。这可能会引入新的开销,以跟上添加新的预定义过滤器以支持对这些字段的查询。因此,从长远来看,根据需要动态添加查询可能是一个基本要求。但这并非难以实现。
包含动态添加的搜索过滤器的GET /users/12345
查询的示例输出可能如下所示:
{
"id": "12345",
"firstName": "Max",
"lastName": "Test",
"_schema": {
"href": "http://example.com/schema/user"
}
"_links": {
"self": {
"href": "/users/12345",
"methods": ["get", "put", "delete"]
},
"curies": [{
"name": "usr",
"href": "http://example.com/docs/rels/{rel}",
"templated": true
}],
"usr:employee": {
"href": "/companies/112233",
"title": "Sample Company",
"type": "application/hal+json"
}
},
"_embedded": {
"usr:address": [
{
"_schema": {
"href": "http://example.com/schema/address"
},
"street" : "Sample Street",
"zip": "...",
"city": "...",
"state": "...",
"location": {
"longitude": "...",
"latitude": "..."
}
"_links": {
"self": {
"href": "/users/12345/address/1",
"_methods": ["get", "post", "put", "delete"],
}
}
}
],
"usr:search": {
"_schema": {
"href": "http://example.com/schema/user_search"
}
"_links": {
"self": {
"href": "/users/12345/search",
"methods: ["post", "delete"]
}
},
"filters": [
"_schema": {
"href": "http://example.com/schema/user_search_filter"
},
"_links": {
"self": {
"href": "/users/12345/search/filters",
"methods: ["get"]
},
"next": {
"href": "/users/12345/search/filters?page=2"
"methods: ["get"]
}
},
{
"byName": {
"query": {
"constant_score": {
"filter": {
"term": {
"name": {
"href": "/users/12345#name"
}
}
}
}
}
"_links": {
"self": {
"href": "/users/12345/search/filter/byName",
"methods": ["get", "put", "delete"],
"_schema": {
"href": "http://example.com/schema/search_byName"
}
"type": "application/hal+json"
}
}
}
},
{
"in20kmDistance" : {
"query": {
"filtered" : {
"query" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "20km",
"Location" : {
"lat" : {
"href": "/users/12345/address/location#lat"
},
"lon" : {
"href": "/users/12345/address/location#lon"
}
}
}
}
}
}
}
"_links": {
"self": {
"href": "/users/12345/search/filter/in20kmDistance,
"methods": ["get", "put", "delete"],
"_schema": {
"href": "http://example.com/schema/search_in20kmDistance"
}
"type": "application/hal+json"
}
}
}
},
{
...
}
]
}
}
}
上面的示例代码包含一个用户表示,其中包含嵌入式地址和扩展JSON HAL格式的搜索过滤器。由于RESTful资源应该像posible一样不言自明,因此示例包含指向其位置和架构的链接,以便post
和put
操作也知道服务器可能需要哪些字段。
search
资源充当过滤器的控制器,因为它只允许添加新过滤器或一次删除所有过滤器,而通过在{上调用GET
来实现迭代过滤页面{1}}。
现在,实际的过滤器包含要执行的实际指令 - 在这种情况下,ElasticSearch查询用户名或当前地址20km距离内的所有内容 - 以及指向执行该实际URI的实际URI的链接查询。请注意,ElasticSearch代码实际上包含指向包含实际查询应使用的数据的资源的链接。当然,有可能返回一个包含实际用户数据的有效ElasticSearch查询,甚至可以返回JSON Pointer而不是URI到数据 - 这也是一些实现细节。
此方法允许在运行时动态添加新查询或更新现有查询,同时在查询时保持/users/{userId}/search/filters?page=pageNo
语义不变。此外,还可以利用缓存功能,这可以显着提高性能 - 尤其是在用户数据不经常更改的情况下。
然而,这种方法的缺点是,您必须返回有关用户查找的更多数据。您还可以考虑不返回嵌入式过滤器,并让客户端明确地轮询这些过滤器。此外,当前过滤器由作为密钥的特定名称添加。在实践中,这可能会导致命名冲突。因此,最终UUID更好,但如果人类必须调用这些URI,也会带走语义,因为GET
对人类的语义肯定比byName
更多,但这更像是一个实现细节。