Site icon Shine Technologies

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 🙂

[code language=”java”]
@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 */
}
[/code]

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:

[code language=”java”]
@RepositoryRestResource (itemResourceRel="employee", collectionResourceRel = "employee", path = "employee")
public interface EmployeeRepository extends CrudRepository<Employee, Long>, JpaSpecificationExecutor {
}
[/code]

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

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: 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:

[code language=”java”]
@Projection(name = "edit" , types = Employee.class)
public interface EditEmployeeProjection {
String getFirstName();
String getLastName();
Set<Project> getProjects();
}
[/code]

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

[code language=”java”]
@Projection(name = "edit" , types = Project.class)
public interface EditProjectProjection {
Long getId();
String getProjectName();
}
[/code]

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

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

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.

Exit mobile version