Save 37% off PRO during our Black Friday Sale! »

JSON:API für Spring HATEOAS

JSON:API für Spring HATEOAS

This slide deck introduces my Open Source Project "JSON:API für Spring HATEOAS", see https://github.com/toedter/spring-hateoas-jsonapi

A0aae1297a0593c1316abdcdb4131e3a?s=128

Kai Toedter

November 11, 2021
Tweet

Transcript

  1. for HATEOAS Kai Tödter

  2. Who am I? ▪ Principal Key Expert at Siemens Smart

    Infrastructure ▪ Web Technology Fan ▪ Open Source Lover ▪ E-mail: kai@toedter.com ▪ Twitter: twitter.com/kaitoedter 11/12/2021 2 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License.
  3. Show Hands! 11/12/2021 © Kai Tödter, Licensed under a Creative

    Commons Attribution 4.0 International License. 3
  4. HATEOAS ▪ Is for “Hypermedia As The Engine Of Application

    State” ▪ Very hard to pronounce ☺ ▪ Key concept of REST ▪ WIKIPEDIA: With HATEOAS, a client interacts with a network application whose application servers provide information dynamically through hypermedia 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 4
  5. JSON:API Web site: jsonapi.org “JSON:API is designed to minimize both

    the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability.” 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 5
  6. Minimal JSON:API Example { "data": { "id": "1", "type": "movies",

    "attributes": { "title": "The Shawshank Redemption", "year": 1994, "rating": 9.3, "rank": 1 } }, "links": { "self": "https://mymovies.com/api/movies/1" } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 6
  7. Spring HATEOAS ▪ Spring basic framework for REST with Hypermedia

    support ▪ Supports generic Hypermedia API ▪ Build-in Support for Representations like HAL, HAL-FORMS, UBER, Collection+JSON, … ▪ Community-based media types: JSON:API, Siren ▪ https://docs.spring.io/spring-hateoas/docs/current- SNAPSHOT/reference/html/ 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 7
  8. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 8 Links
  9. Links ▪ Essential for hypermedia ▪ In REST: How to

    navigate to a REST resource ▪ Link semantic/name is called link relation ▪ The relation between a REST resource and the target REST resource ▪ Links are well known from HTML, like <a href="url">link text</a> 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 9
  10. Links in Spring HATEOAS Link link = new Link("/my-url"); ▪

    A link automatically has a self relation Link link = new Link("/my-url", "my-rel"); ▪ A link with my-rel relation 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 10
  11. Link Relations ▪ Many Link relations are standardized by IANA

    ▪ IANA = Internet Assigned Numbers Authority ▪ https://www.iana.org/assignments/link- relations/link-relations.xhtml ▪ Examples: self, item, next, last, … ▪ Recommendation: Before creating a custom name for a link relation, look up the IANA list! 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 11
  12. Links are great! ▪ For providing navigation to useful other

    REST resources ▪ For providing domain knowledge to the REST clients, so that they don’t have to compute domain state on there own 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 12
  13. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 13 Representation Models
  14. Representation Models ▪ REST => Representational State Transfer ▪ Manipulation

    of resources through their representations ▪ Domain Model != Representation Model ▪ Spring HATEOAS provides RepresentationModel abstraction 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 14
  15. Spring HATEOAS RepresentationModel ▪ RepresentationModel ▪ Root class, for REST

    item resources ▪ CollectionModel ▪ For REST collection resources ▪ EntityModel ▪ Convenient wrapper for converting a domain model into a representation model ▪ PagedModel ▪ Addition to CollectionModel for paged collections 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 15
  16. Domain Model Example public class Director { private Long id;

    private String name; public Director(String name) { this.name = name; } … } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 16
  17. Controller Example @GetMapping("/directors/{id}") public ResponseEntity<EntityModel<Director>> findOne(@PathVariable Long id) { return

    repository.findById(id) .map(director -> EntityModel.of(director) .add(linkTo(methodOn(DirectorController.class) .findOne(director.getId())).withSelfRel())) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 17
  18. Response in HAL Media Type { "id": 2, "name": "Frank

    Darabont", "_links": { "self": { "href": "http://localhost:8080/api/directors/2" } } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 18
  19. {json:api} for Spring HATEOAS

  20. JSON:API for Spring HATEOAS ▪ Open Source Project ▪ Apache

    2 License ▪ https://github.com/toedter/spring-hateoas- jsonapi ▪ Reference Documentation ▪ API Documentation 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 20
  21. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 21
  22. Project Dependencies Maven: <dependency> <groupId>com.toedter</groupId> <artifactId>spring-hateoas-jsonapi</artifactId> <version>1.1.1</version> </dependency> Gradle: implementation

    'com.toedter:spring-hateoas-jsonapi:1.1.1' 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 22
  23. Domain Model Example public class Director { private Long id;

    private String name; public Director(String name) { this.name = name; } … } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 23
  24. Response in JSON:API media type { "data": { "id": "2",

    "type": "directors", "attributes": { "name": "Frank Darabont" } }, "links": { "self": "http://localhost:8080/api/directors/2" } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 24
  25. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 25 Annotations
  26. Annotations ▪ @JsonApiId to mark a JSON:API id ▪ @JsonApiType

    to mark a JSON:API type ▪ @JsonApiTypeForClass to mark a class to provide a JSON:API type ▪ The JSON:API type is a required value of this annotation ▪ @JsonApiRelationships to mark a JSON:API relationship ▪ This annotation is not used for serialization but for deserialization 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 26
  27. Example with Annotations public class Movie { @JsonApiId private String

    myId; private String title; @JsonApiType private String myType; } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 27
  28. Annotations Example (2) EntityModel.of(new Movie("1", "Star Wars", "MOVIE"))); will be

    rendered as { "data": { "id": "1", "type": "MOVIE", "attributes": { "title": "Star Wars" } } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 28
  29. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 29 Builder
  30. JsonApiBuilder Movie movie = new Movie("1", "Star Wars"); final RepresentationModel<?>

    jsonApiModel = jsonApiModel() .model(movie) .build(); 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 30
  31. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 31 Relationships
  32. Relationships ▪ In json:api, relationships between REST resources are made

    explicit, using the relationship object ▪ Relationships can be to-one or to-many ▪ Relationships must contain at least one of: ▪ links: a links object containing at least one of the following: ▪ self: a link for the relationship itself ▪ related: a related resource link ▪ data: resource linkage with id and type ▪ meta: meta object that contains non-standard meta- information about the relationship 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 32
  33. Build Relationships Movie movie = new Movie("1", "Star Wars"); Director

    director = new Director("1", "George Lucas"); final RepresentationModel<?> jsonApiModel = jsonApiModel() .model(movie) .relationship("directors", director) .build(); 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 33
  34. Relationsship Example { "data": { "id": "1", "type": "movies", "attributes":

    { "title": "Star Wars" }, "relationships": { "directors": { "data": { "id": "1", "type": "directors" } } } } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 34
  35. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 35 Inclusion
  36. Inclusion of Related Resources ▪ With included, you can include

    the content of related recourses in the compound document ▪ The JsonApiBuilder supports adding ▪ A single included resource ▪ A collection of included resources ▪ The builder assures that included resources with same id and type appear only ONCE 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 36
  37. Inclusion Example for (Movie movie : pagedResult.getContent()) { jsonApiModelBuilder.included(movie.getDirectors()); }

    "included": [ { "id": "1", "type": "directors", "attributes": { "name": "Lana Wachowski" } }, … 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 37
  38. Sparse Fieldsets Convenient way to specify which ▪ Attributes of

    Resources ▪ Relationships (by name) ▪ Attributes of included Relationships will be included in the JSON response 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 38
  39. Controller for Sparse Fieldset In a REST controller, a method

    with HTTP- mapping could provide an optional request attribute for each sparse fieldset @GetMapping("/movies") public ResponseEntity<RepresentationModel<?>> findAll( @RequestParam(value = "included", required = false) String[] included, @RequestParam(value = "fields[movies]", required = false) String[] fieldsMovies) { 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 39
  40. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 40 Sparse Fieldsets Demo
  41. Meta ▪ JSON:API Meta can be added using the builder

    ▪ Paging information Meta can be added automatically => Use PagedModel 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 41
  42. Pagination Example … "links": { "self": "http://localhost/movies", "first": "http://localhost/movies?page[number]=0&page[size]=2", "prev":

    "http://localhost/movies?page[number]=0&page[size]=2", "next": "http://localhost/movies?page[number]=2&page[size]=2", "last": "http://localhost/movies?page[number]=49&page[size]=2" }, "meta": { "page": { "number": 1, "size": 2, "totalPages": 50, "totalElements": 100 } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 42
  43. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 43 Configuration
  44. Configuration You can configure ▪ If the JSON:API version should

    be rendered automatically, the default is false. ▪ If JSON:API types should be rendered as pluralized or non pluralized class names. ▪ The default is pluralized ▪ If JSON:API types should be rendered as lower cased or original class names. ▪ The default is lower cased ▪ If page information of a PagedModel should be rendered automatically as JSON:API meta object. ▪ The default is true ▪ If a specific Java class should be rendered with a specific JSON:API type. ▪ A lambda expression to add additional configuration to the Jackson ObjectMapper used for serialization. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 44
  45. Configuration Example @Bean JsonApiConfiguration jsonApiConfiguration() { return new JsonApiConfiguration() .withJsonApiVersionRendered(true)

    .withPluralizedTypeRendered(false) .withLowerCasedTypeRendered(false) .withTypeForClass(MyMovie.class, "my-movies") .withObjectMapperCustomizer( objectMapper -> objectMapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)); } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 45
  46. Error Handling To create JSON:API compliant error messages, you can

    use JsonApiErrors and JsonApiError return ResponseEntity.badRequest().body( JsonApiErrors.create().withError( JsonApiError.create() .withAboutLink("http://movie-db.com/problem") .withTitle("Movie-based problem") .withStatus(HttpStatus.BAD_REQUEST.toString()) .withDetail("This is a test case"))); 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 46
  47. Error Example { "errors": [ { "links": { "about": "http://movie-db.com/problem"

    }, "status": "400 BAD_REQUEST", "title": "Movie-based problem", "detail": "This is a test case" } ] } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 47
  48. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 48 Deserialization
  49. Deserialization ▪ We want to deserialize JSON input directly to

    Spring HATEOAS representation models, e.g. for ▪ HTTP POST ▪ HTTP PATCH 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 49
  50. Deserialization Example (1) { "data": { "type": "movies", "attributes": {

    "title": "New Movie" }, "relationships": { "directors": { "data": [ { "id": "1", "type": "directors" }, { "id": "2", "type": "directors" } ] } } } } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 50
  51. Deserialization Example (2) public class MovieWithDirectors extends Movie { @JsonApiType

    String myType = "movies"; @JsonIgnore @JsonApiRelationships("directors") List<Director> directors; <= ONLY Director id will be set! } 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 51
  52. Polymorphic Deserialization (1) @PostMapping("/movies") public ResponseEntity<?> newMovie(@RequestBody EntityModel<Movie> movie) {

    … public class MovieWithRating extends Movie { private double rating; You could now annotate the Movie class with: @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) @JsonSubTypes({ @JsonSubTypes.Type(value = MovieWithRating.class, name = "movieWithRating") }) 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 52
  53. Polymorphic Deserialization (2) { "data": { "id": "3", "type": "movies",

    "attributes": { "@type": "movieWithRating", "title": "Batman Begins", "rating": 8.2 } } } => would be deserialized to an object of class MovieWithRating, even though the controller method accepts the superclass Movie. Note: @-members will be introduced in JSON:API version 1.1. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 53
  54. Polymorphic Deserialization (3) If you use withTypeForClass in a JsonApiConfiguration,

    you could also use withTypeForClassUsedForDeserialization for polymorphic deserialization. new JsonApiConfiguration() .withTypeForClass(MovieDerivedWithTypeForClass.class, "my-movies") .withTypeForClass(DirectorWithEmail.class, "directors-with-email") .withTypeForClassUsedForDeserialization(true)); 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 54
  55. 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 55 Discussion
  56. Links ▪ Spring HATEOAS: https://github.com/spring-projects/spring-hateoas ▪ JSON:API for Spring HATEOAS:

    https://github.com/toedter/spring-hateoas-jsonapi 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 56
  57. License ▪ This work is licensed under a Creative Commons

    Attribution 4.0 International License. ▪ See http://creativecommons.org/licenses/by/4.0/ 11/12/2021 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 57