You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2024/05/24 06:22:55 UTC
(causeway) 02/03: CAUSEWAY-2873: exercises 3-1, 3-2, 3-3
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit deceb525a0735c1afad7c4bcf015537cf125e8a7
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu May 23 17:39:12 2024 +0100
CAUSEWAY-2873: exercises 3-1, 3-2, 3-3
---
.../petclinic/pages/030-petowner-entity.adoc | 236 +++++++++++----------
.../modules/petclinic/partials/domain.adoc | 8 +-
2 files changed, 125 insertions(+), 119 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 6b6e6b9bac..a7b0d399fb 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -5,12 +5,11 @@
In this set of exercises we'll just focus on the `PetOwner` entity.
-[#exercise-3-1-rename-petowners-name-property]
-== Ex 3.1: Rename PetOwner's name property
+[#exercise-3-1-rename-petowners-knownAs-property]
+== Ex 3.1: Add PetOwner's knownAs property
-In the domain we are working on, `PetOwner` has a `firstName` and a `lastName` property, not a single `name` property.
-
-In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`, and change the fixture script that sets up data to something more realistic.
+In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner``'s preferred name.
+We'll also update the fixture script that sets up data to something more realistic.
@@ -18,181 +17,184 @@ In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`,
[source,bash]
----
-git checkout tags/03-01-renames-PetOwner-name-property
+git checkout tags/03-01-adds-PetOwner-knownAs-property
mvn clean install
mvn -pl spring-boot:run
----
-Remember you can use the menu:Prototyping[Fixture Scripts] menu to setup some example data.
+Use the menu:Prototyping[Fixture Scripts] menu to setup some example data.
=== Tasks
-Checkout the solution above and review the git history to see the changes that have already been made.
-These include:
-
-* property `PetOwner#name` -> `PetOwner#lastName` renamed
-* JPA mappings updated:
-** the corresponding JPQL named queries
-** the method names of `PetOwnerRepository`
+* add a `knownAs` property:
+
-This is a Spring Data repository, which uses a link:https://www.baeldung.com/spring-data-derived-queries[naming convention] to infer the queries
-
-** uniqueness constraint for `PetOwner`
-
-* the action method names of `PetOwners` domain service renamed
+[source,java]
+.PetOwner.java
+----
+@Column(length = 40, nullable = true, name = "knownAs") // <.>
+@Getter @Setter
+@Property(editing = Editing.ENABLED) // <.>
+@PropertyLayout(
+ fieldSetId = LayoutConstants.FieldSetId.IDENTITY, // <.>
+ sequence = "1.1" // <.>
+)
+private String knownAs;
+----
+<.> JPA annotation, declared to be `nullable` because we don't necessarily have a value
+<.> The xref:refguide:applib:index/annotation/Property.adoc[@Property] annotation provides domain semantics.
+(In comparison, the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation defines UI hints/semantics.
+Directly editable (similar to the "notes" property)
+<.> Renders this new property in the same field set as `name` ...
+<.> ... and after the `name`, not before it
+
-This also requires updating the `menubars.layout.xml`, which references these action names.
+It's also possible to define UI semantics through the associated `.layout.xml` file; we'll look at that alternative in a later exercise.
-* updating the xref:refguide:applib:index/annotation/ActionLayout.adoc#associateWith[@ActionLayout] of the `updateName` and `delete` action methods in `PetOwner`
+* update `PetOwner_persona` to specify an `knownAs` for some of the owners:
+
-In the UI, the buttons for these actions are located close to the renamed "lastName" property
+[source,java]
+.PetOwner_persona.java
+----
+public enum PetOwner_persona
+implements Persona<PetOwner, PetOwner_persona.Builder> {
+
+ JAMAL("Jamal Washington", "jamal.pdf", "J"),
+ CAMILA("Camila González", "camila.pdf", null),
+ ARJUN("Arjun Patel", "arjun.pdf", null),
+ NIA("Nia Robinson", "nia.pdf", null),
+ OLIVIA("Olivia Hartman", "olivia.pdf", null),
+ LEILA("Leila Hassan", "leila.pdf", null),
+ MATT("Matthew Miller", "matt.pdf", "Matt"),
+ BENJAMIN("Benjamin Thatcher", "benjamin.pdf", "Ben"),
+ JESSICA("Jessica Raynor", "jessica.pdf", "Jess"),
+ DANIEL("Daniel Keating", "daniel.pdf", "Dan");
+
+ private final String name;
+ private final String contentFileName;
+ private final String knownAs;
+
+ ...
+}
+----
-* renames `@Name` meta-annotation to `@LastName`.
+* for the builder, use the `knownAs` value of the entity once created:
+
-Meta-annotations are a useful way of eliminating duplication where the same value type appears in multiple locations, for example as both an entity property and in action parameters.
+[source,java]
+.PetOwner_persona.java
+----
+@Override
+protected PetOwner buildResult(final ExecutionContext ec) {
-Build and run the application to make sure it still runs fine.
+ val petOwner = wrap(PetOwners).create(persona.name);
+ // ...
+ if (persona.knownAs != null) {
+ petOwner.setKnownAs(persona.knownAs);
+ }
+
+ // ...
+}
+----
-[#exercise-3-2-add-petowners-firstname-property]
-== Ex 3.2: Add PetOwner's firstName property
-Now that `PetOwner` has a `lastName` property, let's also add a `firstName` property.
-We'll also update our fixture script (which sets up ``PetOwner``s) so that it is more descriptive.
+
+[#exercise-3-2-define-PetOwners-title-imperatively]
+== Ex 3.2: Define PetOwner's title imperatively
+
+Every domain object has a title, allowing end-users to distinguish one object instance from another.
+There's no requirement for this title to be unique, but it does need to be unique "enough".
+
+Our app currently declares the title of `PetOwner` declaratively using the xref:refguide:applib:index/annotation/Title.adoc[@Title] annotation on the `name` property.
+In this exercise, let's change that to also include the `knownAs` property as defined.
+Our rule will be:
+
+* if there is a `knownAs`, then the title should be "<name> (<knownAs>)"
+* but if `knownAs` is empty, then the title should be simply the `name`, as it is now.
+
+To do that, we need to define the title imperatively, using the xref:refguide:applib-methods:ui-hints.adoc#title[title()] method.
=== Solution
[source,bash]
----
-git checkout tags/03-02-adds-PetOwner-firstName-property
+git checkout tags/03-02-define-PetOwner-title-imperatively
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* copy `@LastName` meta-annotation to create `@FirstName`:
-+
-[source,java]
-.FirstName.java
-----
-@Property(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL)
-@Parameter(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "First Name")
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface FirstName {
-
- int MAX_LEN = 40;
-}
-----
-+
-Note that this property/parameter is optional.
-Its parameter name has also been updated.
-
+* locate the `name` property of `PetOwner`, and remove the `@Title` annotation
-* add a new (JPA nullable) property `firstName` to `PetOwner`:
-+
-[source,java]
-----
-@FirstName
-@Column(length = FirstName.MAX_LEN, nullable = true)
-@Getter @Setter @ToString.Include
-@PropertyLayout(fieldSetId = "name", sequence = "2")
-private String firstName;
-----
-
-* add a new factory method to accept a `firstName`, and refactor the existing one:
+* add a new `title()` method:
+
[source,java]
.PetOwner.java
----
-public static PetOwner withName(String name) {
- return withName(name, null);
-}
-
-public static PetOwner withName(String lastName, String firstName) {
- val simpleObject = new PetOwner();
- simpleObject.setLastName(lastName);
- simpleObject.setFirstName(firstName);
- return simpleObject;
+@ObjectSupport // <.>
+public String title() {
+ return getName() + (getKnownAs() != null ? " (" + getKnownAs() + ")" : "");
}
----
+<.> The xref:refguide:applib:index/annotation/ObjectSupport.adoc[@ObjectSupport] annotation tells the framework to include this method in the metamodel.
-* remove `@Title` annotation from `lastName` property, and add a `title()` method to derive from both properties:
-+
-[source,java]
-.PetOwner.java
-----
-public String title() {
- return getLastName() + (getFirstName() != null ? ", " + getFirstName() : "");
-}
-----
+[#exercise-3-3-remaining-PetOwner-properties]
+== Ex 3.3: Remaining `PetOwner` properties
-* Update the `PetOwner_persona` enum with more realistically last names (family names).
-+
-Learn more about fixture scripts xref:testing:fixtures:about.adoc[here].
+Comparing `PetOwner` as it is currently defined with our domain model, there are a couple of changes still to make:
+* we need to rename `lastCheckedIn` property to `lastVisit` property
+* we still need to add in a `telephoneNumber` property, and an `emailAddress` property
-[#exercise-3-3-modify-petowners-updatename-action]
-== Ex 3.3: Modify PetOwner's updateName action
-Although we've added a `firstName` property, currently it can't be edited.
-In this exercise we'll modify the `updateName` action to also allow the `firstName` to be changed.
+We'll make these changes in this exercise.
=== Solution
[source,bash]
----
-git checkout tags/03-03-modifies-PetOwner-updateName-action
+git checkout tags/03-03-remaining-PetOwner-properties
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* update `PetOwner#updateName` to also accept a new `firstName` parameter:
-+
-image::03-03/refactor-updateName.png[width=800px]
-+
-[source,java]
-.PetOwner.java
-----
-@Action(semantics = IDEMPOTENT, commandPublishing = Publishing.ENABLED, executionPublishing = Publishing.ENABLED)
-@ActionLayout(associateWith = "lastName", promptStyle = PromptStyle.INLINE)
-public PetOwner updateName(
- @LastName final String lastName,
- @FirstName String firstName) {
- setLastName(lastName);
- setFirstName(firstName);
- return this;
-}
-public String default0UpdateName() {
- return getLastName();
-}
-public String default1UpdateName() {
- return getFirstName();
-}
-----
+Rename the `lastCheckedIn` property to `lastVisit`:
-* add in a "default" supporting method for the new parameter.
+* rename the field in `PetOwner`
+* update `PetOwner_persona` also (but your IDE probably refactored this already).
+* to make it more realistic, let's change the fixture script so that the value of `lastVisit` is some number of days in the past:
+
[source,java]
-.PetOwner.java
+.PetOwner_persona.java
----
-public String default1UpdateName() {
- return getFirstName();
-}
+final var numDaysAgo = fakeDataService.ints().between(100, 2); // <.>
+final var lastVisit = clockService.getClock().nowAsLocalDate().minusDays(numDaysAgo); // <.>
+petOwner.setLastVisit(lastVisit);
----
-+
-The "default" supporting methods are called when the action prompt is rendered, providing the default for the "Nth" parameter of the corresponding action.
+<.> The xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] provides an easy way to create fake data for testing and prototyping
+<.> It's good practice to use xref:refguide:applib:index/services/clock/ClockService.adoc[ClockService], so it can be easily mocked in tests
+
+Add the `telephoneNumber` property:
+
+* ...
+* ...
+
+Add the `emailAddress` property:
+
+* ...
+* ...
+
+TODO: ideas for future steps:
+- introduce a meta-annotation
+- introduce a custom type for these
diff --git a/antora/components/tutorials/modules/petclinic/partials/domain.adoc b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
index 87641da592..e699f8cd23 100644
--- a/antora/components/tutorials/modules/petclinic/partials/domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
@@ -28,11 +28,15 @@ package pets {
class PetOwner <<role>> {
+id
..
- #lastName
- #firstName
+ #name
+ #knownAs
..
-phoneNumber
-emailAddress
+ ..
+ -lastVisit
+ ..
+ -notes
}
}