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

时间:2016-07-22 13:34:44

标签: java web-services rest standards resteasy

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

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

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

具体来说,我如何能够使用RESTEasy

3 个答案:

答案 0 :(得分:2)

根据HTTP请求类型,每个请求类型应按协议提供。

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

另外,根据REST,我们应该使用以下HTTP类型:

  1. 获取 - 检索实体。
  2. PUT - 保存/更新实体
  3. POST - 查询或保存实体
  4. 删除 - 删除实体
  5. 等等......

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

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

    Json身体例如:

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

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

    希望它有所帮助!!

答案 1 :(得分:1)

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

请求JSON对象:

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;
    }
}

EntityRequest.java

public class EntityResponse
{
    List<Entity> entities;

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


}

EntityResponse.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;

    }

}

EntityService.java

^\s*(\d+\s*,\s*)*\d+$

答案 2 :(得分:1)

虽然您已经找到了解决方案,但我会发布一个答案,因为我并不是接受答案的忠实粉丝,原因我在下面解释。不可否认,这是一个非常自以为是的答案,因为HTTP规范允许多种方式来实现类似的东西,而REST并没有规定某种URI样式,也为语义解释留下了足够的摆动空间。

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

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

在你的评论中你写道:

  

我真的不这么认为。大多数API都面临着我所描述的工作(也许我曾经使用过错误的工作)。

在我的评论中/entites/{entityId}?where=condition返回一个特定实体的子集而不是实体的子集。通过在URI中指定{entityId}作为路径参数,您已将结果集限制为单个实体。如果您打算首先返回一组与某个实体属性相匹配的entites,为什么甚至提供{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" } f.e过滤时返回address。我希望只返回地址子资源,但在这种情况下我会使用/users/user1/address。像GET /user/user1?lastName=Sample这样的东西可以解释为检查所识别的用户是否具有提供的名称,因此应该返回true或false作为响应。如您所见,人类在语义上解释URI或其参数,而对于计算机,URI只包含子字符串,并且他们不关心参数是作为path-,query-,matrix-或header参数提供的。他们只依赖于一些预定义的指令,告诉他们从哪里提取所需的信息。

我对已接受的解决方案的担忧是,在使用POST时,您可以直接向服务器发送任何内容。因此,您需要明确记录需要发送到服务的预期表示以及服务器在收到请求时将执行的行为。此外,在使用POST查询时,您将无法缓存响应。后者是few constraints REST之一。虽然某些缓存框架不会缓存对包含查询参数的URI的响应,但this link以及this answer都表明这更像是一个城市传奇,然后是现实。

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

  

但是当我们需要获取一组特定的实体时,这将是一个很好的方法,相当于一个SQL select ...其中id为(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) {
    ...
}

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

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

PathSegment返回MultivaluedMap时,相同的键能够返回多个值(作为List),就像在您的情况下要插入到数据库查询中的多个ID一样。 UriInfo还提供MultivaluedMap查找路径和查询参数。

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