Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With Spring Data REST, why is the @Version property becoming an ETag and not included in the representation?

In Spring Data REST (via Spring Boot 1.3.3), when I GET a resource collection of, say, people, the @Version property is not included with the resources:

$curl -v http://localhost:8080/api/people/1
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/people/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.42.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< ETag: "0"
< Last-Modified: Tue, 26 Apr 2016 00:08:12 GMT
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 26 Apr 2016 00:12:56 GMT
< 
{
  "id" : 1,
  "createdDate" : {
    "nano" : 351000000,
    "epochSecond" : 1461629292
  },
  "lastModifiedDate" : {
    "nano" : 351000000,
    "epochSecond" : 1461629292
  },
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/people/1"
    },
    "person" : {
      "href" : "http://localhost:8080/api/people/1"
    }
  }
* Connection #0 to host localhost left intact

by default, or when I configure my Spring Data repository:

@Configuration
public class ApplicationRepositoryConfiguration 
    extends RepositoryRestMvcConfiguration 
{    
    @Override
    protected void configureRepositoryRestConfiguration(
        RepositoryRestConfiguration config
        ) 
    {
        config.exposeIdsFor(Person.class);
        config.setBasePath("/api/");
    }
}

The @Version is the version of the row of data which is incremented on updates, and included in the ETag HTTP Header data when I query a specific resource. Instead of having to invoke a GET on each resource in the collection, I'd prefer getting the @Version in the collection GET so I can write my application to check the @Version value on each resource update without performing the n addition GET round-trips.

Is there a way to include the @Version field in each of the resources a collection GET?

The entity definition looks like this:

@Data @Entity @EntityListeners(AuditingEntityListener.class)
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @CreatedDate
    @Column(nullable=false)
    private Instant createdDate;

    @LastModifiedDate
    @Column(nullable=false)
    private Instant lastModifiedDate;

    @Version
    @JsonProperty
    private Long version;

    …
}
like image 435
Jan Nielsen Avatar asked Apr 26 '16 00:04

Jan Nielsen


People also ask

What is spring boot ETag?

From the official Spring documentation on ETag support: An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. We can use ETags for two things – caching and conditional requests.

How does spring data rest work?

Spring Data REST is a framework that builds itself on top of the applications data repositories and expose those repositories in the form of REST endpoints. In order to make it easier for the clients to discover the HTTP access points exposed by the repositories, Spring Data REST uses hypermedia driven endpoints.

What is used for exposing spring data repositories over rest using spring data rest?

Spring Data REST can be used to expose HATEOAS RESTful resources around Spring Data repositories. Without writing a lot of code, we can expose RESTful API around Spring Data Repositories.

What is spring data rest Webmvc?

The spring-data-rest-webmvc is the project describing the main concepts of spring-data-rest which is the one of the main spring modules.


2 Answers

No, there is not. The ETag is the HTTP equivalent to what's expressed as @Value property in the backend. Spring Data REST turns all backend related properties that have a corresponding mechanism in the HTTP protocol into exactly those: ids become URIs (and shouldn't be part of the payload either), @LastModifiedDate properties become headers, @Version properties, become ETags.

The reason is pretty simple: if you use HTTP, use the protocol means that are available to you to achieve things that are implemented on the data access level. That's one aspect in which Spring Data REST is not simply exposing a database to the web but actually inspects your model and translates model characteristics into protocol specific means.

Long story short: with Spring Data REST you have two options for updates:

  1. Just PUT without an If-Match header — enforces overriding whatever is present on the server as the aggregate gets loaded, incoming data mapped onto it and it written back. You still get optimistic locking applied if another client changed the aggregate in the meantime (although an admittedly very short window). If that's the case you'll see a 409 Conflict.
  2. PUT with an If-Match header - Spring Data REST checks the ETag submitted against the current value of the version property of the aggregate and return a 412 Precondition Failed in case there's a mismatch at that point. In that case clients can lookup the current state of the resource and decide how to proceed. They might just decide to override what's on the server using PUT without an If-Match header.

Similar optimizations can made for GET requests:

  1. GET with If-None-Match (ETag) / If-Modified-Since (with Last-Modified header value) — You'll see a 304 Not Modified in case the resource is still in the same state as before and you thus avoid spending bandwidth for the response.
  2. Plain GET will always return the representation.
like image 53
Oliver Drotbohm Avatar answered Oct 22 '22 13:10

Oliver Drotbohm


I believe not including the version in the body is a mistake, as we often pull a page of resources, so there we can't rely on the ETag. At least it should be configurable which it is not currently.

I simply add another function to the entity that exposes the version, I am on Spring Boot 2.4.4:

  // ...

  @Version
  private Long version;

  public Long getCurrentVersion() {
    return version;
  }

  // ...

like image 2
Johan Hendrik Ehlers Avatar answered Oct 22 '22 12:10

Johan Hendrik Ehlers