Spring Data REST and Projections

Spring Data REST and Projections

Project_Data

Introduction

In recent years, Spring has become much more than just a dependancy injection container and an MVC web application framework. Nowadays, it’s the go-to for building enterprise solutions due to the fact it has a fantastic community built up around it, and it has a multitude of projects that makes every developer’s life that little bit easier! In this blog post, I’m going to briefly introduce Spring Data REST, and how we used it and an unknown feature called ‘projectionson a recent project. Note: This blog post is intended as a tutorial for how to use Spring Data REST. You can find the official reference documentation here, which are a great source of information. However, you won’t find any information about projections there, and hence why I wanted share our experience of using them with you all 🙂

What’s it all about?

According to the home page of the website, the primary goal of Spring Data REST is:

“[…]to provide a solid foundation on which to expose CRUD operations to your JPA Repository-managed entities using plain HTTP REST semantics.”

Ultimately, that means eliminating DTOs and DAOs, replaced instead by RESTful endpoints, which are automatically generated by the framework. This in turn leads to greatly decreased boilerplate code in applications that are not massive big enterprise monstrosities! For example, I will use our good old friend the Employee entity 🙂

@Entity
@Table(name = "Employee" )
public class Employee implements Serializable {
 @Id
 @GenericGenerator(name= "employeegen" , strategy="increment" )
 @GeneratedValue(generator= "employeegen")
 @Column(name= "EmployeeId")
 private Long id;

 @NotNull
 @Pattern(regexp = "^[A-Za-z\\s-]+$", message = "First name must contain letters, spaces or dashes")
 private String firstName;

 @Pattern(regexp = "^[A-Za-z\\s-]*$", message = "Surname must contain letters, spaces or dashes")
 private String lastName;

 @ManyToMany(fetch=FetchType.EAGER)
 @JoinTable(name = "EmployeesProjects",
 joinColumns = @JoinColumn(name = "EmployeeId" , referencedColumnName = "EmployeeId"),
 inverseJoinColumns = @JoinColumn(name = "ProjectId" , referencedColumnName = "ProjectId")
 )
 private Set<Project> projects;

 /* Getters-Setters are going here */
}

As we can clearly see from this snippet, the founders of Spring Data REST are obviously big advocates of the KISS principle! To expose the entity as a RESTful service, all we need to do is add the following annotation to the Spring repository:

@RepositoryRestResource (itemResourceRel="employee", collectionResourceRel = "employee", path = "employee")
public interface EmployeeRepository extends CrudRepository<Employee, Long>, JpaSpecificationExecutor {
}

Spring Data REST wires up all the repositories marked with the annotation @RepositoryRestResource, and creates a set of CRUD services for us. Under the hood, it’s using the HATEOAS representation schema. Thus, we can easily discover our beautifully auto-generated RESTful resources, like so:

>curl -v -H "Accept: application/x-spring-data-compact+json" http://localhost:8080/api/employee
...
{
  "links" : [
    {
      "rel" : "employee",
      "href" : "http://localhost:8080/api/employee/1"
    }, {
      "rel" : "employee",
      "href" : "http://localhost:8080/api/employee/2"
    }, {
      "rel" : "employee",
      "href" : "http://localhost:8080/api/employee/3"
    }, {
      "rel" : "employee",
      "href" : "http://localhost:8080/api/employee/4"
    } 
  ],
  "content" : [ ]
}
> curl -v -H "Accept: application/x-spring-data-compact+json" http://localhost:8080/api/employee/1
{
  "firstName" : "Bob",
  "lastName" : "Well",
  "links" : [ ],
  "content" : [ ],
  "links" : [
    {
      "rel" : "self",
      "href" : "http://localhost:8080/api/employee/1"
    }, 
    {
      "rel" : "projects",
      "href" : "http://localhost:8080/api/employee/1/projects"
    }
  ]
}

As you can clearly see, HATEOAS represents inner entities as href links. This is good because all linked objects are lazily loaded, and thus network overhead is reduced because we are not downloading unnecessary data. However, there is a problem with this – how do we customise this representation?

Projections

stock-footage-old-projector-showing-film That’s a simple use case when you need to expose an object using simple CRUD operations. But you also need different fields for different views. Using our Employee entity again as an example, the edit form might look something like this: Edit employee form Using the standard HATEOAS representation is not exactly convenient because we need to send additional network requests/queries to retrieve an employee’s project(s). This is where ‘projections’ comes in very handy 🙂 A projection is a special type of contract that you declare to describe what fields you want to include in a response object. In terms of Java, a projection is simply an interface that declares what fields should be sent to the client. When Spring Data REST is serializing an object it looks for the projection that was requested and uses its interface to determine what fields should be included. To add a projection is straight forward – all we need to do is add the annotation @Projection to our new interface, and give it a parameter name (e.g. “edit”) and specify the type (e.g. “Employee.class”) like so:

@Projection(name = "edit" , types = Employee.class)
public interface EditEmployeeProjection {
    String getFirstName();
    String getLastName();
    Set<Project> getProjects();
}

