diff --git a/affordances/README.adoc b/affordances/README.adoc index 59452d0..d238067 100644 --- a/affordances/README.adoc +++ b/affordances/README.adoc @@ -125,10 +125,10 @@ class EmployeeController { ... @GetMapping("/employees") - ResponseEntity>> findAll() { + ResponseEntity>> findAll() { - List> employeeResources = StreamSupport.stream(repository.findAll().spliterator(), false) - .map(employee -> new Resource<>(employee, + List> employeeResources = StreamSupport.stream(repository.findAll().spliterator(), false) + .map(employee -> new EntityModel<>(employee, linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel() .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, employee.getId()))) .andAffordance(afford(methodOn(EmployeeController.class).deleteEmployee(employee.getId()))), @@ -136,7 +136,7 @@ class EmployeeController { )) .collect(Collectors.toList()); - return ResponseEntity.ok(new Resources<>(employeeResources, + return ResponseEntity.ok(new CollectionModel<>(employeeResources, linkTo(methodOn(EmployeeController.class).findAll()).withSelfRel() .andAffordance(afford(methodOn(EmployeeController.class).newEmployee(null))))); } @@ -146,7 +146,7 @@ class EmployeeController { Employee savedEmployee = repository.save(employee); - return new Resource<>(savedEmployee, + return new EntityModel<>(savedEmployee, linkTo(methodOn(EmployeeController.class).findOne(savedEmployee.getId())).withSelfRel() .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, savedEmployee.getId()))) .andAffordance(afford(methodOn(EmployeeController.class).deleteEmployee(savedEmployee.getId()))), @@ -171,7 +171,7 @@ class EmployeeController { Look at these controller details: * A *GET* call for the aggregate collection is laid out. It uses the repository's `findAll()` method and transforms it -into a `Resources>`. +into a `CollectionModel>`. * A *POST* call for creating new employees is also defined, on the same URI. `@RequestBody` tells Spring MVC to deserialize the request body into an `Employee` object, which is then sent through the repository's `save()` operation. From there, it's wrapped as a `Resource` with links added to itself and to the aggregate root. Finally a `Location` response header @@ -204,10 +204,10 @@ class EmployeeController { ... @GetMapping("/employees/{id}") - ResponseEntity> findOne(@PathVariable long id) { + ResponseEntity> findOne(@PathVariable long id) { return repository.findById(id) - .map(employee -> new Resource<>(employee, + .map(employee -> new EntityModel<>(employee, linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel() .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, employee.getId()))) .andAffordance(afford(methodOn(EmployeeController.class).deleteEmployee(employee.getId()))), @@ -225,7 +225,7 @@ class EmployeeController { Employee updatedEmployee = repository.save(employeeToUpdate); - return new Resource<>(updatedEmployee, + return new EntityModel<>(updatedEmployee, linkTo(methodOn(EmployeeController.class).findOne(updatedEmployee.getId())).withSelfRel() .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, updatedEmployee.getId()))) .andAffordance(afford(methodOn(EmployeeController.class).deleteEmployee(updatedEmployee.getId()))), diff --git a/api-evolution/README.adoc b/api-evolution/README.adoc index 5847bc9..6103672 100644 --- a/api-evolution/README.adoc +++ b/api-evolution/README.adoc @@ -106,7 +106,7 @@ From there, we need to add the ability to create new employees: [source,java] ---- @PostMapping("/employees") -public ResponseEntity> newEmployee(@RequestBody Employee employee) { +public ResponseEntity> newEmployee(@RequestBody Employee employee) { Employee savedEmployee = repository.save(employee); @@ -223,9 +223,9 @@ To construct a listing of all employees, check out the following controller meth public String index(Model model) throws URISyntaxException { Traverson client = new Traverson(new URI(REMOTE_SERVICE_ROOT_URI), MediaTypes.HAL_JSON); - Resources> employees = client + CollectionModel> employees = client .follow("employees") - .toObject(new ResourcesType>(){}); + .toObject(new ResourcesType>(){}); model.addAttribute("employee", new Employee()); model.addAttribute("employees", employees); @@ -237,7 +237,7 @@ public String index(Model model) throws URISyntaxException { Presuming you already understand Spring MVC, let's focus on the RESTful bits. * `Traverson` is used to start from the root node (*REMOTE_SERVICE_ROOT_URI*) and "hop" to *employees*. -Then it fetches an object, and transforms it into Spring HATEOAS's vendor neutral `Resources>` structure. +Then it fetches an object, and transforms it into Spring HATEOAS's vendor neutral `CollectionModel>` structure. * Using this, we are able to construct a `Model` object for the template. ** An *employee* object is created to hold an empty, form-backed bean. ** *employees* is loaded up with the entire Spring HATEOAS structure, allowing the template to use what bits it wants. @@ -274,7 +274,7 @@ It isn't necessary to post ALL of the Thymeleaf template `index.html`, but the c This shows the employee data being served up inside an HTML table. * `th:each="employee : ${employees}"` lets your iterate over each one. -* `th:text="${employee.content.name}"` navigates the `Resource` structure (remmeber, you're iterating over each entry of `Resources<>`). +* `th:text="${employee.content.name}"` navigates the `EntityModel` structure (remmeber, you're iterating over each entry of `CollectionModel<>`). * `${employee.links}` gives each entry access to a Spring HATEOAS `Link`. * `` lets you show the end user each link, both name and URI. diff --git a/hypermedia/README.adoc b/hypermedia/README.adoc index bf5d512..14e84d2 100644 --- a/hypermedia/README.adoc +++ b/hypermedia/README.adoc @@ -145,7 +145,7 @@ class ManagerController { * Spring Web's {@link ResponseEntity} fluent API. */ @GetMapping("/managers") - ResponseEntity>> findAll() { + ResponseEntity>> findAll() { return ResponseEntity.ok( assembler.toCollectionModel(repository.findAll())); @@ -159,7 +159,7 @@ class ManagerController { * @param id */ @GetMapping("/managers/{id}") - ResponseEntity> findOne(@PathVariable long id) { + ResponseEntity> findOne(@PathVariable long id) { return ResponseEntity.ok( assembler.toEntityModel(repository.findOne(id))); } @@ -197,7 +197,7 @@ class EmployeeController { * @return */ @GetMapping("/managers/{id}/employees") - public ResponseEntity>> findEmployees(@PathVariable long id) { + public ResponseEntity>> findEmployees(@PathVariable long id) { return ResponseEntity.ok( assembler.toCollectionModel(repository.findByManagerId(id))); } @@ -237,7 +237,7 @@ class ManagerResourceAssembler extends SimpleIdentifiableResourceAssembler resource) { + protected void addLinks(EntityModel resource) { /** * Retain default links. */ @@ -313,7 +313,7 @@ To support this, we can write the corresponding route in `EmployeeController`: [source,java] ---- @GetMapping("/employees/detailed") -public ResponseEntity>> findAllDetailedEmployees() { +public ResponseEntity>> findAllDetailedEmployees() { return ResponseEntity.ok( employeeWithManagerResourceAssembler.toCollectionModel( @@ -323,7 +323,7 @@ public ResponseEntity>> findAllDetailedE } @GetMapping("/employees/{id}/detailed") -public ResponseEntity> findDetailedEmployee(@PathVariable Long id) { +public ResponseEntity> findDetailedEmployee(@PathVariable Long id) { Employee employee = repository.findOne(id); @@ -352,7 +352,7 @@ class EmployeeWithManagerResourceAssembler extends SimpleResourceAssembler resource) { + protected void addLinks(EntityModel resource) { resource.add(linkTo(methodOn(EmployeeController.class).findDetailedEmployee(resource.getContent().getId())).withSelfRel()); resource.add(linkTo(methodOn(EmployeeController.class).findOne(resource.getContent().getId())).withRel("summary")); @@ -365,7 +365,7 @@ class EmployeeWithManagerResourceAssembler extends SimpleResourceAssembler> resources) { + protected void addLinks(CollectionModel> resources) { resources.add(linkTo(methodOn(EmployeeController.class).findAllDetailedEmployees()).withSelfRel()); resources.add(linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")); @@ -380,8 +380,8 @@ This has a handful of differences from the `ResourceAssembler` objects you've bu * Since the routes are different than traditional */employees* and */employees/{id}*, it makes no sense to use `SimpleIdentifiableResourceAssembler`. So instead, you want to fall back to `SimpleResourceAssembler`, in which NO links are defined out of the box. * Because there are no defined routes, you are in full control. -** `addLinks(Resource resource)` defines links for single items -** `addLinks(Resources> resources)` defines links for collections +** `addLinks(EntityModel resource)` defines links for single items +** `addLinks(CollectionModel> resources)` defines links for collections In this case, single `EmployeeWithManager` items include a self link to itself, a hop to it's parallel record that only has `Employee` info known as *summary*, and a link to the detailed collection. To avoid semantic confusion, this is called *detailedEmployees* given *employees* is the common reference to @@ -389,7 +389,7 @@ a collection of summary `Employee` records. It also makes sense to add links from the other existing REST resources to this detailed `EmployeeWithManager`. -WARNING: Even though `addLinks(Resources> resources)` gives you access to a single item's `Resource` object, +WARNING: Even though `addLinks(CollectionModel> resources)` gives you access to a single item's `EntityModel` object, it is recommended to NOT manipulate individual item links this way. Instead, use the other method. Is this the _only_ way to display a detailed record? Not at all. Spring MVC supports request parameters, so it's not that difficult @@ -522,10 +522,10 @@ public class SupervisorController { } @GetMapping("/supervisors/{id}") - public ResponseEntity> findOne(@PathVariable Long id) { + public ResponseEntity> findOne(@PathVariable Long id) { - Resource managerResource = controller.findOne(id).getBody(); - Resource supervisorResource = new Resource<>( + EntityModel managerResource = controller.findOne(id).getBody(); + EntityModel supervisorResource = new EntityModel<>( new Supervisor(managerResource.getContent()), managerResource.getLinks()); @@ -535,17 +535,17 @@ public class SupervisorController { ---- In this example, the assumption is that there was a route for individual supervisors, but not a link for a collection. -This controller has that route, and serves up a `Resource` record. But instead of fetching the data directly, +This controller has that route, and serves up a `EntityModel` record. But instead of fetching the data directly, it leverages the `ManagerController`. Is that a good idea or a bad idea? Again, there are tradeoffs. This example is meant to illustrate other options. In this case, leveraging `ManagerController` -allows all links to be generated courtesy of the `ManagerResourceAssembler`. When a `ResponseEntity>` object +allows all links to be generated courtesy of the `ManagerResourceAssembler`. When a `ResponseEntity>` object is returned by the controller, its wrapped REST resource is extracted by Spring MVC's `getBody()` method. A new `Supervisor` REST resource is constructed by injecting the `Manager` into a `Supervisor` DTO. The provided links are -then copied into that `Resource` object. +then copied into that `EntityModel` object. Hence, this controller will respond to calls for */supervisors/{id}*, but provide links onto the new system should the client want to gracefully start migrating. diff --git a/simplified/README.adoc b/simplified/README.adoc index 08d2e82..9fa1a0b 100644 --- a/simplified/README.adoc +++ b/simplified/README.adoc @@ -55,7 +55,7 @@ operations. In REST, the "thing" being linked to is a *resource*. Resources provide both information as well as details on _how_ to retrieve and update that information. -Spring HATEOAS defines a generic `Resource` container that lets you store any domain object (`Employee` in this example), and +Spring HATEOAS defines a generic `EntityModel` container that lets you store any domain object (`Employee` in this example), and add additional links. IMPORTANT: Spring HATEOAS's `Resource` and `Link` classes are *vendor neutral*. HAL is thrown around a lot, being the @@ -93,22 +93,22 @@ The route for the https://martinfowler.com/bliki/DDD_Aggregate.html[aggregate ro * Then return them through Spring Web's {@link ResponseEntity} fluent API. */ @GetMapping("/employees") -ResponseEntity>> findAll() { +ResponseEntity>> findAll() { - List> employees = StreamSupport.stream(repository.findAll().spliterator(), false) - .map(employee -> new Resource<>(employee, + List> employees = StreamSupport.stream(repository.findAll().spliterator(), false) + .map(employee -> new EntityModel<>(employee, linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(), linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees"))) .collect(Collectors.toList()); return ResponseEntity.ok( - new Resources<>(employees, + new CollectionModel<>(employees, linkTo(methodOn(EmployeeController.class).findAll()).withSelfRel())); } ---- It retrieves a collection of `Employee` objects, streams through a Java 8 spliterator, and converts them into a collection -of `Resource` objects by using Spring HATEOAS's `linkTo` and `methodOn` helpers to build links. +of `EntityModel` objects by using Spring HATEOAS's `linkTo` and `methodOn` helpers to build links. * The natural convention with REST endpoints is to serve a *self* link (denoted by the `.withSelfRel()` call). * It's also useful for any single item resource to include a link back to the aggregate (denoted by the `.withRel("employees")`). @@ -117,7 +117,7 @@ The whole collection of single item resources is then wrapped in a Spring HATEOA NOTE: `Resources` is Spring HATEOAS's vendor neutral representation of a collection. It has it's own set of links, separate from the links of each member of the collection. That's why the whole -structure is `Resources>` and not `Resources`. +structure is `CollectionModel>` and not `CollectionModel`. To build a single resource, the `/employees/{id}` route is shown below: @@ -130,10 +130,10 @@ To build a single resource, the `/employees/{id}` route is shown below: * @param id */ @GetMapping("/employees/{id}") -ResponseEntity> findOne(@PathVariable long id) { +ResponseEntity> findOne(@PathVariable long id) { return repository.findById(id) - .map(employee -> new Resource<>(employee, + .map(employee -> new EntityModel<>(employee, linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(), linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees"))) .map(ResponseEntity::ok) @@ -142,7 +142,7 @@ ResponseEntity> findOne(@PathVariable long id) { ---- This code is almost identical. It fetches a single item `Employee` from the database and that wraps up into a -`Resource` object with the same links, but that's it. No need to create a `Resources` object since is NOT a +`EntityModel` object with the same links, but that's it. No need to create a `Resources` object since is NOT a collection. IMPORTANT: Does this look like duplicate code found in the aggregate root? Sures it does. That's why Spring HATEOAS