Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JSON:API for Spring HATEOAS

JSON:API for Spring HATEOAS

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

Kai Toedter

March 18, 2022
Tweet

More Decks by Kai Toedter

Other Decks in Programming

Transcript

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

    Infrastructure ▪ Web Technology Fan ▪ Open Source Lover ▪ E-mail: [email protected] ▪ Twitter: twitter.com/kaitoedter 4/29/2022 2 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License.
  2. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 3
  3. 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.” 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 4
  4. 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" } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 5
  5. 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/ 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 6
  6. 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> 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 8
  7. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 9
  8. 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! 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 10
  9. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 11
  10. 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution

    4.0 International License. 12 Representation Models
  11. Representation Models ▪ REST => Representational State Transfer ▪ Manipulation

    of resources through their representations ▪ Domain Model != Representation Model ▪ Spring HATEOAS provides RepresentationModel abstraction 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 13
  12. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 14
  13. Domain Model Example public class Director { private Long id;

    private String name; public Director(String name) { this.name = name; } … } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 15
  14. 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()); } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 16
  15. Response in HAL Media Type { "id": 2, "name": "Frank

    Darabont", "_links": { "self": { "href": "http://localhost:8080/api/directors/2" } } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 17
  16. JSON:API for Spring HATEOAS ▪ Open Source Project ▪ Apache

    2 License ▪ https://github.com/toedter/spring-hateoas- jsonapi ▪ Reference Documentation ▪ API Documentation 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 19
  17. Project Dependencies Maven: <dependency> <groupId>com.toedter</groupId> <artifactId>spring-hateoas-jsonapi</artifactId> <version>1.3.0</version> </dependency> Gradle: implementation

    'com.toedter:spring-hateoas-jsonapi:1.3.0' 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 21
  18. Domain Model Example public class Director { private Long id;

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

    "type": "directors", "attributes": { "name": "Frank Darabont" } }, "links": { "self": "http://localhost:8080/api/directors/2" } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 23
  20. 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 ▪ @JsonApiRelationships to mark a JSON:API relationship, only used for deserialization ▪ @JsonApiMeta to serialize/deserialize properties to JSON:API meta 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 25
  21. Example with Annotations public class Movie { @Id private String

    myId; @JsonApiType private String myType; @JsonApiMeta private String myMeta; private String title; } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 26
  22. Annotations Example (2) EntityModel.of( new Movie("1", "MOVIE", "metaValue", "Star Wars"));

    will be rendered as { "data": { "id": "1", "type": "MOVIE", "attributes": { "title": "Star Wars" }, "meta": { "myMeta": "metaValue" } } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 27
  23. JsonApiBuilder Movie movie = new Movie("1", "Star Wars"); final RepresentationModel<?>

    jsonApiModel = jsonApiModel() .model(movie) .build(); 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 29
  24. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 31
  25. 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(); 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 32
  26. Relationsship Example { "data": { "id": "1", "type": "movies", "attributes":

    { "title": "Star Wars" }, "relationships": { "directors": { "data": { "id": "1", "type": "directors" } } } } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 33
  27. 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 35
  28. Inclusion Example for (Movie movie : pagedResult.getContent()) { jsonApiModelBuilder.included(movie.getDirectors()); }

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

    Resources ▪ Relationships (by name) ▪ Attributes of included Relationships will be included in the JSON response 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 37
  30. 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) { 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 38
  31. 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution

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

    or by using the @JsonApiMeta annotation ▪ Paging information Meta can be added automatically => Use PagedModel 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 40
  33. 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 } } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 41
  34. Configuration (1) 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 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 43
  35. Configuration (2) You can configure ▪ 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. ▪ Experimental: Render Spring HATEOAS affordances as JSON:API link meta. 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 44
  36. 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)); } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 45
  37. 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"))); 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 46
  38. Error Example { "errors": [ { "links": { "about": "http://movie-db.com/problem"

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

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

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

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

    String myType = "movies"; @JsonIgnore @JsonApiRelationships("directors") List<Director> directors; <= ONLY Director id will be set! } 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 51
  43. Conclusion With JSON:API for Spring HATEOAS, it is very easy

    to support JSON:API (serialization + deserialization) out of the box. With the builder, special JSON:API features like relationships and sparse fieldsets are supported as well. 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 52
  44. “ The affordances of the environment are what it offers

    …​ what it provides or furnishes, either for good or ill. The verb 'to afford' is found in the dictionary, but the noun 'affordance' is not. I have made it up.” James J. Gibson The Ecological Approach to Visual Perception (1986) 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 54
  45. Affordances ▪ With Affordances, possible actions on a REST resource

    (or Link relation) can be described in more detail, e.g. ▪ What can you do with a REST resource? ▪ Create, Update, Delete, … ▪ Which parameters are mandatory or optional ▪ How would (internationalized) labels be displayed in a user interface ▪ … 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 55
  46. Spring HATEOAS Affordances Example © Kai Tödter, Licensed under a

    Creative Commons Attribution 4.0 International License. 56 final Affordance newMovieAffordance = afford(methodOn(MovieController.class).newMovie(null)); Link selfLink = linkTo(MovieController.class).slash("movies" + uriParams + "page[number]=" + pagedResult.getNumber() + "&page[size]=" + pagedResult.getSize()) .withSelfRel() .andAffordance(newMovieAffordance);
  47. Rendered Affordance (1) 4/29/2022 © Kai Tödter, Licensed under a

    Creative Commons Attribution 4.0 International License. 57 "links": { "self": { "href": "http://localhost:8080/api/movies?page[number]=0&page[size]=10", "meta": { "affordances": [ { "name": "newMovie", "link": { "rel": "newMovie", "href": "http://localhost:8080/api/movies" }, "httpMethod": "POST", ...
  48. Rendered Affordance (2) 4/29/2022 © Kai Tödter, Licensed under a

    Creative Commons Attribution 4.0 International License. 58 "inputProperties": [ { "name": "imdbId", "type": "text" }, … { "name": "title", "type": "text", "required": true }, … ]
  49. Links ▪ Spring HATEOAS: https://github.com/spring-projects/spring-hateoas ▪ JSON:API for Spring HATEOAS:

    https://github.com/toedter/spring-hateoas-jsonapi 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 60
  50. License ▪ This work is licensed under a Creative Commons

    Attribution 4.0 International License. ▪ See http://creativecommons.org/licenses/by/4.0/ 4/29/2022 © Kai Tödter, Licensed under a Creative Commons Attribution 4.0 International License. 61