Skip to content

Commit

Permalink
Lesson 5: Giving Your Microservice a REST
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlong authored and philwebb committed Sep 24, 2016
1 parent 2db53a6 commit bfb4535
Show file tree
Hide file tree
Showing 23 changed files with 561 additions and 0 deletions.
8 changes: 8 additions & 0 deletions livelessons-rest/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:compat-mode:
= Lesson 5: Giving Your Microservice a REST

_Why the web is a existence proof of the HTTP architecture and how to build
services that exploit that._

- link:livelessons-rest-basic[Basic]
- link:livelessons-rest-sync[Sync]
32 changes: 32 additions & 0 deletions livelessons-rest/livelessons-rest-basic/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>livelessons</groupId>
<artifactId>livelessons-rest</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>livelessons-rest-basic</artifactId>
<properties>
<main.basedir>../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package demo;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

@Bean
CommandLineRunner commandLineRunner(PersonRepository personRepository) {
return args -> {
Arrays.asList("Phil", "Josh").forEach(name -> personRepository
.save(new Person(name, (name + "@email.com").toLowerCase())));
personRepository.findAll().forEach(System.out::println);
};
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {

@Id
@GeneratedValue
private Long id;

private String name;

private String email;

Person() {
}

public Person(String name, String email) {
this.name = name;
this.email = email;
}

public Long getId() {
return this.id;
}

public String getName() {
return this.name;
}

public String getEmail() {
return this.email;
}

@Override
public String toString() {
return "Person{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email
+ '\'' + '}';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.URI;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@RestController
@RequestMapping("/people/{id}/photo")
public class PersonPhotoRestController {

private File root;

@Autowired
private PersonRepository personRepository;

@Value("${user.home}")
public void setUserHome(String home) {
this.root = new File(home, "Desktop/images");
Assert.isTrue(this.root.exists() || this.root.mkdirs(),
"The path '" + this.root.getAbsolutePath() + "' must exist.");
}

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Resource> read(@PathVariable Long id) throws Exception {
Person person = this.personRepository.findOne(id);
File file = fileFor(person);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.IMAGE_JPEG);
Resource resource = new FileSystemResource(file);
return new ResponseEntity<>(resource, httpHeaders, HttpStatus.OK);
}

@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
public ResponseEntity<?> write(@PathVariable Long id,
@RequestParam MultipartFile file) throws Exception {
Person person = this.personRepository.findOne(id);
FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fileFor(person)));
URI location = ServletUriComponentsBuilder.fromCurrentRequest().buildAndExpand(id)
.toUri();
return ResponseEntity.created(location).build();
}

private File fileFor(Person person) {
return new File(this.root, Long.toString(person.getId()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package demo;

import java.util.Collection;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(path = "people")
interface PersonRepository extends JpaRepository<Person, Long> {

Collection<Person> findByEmail(@Param("email") String e);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package demo;

import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;

@Component
public class PersonResourceProcessor implements ResourceProcessor<Resource<Person>> {

@Override
public Resource<Person> process(Resource<Person> resource) {
String id = Long.toString(resource.getContent().getId());
UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/people/{id}/photo").buildAndExpand(id);
String uri = uriComponents.toUriString();
resource.add(new Link(uri, "photo"));
return resource;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

@Configuration
public class SimpleRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {

@Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.exposeIdsFor(Person.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
insert into person( name, email) values ( 'josh', 'josh@email.com');
insert into person( name, email) values ( 'john', 'john@email.com');
insert into person( name, email) values ( 'yoana', 'yoana@email.com');
insert into person( name, email) values ( 'emo', 'emo@email.com');
40 changes: 40 additions & 0 deletions livelessons-rest/livelessons-rest-errors/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>livelessons</groupId>
<artifactId>livelessons-rest</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>livelessons-rest-errors</artifactId>
<properties>
<main.basedir>../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package demo;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

@Bean
public CommandLineRunner commandLineRunner(PersonRepository personRepository) {
return args -> {
Arrays.asList("Phil", "Josh").forEach(name -> personRepository
.save(new Person(name, (name + "@email.com").toLowerCase())));
personRepository.findAll().forEach(System.out::println);
};
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {

@Id
@GeneratedValue
private Long id;

private String name;

private String email;

Person() {
}

public Person(String name, String email) {
this.name = name;
this.email = email;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

@Override
public String toString() {
return "Person{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email
+ '\'' + '}';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package demo;

import java.io.FileNotFoundException;
import java.util.Optional;

import org.springframework.hateoas.VndErrors;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
@RequestMapping(produces = "application/vnd.error")
@ResponseBody
public class PersonControllerAdvice {

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(FileNotFoundException.class)
public VndErrors fileNotFoundException(FileNotFoundException ex) {
return this.error(ex, ex.getLocalizedMessage());
}

@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(PersonNotFoundException.class)
public VndErrors personNotFoundException(PersonNotFoundException e) {
return this.error(e, e.getPersonId() + "");
}

private <E extends Exception> VndErrors error(E e, String logref) {
String msg = Optional.of(e.getMessage()).orElse(e.getClass().getSimpleName());
return new VndErrors(logref, msg);
}

}
Loading

0 comments on commit bfb4535

Please sign in to comment.