22 Haziran 2018 Cuma

Adding HATEOAS Support To Your REST APIs with spring-hateoas

HATEOAS (Hypertext as the Engine of Application State)

It means that the hypertext should be used to find your way through the API. With HATEOAS support in your REST APIs, clients should know just where to start. After getting the first response from the server, they will be able to find their way.

Sample REST API without HATEOAS

Below is a sample REST API with Spring MVC

public class Person extends ResourceSupport {

 private String personId;
 private String name;
 private String surname;

 ... getters and setters
}

/**
 * A builder class for building person objects. This class is not necessary. It is just 
 * a better way to create new objects instead of using multiple setters in each line.
 */
public final class PersonBuilder {
 private String personId;
 private String name;
 private String surname;

 private PersonBuilder() {
 }

 public static PersonBuilder aPerson() {
  return new PersonBuilder();
 }

 public PersonBuilder withPersonId(String personId) {
  this.personId = personId;
  return this;
 }

 public PersonBuilder withName(String name) {
  this.name = name;
  return this;
 }

 public PersonBuilder withSurname(String surname) {
  this.surname = surname;
  return this;
 }

 public Person build() {
  Person person = new Person();
  person.setPersonId(personId);
  person.setName(name);
  person.setSurname(surname);
  return person;
 }
}


@RestController
@RequestMapping("/people")
public class PersonController {

 @RequestMapping(value = "/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<Person> getPersonById(@PathVariable String personId) {
  Person person = PersonBuilder.aPerson().withPersonId("1").withName("furkan").withSurname("danismaz").build();
  return ResponseEntity.ok(new Resource<>(person, selfLink));
 }
}

The response of such an endpoint will be:

{
 personId: "1",
 name: "furkan",
 surname: "danismaz"
}

Adding HATEOAS Support with spring-hateoas

  • Add the spring-hateoas dependency to your project

<dependency>
 <groupid>org.springframework.hateoas</groupid>
 <artifactid>spring-hateoas</artifactid>
 <version>...</version>
</dependency>


  • Extend ResourceSupport in your model classes
  • Change return type of your Controller methods to return Response (or Responses if you are returning a collection).
  • Create links and associate them with the returning data using the Resource constructor (or Resources if you are returning a collection)
public class Person extends ResourceSupport {

 private String personId;
 private String name;
 private String surname;

 ... getters and setters 


Note that I just added ResourceSupport as parent class. Nothing has changed other than that.


@RestController
@RequestMapping("/people")
public class PersonController {

 @RequestMapping(value = "/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<Resource<Person>> getPersonById(@PathVariable String personId) {
  Person person = PersonBuilder.aPerson().withPersonId("1").withName("furkan").withSurname("danismaz").build();
  Link selfLink = linkTo(methodOn(PersonController.class).getPersonById(personId)).withSelfRel();
  return ResponseEntity.ok(new Resource<>(person, selfLink));
 }
}


In this controller method I've changed:
  • The return type of the method from
    ResponseEntity<Person>
    
    to
    ResponseEntity<Resource<Person>>
    
  • Created a link
  • Created a resource object with a person and a link and returned it as a response
Now the response becomes:
{
 personId: "1",
 name: "furkan",
 surname: "danismaz",
 links: [
  {
   rel: "self",
   href: "http://localhost:8080/people/1",
  }
 ],
}

21 Haziran 2018 Perşembe

Monitoring entities with spring-data

If you are using spring-data, you can easily add fields to your entity models to monitor which user has created them and when, and also see who is the last modifier and when was the last modification time.

Notice that spring data is enough for tracing when your data is created or modified, but it is not enough to trace which user is creating or updating them. In that case, we also need spring security integration

To achieve this we will:
1. Create an abstract base entity model and put those fields in it so that all the entity model classes have those properties
2. Create a concrete entity model class and extend the abstract base entity model.
3. Implement the AuditorAware interface and set the auditorAware bean.

In this article, I will not show how to integrate with Spring security. I just assume you already have done that and you have your authentication object in Spring security context.


1. The Base EntityModel class


@MappedSuperclass@EntityListeners(AuditingEntityListener.class)
public class EntityModelBase implements Serializable {

   @CreatedDate   @Column(name = "CREATED_DATE", nullable = false, updatable = false)
   protected long createdDate;

   @LastModifiedDate   @Column(name = "LAST_MODIFIED_DATE", nullable =  false)
   protected long lastModifiedDate;

   @CreatedBy
   @Column(name = "CREATED_BY")
   protected String createdBy;

   @LastModifiedBy
   @Column(name = "LAST_MODIFIED_BY")
   protected String lastModifiedBy;

   ...


2. A Concrete EntityModel class

@Entity@Table(name="ROLES")
public class RoleEntityModel extends EntityModelBase {

   @Column(name = "NAME", nullable = false)
   private String name;

   ...


3. The AuditorAware Bean

First, we need to create our own AuditorAware bean. Note that in this bean I used my own authentication accessor object that brings me the authentication object from Spring security context. Also, I used a custom authentication class (an extension of the spring security's UsernamePasswordAuthenticationToken) which holds extra information about the authenticated user that I need.

import org.springframework.data.domain.AuditorAware;

public class SpringSecurityAuditorAware implements AuditorAware {

   @Autowired
   private MyAuthenticationAccessor authAccessor;

   @Override   
   public String getCurrentAuditor() {
      MyUser user = this.authAccessor.getUser();
      if (user != null) {
         return user.getUsername();
      }
      return null;
   }
}

@Component
public class MyAuthenticationAccessor {

   @Override   
   public MyUser getUser() {
      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if (auth != null && auth instanceof MyCustomAuthentication) {
         return ((MyCustomAuthentication)authentication).getUser();
      }
      return null;
   }

   ...

}

Finally, we need to set the AuditorAware bean. The best place to do that is in a @Configurationannotated class:

@Configuration@EnableJpaAuditing(auditorAwareRef = "auditorAware")
@EnableTransactionManagement@EnableJpaRepositories(basePackages = "...")
public class VeriErisimKatmaniKonfigurasyonu {

   @Bean
   public AuditorAware auditorAware() {
      return new SpringSecurityAuditorAware();
   }

   ...

}

Now, when we save an entity model to the database, we will have its created date, created by, last modified date, and last modified by properties.