@@ -436,3 +436,148 @@ Link link = discoverer.findLinkWithRel("foo", content);
436
436
assertThat(link.getRel(), is("foo"));
437
437
assertThat(link.getHref(), is("/foo/bar"));
438
438
----
439
+
440
+ [[affordances]]
441
+ == Affordances
442
+
443
+ The *Affordances API* provides the means to mark up your domain objects and controllers such that you can generate
444
+ not only links, but additional queries with extra metadata.
445
+
446
+ This is done using a much richer version of Spring HATEOAS's `Link` class, the `Affordance` class. Fundamentally,
447
+ an `Affordance` IS a `Link`:
448
+
449
+ [source,java]
450
+ ----
451
+ public class Affordance extends Link {
452
+ ...
453
+ }
454
+ ----
455
+
456
+ It comes with extra operators that allows assembling not just links, but access to extra metadata that can
457
+ be used to serve clients, as you'll see demonstrated in this section.
458
+
459
+ For a more detailed description of "affordances" in the realm of hypermedia, checkout the following video by
460
+ Mike Amundsen.
461
+
462
+ video::W7NRMhZ4MDk[youtube]
463
+
464
+ === Generating metadata about possible flows
465
+
466
+ Imagine defining the following domain object:
467
+
468
+ [source,java,indent=0]
469
+ ----
470
+ include::{baseDir}/src/test/java/org/springframework/hateoas/affordance/AffordanceDocumentationTest.java[tag=employee]
471
+ ----
472
+
473
+ This is like any other domain object with its attributes (with the boilerplate handled by Lombok's
474
+ `@Data` annotation). However, buried in the constructor call are some extra annotations:
475
+
476
+ * Jackson's `@JsonCreator` annotation flags this constructor as the one to use when Jackson creates a new object.
477
+ * Each field has a corresponding Jackson `@JsonProperty` annotation.
478
+ * Additionally, the input fields are further marked up with `@Input(required=true)`, indicated they are not
479
+ optional fields.
480
+
481
+ While these annotations aren't require for Jackson to do its thing, the Affordance API uses this additional data
482
+ to fabricate additional hypermedia.
483
+
484
+ Create a Spring Web controller like this:
485
+
486
+ [source,java,indent=0]
487
+ ----
488
+ include::{baseDir}/src/test/java/org/springframework/hateoas/affordance/AffordanceDocumentationTest.java[tag=employee-controller]
489
+ ...
490
+ }
491
+ ----
492
+
493
+ Add a request handler for fetching all employees:
494
+
495
+ [source,java,indent=0]
496
+ ----
497
+ include::{baseDir}/src/test/java/org/springframework/hateoas/affordance/AffordanceDocumentationTest.java[tag=find-all]
498
+ ----
499
+
500
+ The return type is `Resources<Resource<Employee>>`. This represents a resource collection, with each element itself
501
+ also a resource. To build the collection up, you leverage the controller's `findOne()` method, which returns a
502
+ `Resource<Employee>`. With the content assembled, you can move into defining this resource's *affordances*.
503
+
504
+ The first link in any `Affordance` is the *self* link. So you can use the Affordance API's
505
+ `linkTo(methodOn(...)` methods (just like `ControllerLinkBuilder`) and create an `AffordanceBuilder` against
506
+ this method.
507
+
508
+ From there, we can take the `builder` and add more affordance using the `.add(...)` method. In this example, you
509
+ are grabbing a hold of the controller's `newEmployee()` method.
510
+
511
+ TIP: Certain mediatypes, like HAL-Forms, support _templates_. These are additional operations that work, but
512
+ generally against the same URI (the *self* link). Therefore, we are only adding affordances that also map onto
513
+ `/employees` in this method.
514
+
515
+ Return a `Resources` collection resource, making the `builder` produce a *self* link.
516
+
517
+ NOTE: Normally, you might use a Spring Data repository to actually retrieve this list of employees. However,
518
+ for simplicity, we are using a plain old Java map.
519
+
520
+ Before we test drive this, we need to define `findOne(...)` that we just saw. Add the following to your controller:
521
+
522
+ [source,java,indent=0]
523
+ ----
524
+ include::{baseDir}/src/test/java/org/springframework/hateoas/affordance/AffordanceDocumentationTest.java[tag=find-one]
525
+ ----
526
+
527
+ As shown in the comments, you start with an affordance for the "self" link, i.e. this method. Assuming this controller
528
+ has support to both *PUT* and *PATCH* single resources, we can create affordances for both. A key difference between
529
+ PUT and PATCH is that PUT replaces the entire record, while PATCH often is used to update individual fields. Hence,
530
+ we want to flip the parameters on `Employee` that are marked `@Input(required = true)` to false.
531
+
532
+ With this in place, we can now inspect the hypermedia generated by Spring HATEOAS.
533
+
534
+ To interrogate the collection, we just need to make a request like this:
535
+
536
+ include::{snippets}/basic/1/http-request.adoc[]
537
+
538
+ As expected, we get back a collection resource with `_embedded` and `_links`.
539
+
540
+ include::{snippets}/basic/1/response-body.adoc[]
541
+
542
+ This document is chock full of data. But when it comes time to create a new employee, what do we do?
543
+
544
+ IMPORTANT: HAL is a popular format for hypermedia. Here is where we get to see its limitations. The self link
545
+ at the bottom to `/employees` _only_ shows us the URI. It doesn't communicate ALL the operations _afforded_
546
+ to us at that URI.
547
+
548
+ To discover what we can do, we merely need to change the *Accept* header in our request, like this:
549
+
550
+ include::{snippets}/basic/2/http-request.adoc[]
551
+
552
+ This will give us a HAL-Forms document.
553
+
554
+ include::{snippets}/basic/2/response-body.adoc[]
555
+
556
+ We can see the *self* link at the top. But below that, is a *templates* section. These are operations we can perform.
557
+
558
+ Remember where we had `builder.and(linkTo(methodOn(EmployeeController.class).newEmployee(null)).rel("create"))` in
559
+ `all()`? That is what got transformed into the *default* template for *POST*, using the `@JsonCreator` and `@Input`
560
+ metadata from our domain object.
561
+
562
+ This hypermedia can be used by your website to generate an HTML FORM, hence why it's called HAL-Forms.
563
+
564
+ Remember marking marked up `findOne`? To see that, we need to navigate to an individual employee's
565
+ HAL-Form:
566
+
567
+ include::{snippets}/basic/4/http-request.adoc[]
568
+
569
+ NOTE: We skipped navigating to the HAL record for `/employees/0` and jumped straight to the HAL-Form:
570
+
571
+ include::{snippets}/basic/4/response-body.adoc[]
572
+
573
+ You can see the self link as well as the link back to the collection resource. But focusing on the self like
574
+ (which HAL-Forms apply to), there are two templates: *PUT* and *PATCH*. In PUT, both properties are required,
575
+ as depicted in your `Employee` definition. But in PATCH, they aren't.
576
+
577
+ One last piece of information on this HAL-Form. The PUT template is named *default*, since it's the first. But
578
+ the PATCH template is named *partial-update*. By default, Spring HATEOAS will name the template based on the
579
+ HTTP verb. But that was overridden using `@Action("partial-update")`, applied to the `.partialUpdate(...)`.
580
+
581
+ By using a few extra annotations on the domain object and the controller, and by chaining together some REST
582
+ methods, the Affordance API has made it possible to generate a much richer hypermedia that can simplify front
583
+ end development.
0 commit comments