Desenvolvendo uma aplicação REST com HATEOAS, utilizando Spring Boot com JWT para segurança [Parte 3]

João Rodrigo Da Silva
5 min readOct 7, 2018

--

No post anterior nós criamos a fundação do nosso serviço criando as entidades e repositórios necessários. Agora daremos continuidade criando a camada de apresentação. Código do projeto pode ser acessado no GitHub.

Começaremos criando a StudentResource:

public class StudentResource extends ResourceSupport {

private final Long id;
private final String name;

public StudentResource(Long id, String name) {
this.id = id;
this.name = name;
}


@JsonProperty("id")
public Long getResourceId() {
return id;
}

public String getName() {
return name;
}
}

Primeiro, herdamos da classe ResourceSupport fornecida pelos pacotes Spring HATEOAS, o que nos permite anexar links ao nosso recurso. Em segundo lugar, fizemos todos os campos finais. Embora isso não seja um requisito, é uma boa prática porque desejamos restringir os valores dos campos no recurso a serem alterados depois que eles foram definidos.

Neste caso simples, a classe StudentResource tem um relacionamento de campo um-pra-um com a classe Student, o principal motivo para criar uma classe de recurso separada é que a classe de recurso nos permite implementar um nível de indireção entre a própria classe Student e como essa classe é apresentada. Nesse caso, embora os campos sejam os mesmos, também anexamos links. Sem uma classe de recursos dedicada, teríamos que misturar a lógica de domínio com a lógica de apresentação, o que causaria sérios problemas de dependência em um sistema de grande escala.

Não podemos usar o método getId () como o getter para ID, pois a classe ResourceSupport possui um método padrão getId () que retorna um link. Portanto, usamos o método getResourceId () como getter para o campo id. Assim, temos que anotar o método getResourceId (), pois, por padrão, o recurso seria serializar o campo ID para resourceId devido ao nome do método getter. Para forçar essa propriedade a ser serializada para id, usamos a anotação @JsonProperty (“id”).

Agora é só implementar a mesma ideia com a entidade User.

Com a classe de recursos já implementada, é preciso implementar uma classe que criará um StudentResource a partir de um objeto de domínio Student. Para isso criaremos a classe StudentResourceMapper com dois métodos: toResource, que consome um único objeto Student e produz um objeto StudentResource e toResourceCollection, que consome uma coleção de objetos Student e produz uma coleção de objetos StudentResource.

@Component
public class StudentResourceMapper {

private final EntityLinks entityLinks;
private static final String UPDATE = "update";
private static final String DELETE = "delete";

@Autowired
public StudentResourceMapper(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}

public StudentResource toResource(Student student) {
StudentResource resource = new StudentResource(student.getId(), student.getName());
final Link selfLink = entityLinks.linkToSingleResource(student);
resource.add(selfLink.withSelfRel());
resource.add(selfLink.withRel(UPDATE));
resource.add(selfLink.withRel(DELETE));
return resource;
}

public Collection<StudentResource> toResourceCollection(Collection<Student> domainObjects) {
return domainObjects.stream()
.map(t -> toResource(t))
.collect(Collectors.toList());
}

}

Para facilitar a criação de links o Spring HATEOAS conta com a classe EntityLinks, que fornece métodos auxiliares que fornecem a construção de links usando apenas o tipo de objeto de domínio. Isso é feito através da utilização da anotação @ExposesResourceFor no RestController (conforme veremos mais tarde), que informa à estrutura HATEOAS que os links construídos para a classe de domínio fornecida devem apontar para esse controller REST.

Novamente, basta aplicar a mesma ideia com a entidade User.

Agora já temos tudo necessário para criar nosso controller REST, para isso teremos que criar a nossa classe StudentController e adicionar a anotação @RestController ou @Controller. Agora basta implementar os métodos desejados que no nosso caso serão GET, POST, PUT, DELETE.

Abaixo será listada a implementação da classe StudentController e depois uma breve explicação

@RestController
@ExposesResourceFor(Student.class)
@RequestMapping(value = "/student", produces = "application/json")
public class StudentController {

private final StudentRepository repository;
private final StudentResourceMapper mapper;

@Autowired
public StudentController(StudentRepository repository, StudentResourceMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Collection<StudentResource>> findAll() {
List<Student> students = repository.findAll();
Collection<StudentResource> resource = mapper.toResourceCollection(students);
return ResponseEntity.ok(resource);
}

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<StudentResource> findById(@PathVariable Long id) {
Optional<Student> found = repository.findById(id);

if (found.isPresent()) {
return ResponseEntity.ok(mapper.toResource(found.get()));
}
else {
return ResponseEntity.notFound().build();
}
}

@RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<StudentResource> create(@RequestBody Student student) {
Student createdStudent = repository.save(student);
StudentResource resource = mapper.toResource(createdStudent);
return new ResponseEntity<>(resource, HttpStatus.CREATED);
}

@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> delete(@PathVariable Long id) {
repository.deleteById(id);
return ResponseEntity.ok().build();
}

@RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = "application/json")
public ResponseEntity<StudentResource> update(@PathVariable Long id, @RequestBody Student toUpdate) {
Optional<Student> found = repository.findById(id);
if (found.isPresent()) {
Student updated = repository.save(toUpdate);
return ResponseEntity.ok(mapper.toResource(updated));
}
return ResponseEntity.notFound().build();
}
}

Para ser mais breve o repository foi injetado diretamente no controller, lembre-se que em uma aplicação real o ideal é adicionar uma camada service que faz esse meio de campo entre controller e outros componentes da nossa aplicação.

Conforme já comentado a classe começa com a anotação @RestController informando que é um controller REST, após isto inserimos a anotação @ExposesResourceFor(Student.class) que indica que, se um link for necessário para um objeto Student, esse controller deverá ser usado para fornecer o caminho para esse link.

As informações de caminho para o controller (/student em http://localhost: 8080/student) é fornecida usando o @RequestMapping, que mapeia a string fornecida como o caminho para o controller. Também inserimos o tipo de dados produzidos (application/json), para informar o Spring que este controller produz como saída um JSON.

Seguindo, você pode pegar como base nosso StudentController e construir um controller para User.

Agora o último passo é implementar os métodos que executarão as operações REST. Para declarar um novo endpoint REST, usamos o @RequestMapping para anotar um método e fornecer o verbo HTTP que desejamos usar.

Para casos onde nosso endpoint recebe informação, como no caso do update, temos que informar qual formato de dados estamos esperando. Para informar este formato utilizamos a opção consumes = “application/json” da anotação @RequestMapping, assim estamos informar que estamos esperando receber um JSON como input.

Nossa aplicação já está pronta para ser iniciada e receber requisições, segue relação de endereço dos nosso endpoints:

GET[Lista todos os estudantes] — http://localhost:8080/student

GET[Exibe informações do estudante de id 1] — http://localhost:8080/student/1

POST[Cria um novo estudante] — http://localhost:8080/student (passando no corpo da requisição JSON com dados do estudante que será cadastrado)

PUT[Altera um estudante já existente com id 1] — http://localhost:8080/student/1 (passando no corpo da requisição JSON com dados do estudante que será alterado)

DELETE[Exclui um estudando com id 1] — http://localhost:8080/student/1

Já podemos fazer operações de CRUD na nossa aplicação, o próximo passo será adicionar segurança no acesso aos nossos endpoints.

--

--