【问题标题】:REST best practise for retrieving a specific subset of elements检索特定元素子集的 REST 最佳实践
【发布时间】:2016-11-26 10:34:03
【问题描述】:

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

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

但是当我们需要获取一组特定的实体时,当多个 HTTP/GET entities/{entityId} 由于延迟而不能选择时,什么是一个好方法?

具体来说,我将如何使用 RESTEasy 来做到这一点

【问题讨论】:

  • 不是另一种情况吗?你为什么不使用 /entities?id=id1,id2,id3
  • REST 与您的 URI 设计几乎没有任何关系!此外,在我的拙见中,/entities/{entityId}?where=condition 应该返回一个实体的某个子集,而不是一组实体。
  • @RomanVottner 我真的不这么看。我遇到的大多数 API 都像我描述的那样工作(也许我使用了错误的 API)。另外我不明白您所说的实体子集是什么意思?

标签: java web-services rest standards resteasy


【解决方案1】:

根据 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"
]
    }

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

希望对你有帮助!!

【讨论】:

  • 谢谢,也许这就是我缺少的那种方法。
【解决方案2】:

您可以使用 JSON 正文创建 HTTP/POST 请求,将实体 ID 作为数组属性和其他属性用于任何其他自定义匹配/选择条件,其中将在服务上反序列化。

请求 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“得到”这正是困扰我的地方。
【解决方案3】:

虽然您已经找到了解决方案,但我会发布一个答案,因为我不喜欢接受的答案,原因我在下面解释。诚然,这是一个相当固执己见的答案,因为 HTTP 规范允许多种方式来实现类似的事情,而 REST 并没有规定某种 URI 样式,也为语义解释留下了很大的回旋余地。

超文本传输​​协议 (HTTP) 对可能的 URI 参数的语义没有很好的描述性。 pathquery 参数可能是众所周知的,headermatrix 参数经常被忽略,尽管 JAX-RS(正如您最初要求的 RESTeasy)可以像处理其他参数一样容易。

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

您在评论中写道:

我真的不这么看。我遇到的大多数 API 都像我描述的那样工作(也许我使用了错误的 API)。

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

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

我也不明白你所说的实体子集是什么意思?

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

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

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

我对接受的解决方案的担忧是,在使用POST 时,您实际上可以将任何内容发送到服务器。因此,您需要明确记录需要发送到服务的预期表示以及服务器在接收到请求时将执行的行为。此外,在使用POST 进行查询时,您将失去缓存响应的能力。后一个是few constraints REST 中的一个。尽管某些缓存框架不会在包含查询参数的 URI 上缓存响应,但 this linkthis answer 都表明这更像是一个都市传说而不是现实。

当然,您可以实现服务器端缓存以最小化数据库查找,但请求仍会到达服务器。在使用 GET 而不是 POST 时,由于客户端能够缓存答案(如果没有通过特殊的响应标头设置阻止),因此请求甚至不会连续尝试到达服务器,因此直接从缓存中返回答案而不是查看状态一次又一次。

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

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

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

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

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

在参数无法自动编组到对象的情况下,您还可以使用@Context 注解注入UriInfo 对象,然后通过相应的PathSegment 检索矩阵参数然后自己将其编组到一个对象。

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

由于PathSegment 返回MultivaluedMap,相同的键能够返回多个值(作为列表),就像您想要插入数据库查询的多个 ID 一样。 UriInfo 还提供了一个 MultivaluedMap 用于查找路径和查询参数。

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

【讨论】:

  • 我完全同意你所说的。实际上,我相信我输入错误/误解了地址,我的论点实际上是针对“/entities?where...”。
猜你喜欢
  • 2011-01-19
  • 2011-11-19
  • 2020-03-22
  • 2013-10-23
  • 1970-01-01
  • 2010-09-27
  • 1970-01-01
  • 2018-03-31
相关资源
最近更新 更多