
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 ‘projections‘ on 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
[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
- 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:
[code language=”java”]
public class Employee implements Serializable {
private Long id;
private String firstName;
private String lastName;
private Set<Project> projects;
}
[/code]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.