Let’s not forget to add a projection for our Project interface:

@Projection(name = "edit" , types = Project.class)
public interface EditProjectProjection {
    Long getId();
    String getProjectName();
}

Spring Data REST will automatically (as usual!) recognise all our projections, and generate an additional parameter for our RESTful services:

>curl -v -H "Accept: application/x-spring-data-compact+json" http://localhost:8080/api/employee
...
{
  "links" : [
  {
    "rel" : "employee",
    "href" : "http://localhost:8080/api/employee/1{?projection}"
  },
  ... 
  ],
  "content" : [ ]
}

Finally, after adding the name of the projection to the request:

> curl -v -H "Accept: application/json" http://localhost:8080/api/employee/1?projection=edit
{
  "projects" : [ 
  {
    "id" : 2,
    "projectName : "Christmas Turkey"
  },
  {
    "id" : 5,
    "projectName : "Chinese New Year Duck"
  } 
  ],
  "firstName" : "Bob",
  "lastName" : "Well",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/employee/1{?projection}",
      "templated" : true
    }
  }
}

And that’s it! Interestingly enough, projections are not mentioned in any official reference documentation. In saying that however, there is an existing ticket about the lack of official documentation in JIRA.

Limitations

tumblr_mboug8ln841qaobbko1_500 There are some points which you probably need to consider before you decide to use Spring Data REST in your project.

  • It’s not a replacement for business layer services. It may be difficult to integrate custom business logic with Spring Data REST. The framework provides listeners, but they are only useful for CRUD operations. You must separate your RESTful and business services layers. However, if you have a lot of business logic then you need to stop and ask yourself “should I really be using Spring Data REST?“.
  • You can’t update individual parts of an object. If you provide a RESTful interface to your object model, another limitation is that you can only update the complete object, and not individual fields. For example, in our Employee class:
    public class Employee implements Serializable {
        private Long id;
        private String firstName;
        private String lastName;
        private Set<Project> projects;
    }
    

    In this case, anyone who can update user can only update first name, last name and projects. When an user can update his names (for example, as part of a profile) then this model is not the best choice. So, be more careful with your data model when designing your application with REST.

That’s a Wrap!

In my opinion, Spring Data REST is really good choice for small to medium size applications where you don’t have a lot of custom business logic, and it could be driven by your data. Although, it has limitations like those that I’ve listed above, it really impressed us with its ease of use and simplicity.

11 Comments
  • Fernando Maquedano
    Posted at 21:33h, 20 April Reply

    Well done Dmitry! Pretty interesting to read about Spring Data REST Projections.

  • Pingback:This week in API land, episode 6 | Restlet - We Know About APIs
    Posted at 23:40h, 24 April Reply

    […] Dmitry Pokidov writes about Spring Data Rest and projections. […]

  • Joseph Brr
    Posted at 03:05h, 12 June Reply

    how do you delete entity? i only can update and create new…

    • Dmitry Pokidov
      Posted at 09:16h, 18 August Reply

      Hi Joseph,

      You can send DELETE request to URL of entity.

  • Dominik Münch
    Posted at 02:15h, 18 August Reply

    Just stumbled on this and wanted to add a quick note on your limitations: Spring Data Rest works fine with regular Spring MVC controllers (good example here: https://github.com/olivergierke/spring-restbucks). Also you can update individual parts of an object by using a HTTP PATCH request and including only the properties you want to change.

    • Dmitry Pokidov
      Posted at 09:16h, 18 August Reply

      Hi Dominik,

      You are absolutely right, it’s working good with controllers, but I meant the case when you don’t want to use controllers at all. Spring-Rest-MVC can’t offer much to insert your business logic.

      You can update individual fields, but you can’t prevent to not update some fields if you have security constraints. I think it’s moving us back to controllers where you can pass only fields that user can update.

      • Eka
        Posted at 11:27h, 13 September

        HTTP PATCH for case if you dont want to use controller too.

      • Szymon Konicki
        Posted at 16:40h, 14 September

        Hi Dmitry,

        Take a look at OpenRest (https://github.com/konik32/openrest) it is an extension to Spring Data Rest. It adds DTO mechanism for creating and updating resources. With DTO (without any additional controllers) you can specify exactly which fields you allow clients to update. You can find usage example in a following article http://www.codeproject.com/Articles/1029761/OpenRest

      • Dmitry Pokidov
        Posted at 20:46h, 15 September

        Thanks Szymon, DTO looks really interesting. I will definitely consider openrest in future projects. I like how you mix DTO and authorizations, seems really useful!

  • Eka
    Posted at 11:29h, 13 September Reply

    Sorry I meant, HTTP Patch works without regular Spring MVC controller too for individual field update

  • HalWu
    Posted at 13:00h, 14 June Reply

    hi,everyone,I made a nice js lib can make it easy to work will spring data rest backend.
    see project on https://github.com/gwuhaolin/spring-data-rest-js

Leave a Reply

Discover more from Shine Solutions Group

Subscribe now to keep reading and get access to the full archive.

Continue reading