检索特定元素子集的 REST 最佳实践

REST best practise for retrieving a specific subset of elements

好的,所以我们都知道获取所有实体列表的 REST 方法是 HTTP/GET /entities,获取单个实体的最佳方法是 HTTP/GET /entities/{entityId} 而获得某些实体的最佳方法是 HTTP/GET /entities/{entityId}?where=condition(*) 对吗?

(*) 我的意思是 /entities?where=condition

但是当我们需要获取一组特定的实体时,什么是好的方法,相当于一个 SQL select ... where id in(id1, id2...) 而多个 HTTP/GET entities/{entityId} 不是由于延迟的选择?

特别是我如何使用 RESTEasy 来做到这一点

根据 HTTP 请求类型,每个请求类型应按协议服务。

例如,HTTP/get 应始终检索数据,切勿使用此调用进行修改。

此外,根据 REST,我们应该按如下方式使用这些 HTTP 类型:

  1. GET - 检索实体。
  2. PUT - 保存/更新实体
  3. POST - 查询或保存实体
  4. DELETE - 删除实体

等等...

因此,我建议实现一个 HTTP/post 类型的 /query 端点,它应该是通用的以处理最大的查询场景。

我们可以在正文中发送嵌套的 json 数据来指定查询参数。

Json正文例如:

    {
    "whereClause":{
    "OR":{
    {
    "field":"name",
    "operator":"=",
    "value":"Raj"
    },
    {
    "field":"age",
    "operator":">=",
    "value":20
    }
    },
"orderByClause":{
"name":"ASC"
"age":"DESC"
},
"groupByClause":[
"name"
]
    }

这种方式可以实现高度的灵活性,并且可以通过多种不同的方式进行查询。

希望对您有所帮助!!

您可以使用 JSON 主体创建一个 HTTP/POST 请求,将实体 ID 作为数组 属性 以及任何其他自定义 matching/selection 条件的其他属性,在其中将在服务上反序列化。

请求JSON对象:

{
  "entityIds" : [12,22,45,2,44,5,66],
  "order" : "DESC"
}

EntityRequest.java

public class EntityRequest {

    List<Integer> entityIds;
    String order;

    public List<Integer> getEntityIds() {
        return entityIds;
    }

    public void setEntityIds(List<Integer> entityIds) {
        this.entityIds = entityIds;
    }

    public String getOrder() {
        return order;
    }

    public void setOrder(String order) {
        this.order = order;
    }
}

EntityResponse.java

public class EntityResponse
{
    List<Entity> entities;

    public List<Entity> getEntities() {
            return entities;
    }
    public void setEntities(List<Entity> entities) {
            this.entities= entities;
    }


}

EntityService.java

@Path("/entities")
public class EntityService {


    @POST
    @Path("/")
    @Consumes("application/json")
    @Produces("application/json")
    public EntityResponse createProductInJSON(EntityRequest entityRequest) {
        List<Entity> entities = new ArrayList<>();
        EntityResponse response = new EntityResponse();
        List<Integer> ids = entityRequest.getEntityIds();
        String order = entityRequest.getOrder();
        //TODO: Build/execute your sql query, populate the entities list and return
        response.setEntites(entities);
        return response;

    }

}

虽然您已经找到了解决方案,但我会 post 给出一个答案,因为我不是公认答案的忠实拥护者,原因我在下面解释。不可否认,这是一个非常自以为是的答案,因为 HTTP 规范允许多种方式来实现类似的事情,而 REST 不规定特定的 URI 样式,也为语义解释留下了很大的回旋余地。

超文本传输​​协议 (HTTP) 对可能的 URI 参数的语义没有很好的描述。 pathquery 参数可能是 well-known,headermatrix 参数经常被忽略,尽管 JAX-RS (正如您最初要求的 RESTeasy)几乎可以像其他人一样轻松地处理它们。

此外,REST 是一种架构风格,而不是一种协议。要调用服务(或 API)RESTful,它必须遵守一些 constraints 以及尊重底层 HTTP 协议。由于 REST 是面向资源的,因此使用唯一的资源标识符来调用相应的资源(因此是 URI)。然而,REST 并未对 URI 的设计方式施加任何限制。人们倾向于将一些语义意义放入一个好的 URI 设计中,尽管对于计算机来说它只是一个进一步的字符串。

您在评论中写道:

I don't really see it like that. Most of the API's I've faced work like I described (maybe I have worked with the wrong ones).

根据我的评论,/entites/{entityId}?where=condition return 是一个特定实体的子集,而不是实体的子集。通过在 URI 中指定 {entityId} 作为路径参数,您已经将结果集限制为单个实体。如果您打算 return 首先匹配某个实体 属性 的一组实体,您为什么还要提供 {entityId}

查询参数附加到 URI 的末尾,因此与矩阵参数 f.e 相比,它们平等地属于每个路径段。仅属于单个路径段,因此在具有多个路径段的较长 URI 上传达略微不同的语义。在不包含 sub-resources 的简单 URI 上,matrix- 和 query-parameters 之间的区别相当小,它们都可以互换使用。但是,对于具有多个路径段的 URI,语义可能会发生变化。

Also I don't understand what you mean by a subset of an entity?

如果您有一个 JSON 表示 ID 为 user1 的用户实体,如下所示:

{
    "firstName": "Tim",
    "lastName": "Sample",
    "address": {
        "street": "...",
        "zip": "...",
        "city": "...",
        "country": "..."
    }
}

调用 GET /user/user1?filter=lastName 我希望查询仅 return { "lastName": "Sample" },按 address f.e 过滤。我希望只有地址 sub-resource 得到 returned,尽管在那种情况下我会使用 /users/user1/addressGET /user/user1?lastName=Sample 之类的东西可能被解释为检查所识别的用户是否具有提供的名称,因此应该 return true 或 false 作为响应。如您所见,人类在语义上解释 URI 或其参数,而对于计算机而言,URI 仅包含子字符串,他们不关心参数是作为路径、查询、矩阵还是 header 参数提供的。他们只依赖于一些预定义的指令,这些指令告诉他们从哪里提取所需的信息。

我对已接受的解决方案的担忧是,在使用 POST 时,您实际上可以向服务器发送任何内容。因此,您需要明确记录需要发送到服务的预期表示以及服务器在收到请求后将执行的行为。此外,在对查询使用 POST 时,您将失去缓存响应的能力。后一个是few constraints REST has. Although certain caching frameworks do not cache responses on URIs which contain query parameters, this link as well as this answer中的一个,都表明这是一个都市传说而不是现实。

当然,您可以实施服务器端缓存以最大程度地减少数据库查找,但请求仍会到达服务器。在使用 GET 而不是 POST 时,由于客户端能够缓存答案(如果不通过特殊响应 header 设置阻止),请求甚至不会连续尝试到达服务器,因此 return 直接从缓存中获取答案,而不是一次又一次地查找状态。

But what would be a good approach when we need to get a specific set of entities, equivalent to a SQL select ... where id in(id1, id2...) when multiple HTTP/GET entities/{entityId} is not an option due to latency?

an other post 中所述,矩阵参数可以在路径段上指定,而不是像查询参数一样在整个 URI 上指定。这使得它们对于过滤 URI 的某些部分非常有用。如果要returnf.e。所有由头发花白的教授开设的课程,你都可以使用 GET /professors;haircolor=grey/courses 之类的东西。您当然可以反转结构并使用 /courses/professors?haircolor=grey 之类的东西,这在语法上完全没问题,但是如果您考虑哪些资源可以更容易地存在而没有其他资源并在更多依赖资源之前使用这些资源,您可能最终会得到以前的 URI。

因此,您的问题的可能解决方案可能类似于:GET /entity;id={id1};id={id2};...。正如 this answer 中所解释的那样,在单个资源上使用查询或矩阵参数可能没有太大区别,但如果你 f.e。想要 return 一组指定用户的所有地址,您可以使用这样的东西:GET /users;id={id1};id={id2}/addresses。这允许在您使用 HTTP GET 时进行响应缓存,您还在语义上使用 resource-subresource 语法,其中没有other 在引用资源之前使用。

由于 RESTeasy 能够与 JAX-RS 一起使用,因此可以使用 @MatrixParam 注释轻松地将矩阵参数注入到方法参数中。与 @QueryParam@PathParam 参数一样,基础 JAX-RS 框架将尝试 convert the parameter best-effort 尝试。

@GET
public Response getSomething(@MatrixParam List<String> ids) {
    ...
}

如果参数无法自动编组为 object,您还可以使用 @Context 注释注入 UriInfo object,然后检索矩阵参数通过各自的 PathSegment 参数被注释,然后将其编组为您自己的 object。

@GET
public Response getSomething(@Context UriInfo info) {
    for (PathSegment segment : info.getPathSegments()) {
        MultivaluedMap matrixParameters = segment.getMatrixParameters();
        ...
    }
}

作为 PathSegment return 一个 MultivaluedMap, the same key is able to return multiple values (as a List) like in your case multiple IDs you want to insert into the DB query. UriInfo 也提供了一个 MultivaluedMap 查找路径和 query-parameters.

因此,您喜欢哪种参数样式取决于您,REST 不规定特定的 URI 设计或语义。不过,我建议不要使用 POST,而是使用 GET 来减少向服务发送查询所需的文档开销,并获得缓存响应的能力 returned。