The PetClinic domain, and the petowner module
By now you should understand the basics of what Apache Causeway does, but there’s only so much we can learn from a single domain class. Let’s therefore evolve the app into a slightly more interesting domain and explore other features of the framework.
The domain we’re going to work on is a version of the venerable "Pet Clinic" app. Here’s a sketch of (our version of) its domain:
|
The colours used are from "Java: Modeling in Color" book by Coad et al. |
Some of the use cases we might want to support include:
-
create a
PetOwner -
add and remove
Pets for saidPetOwner -
delete a
PetOwnerand itsPets andVisits, so long as there are no unpaidVisits. -
book a
Petin for aVisit -
delete a
PetOwnerand itsPets, unless there areVisits.
This tutorial has worked solutions for all of these
Some ideas for future extensions to the domain:
-
enter an
outcomeandcostof aVisit -
allow a
PetOwnerto pay for aVisit -
find
Visits not yet paid and overdue -
delete a
PetOwnerand itsPets andVisits, so long as there are no unpaidVisits. -
add a previous/next buttons when paging through upcoming
Visits -
make "lastVisit" non-editable and maintained automatically from
Visits
Once you have finished the formal tutorial, have a go at implementing some of these ideas.
Ex 2.1: Create a new petowner module
In this exercise we’ll create a new petowner module, copying the simple module to get us started.
The SimpleObject entity will be copied to PetOwner, and the SimpleObjects domain service will be copied to PetOwners, but we won’t change the structure.
We’ll also leave the simple module untouched, to use as a reference.
Solution
git checkout tags/3.2.0/02-01-copies-SimpleObject-to-PetOwner
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
To save time, just checkout the solution tag above and review the git history to see the classes that were copied and renamed:
-
dom.petownerpackage:-
SimpleObject→PetOwner(entity)plus related
.layout.xml,.pngand.columnOrder.txtfiles -
SimpleObjects→PetOwners(domain service) -
SimpleObjectRepositoryrepository service →PetOwnerRepository -
SimpleModule→PetOwnerModule(module)
-
-
fixturepackage:-
PetOwner_persona(fixture script)
-
-
typespackage-
Name(meta annotation) -
Notes(meta annotation)
-
-
root package of module
-
PetOwnerModule(Spring@Configuration, aka module class)
-
-
In the
PetOwnerModule, the namespace and schema constants were changed:PetOwnerModule.java// ... public class PetOwnerModule implements ModuleWithFixtures { public static final String NAMESPACE = "petowner"; public static final String SCHEMA = "petowner"; // ... } -
in
application.properties, the "petowner" schema was also added to the causeway.persistence.schema.auto-create-schemas config property:application.propertiescauseway.persistence.schema.auto-create-schemas=petowner,simple,...
Resource classes were also copied.
In addition, the new Maven module was defined in the top-level pom.xml, and included in <modules>.
Confirm that the code still builds:
mvn install
However, if you run the app confirm that it is still showing just the original simpleapp module:
mvn -pl webapp spring-boot:run
We’ll fix this in the next exercise.
Ex 2.2: Configure the app to include the petowner module
We have our new petowner module, but we’re not actually using it; if we run the application we still see the original simpleapp module.
mvn -pl webapp spring-boot:run
In this exercise we’ll update the app to include petowners as well.
Solution
git checkout tags/3.2.0/02-02-configure-the-app-to-include-petowner
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
Rename the main class that acts as the entry point for the app (annotated with @@SpringBootApplication):
-
locate the
SimpleAppclass and rename toPetClinicApp -
Also update classes that reference this type (in particular, in
pom.xml).Your IDE can probably do this for you automatically.
The PetClinicApp main class references the AppManifest, a top-level Spring @Configuration which imports all the other classes.
-
Inspect the
AppManifestclass: most will beCausewayXxxModules -
note that it also imports the
ApplicationModule, which defines your own modules which make up the application
We want to update ApplicationModule to reference PetOwnerModule.
We need to change both Maven and then Spring:
-
in the
pom.xml(of thewebappmodule), add in a dependency to thepetownermodule:webapp/pom.xml<dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simpleapp-jpa-module-petowner</artifactId> </dependency> ... <dependencies>You might need to reimport or refresh so that your IDE rebuilds its classpath.
-
in the
ApplicationModule, update the@Importto reference thePetOwnerModule:Application.java@Configuration @Import({ PetOwnerModule.class, (1) SimpleModule.class, }) @ComponentScan public class ApplicationModule { }1 add this import
Run up the application using the IDE or from maven command line. Although everything looks good, in fact you won’t see any change. That’s because the simpleapp starter app includes the SecMan security module, for users, roles and permissions. We’ll fix this in the next exercise.
Ex 2.3: Add security role for the petowner module
In this exercise we’ll add a security role to grant access to our new petowner module, and we’ll make sure that our "sven" demo user is a member of this role.
Solution
git checkout tags/3.2.0/02-03-add-security-role-for-petowner
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
The starter app is configured to use an in-memory database. The SecMan roles and permissions are therefore set up each time the applicatoin is bootstrapped, using a fixture scripts. We’ll learn more about fixture scripts and their role in testing in a later exercise. For now, all that’s necessary to know is that we can use them to set up the roles.
-
Locate the
CustomRolesAndUsersfixture script class -
within it, copy the existing
SimpleModuleSuperuserRoleto create a similarPetOwnerModuleSuperuserRole:CustomRolesAndUsers.javaprivate static class PetOwnerModuleSuperuserRole extends AbstractRoleAndPermissionsFixtureScript { public static final String ROLE_NAME = "petowner-superuser"; public PetOwnerModuleSuperuserRole() { super(ROLE_NAME, "Permission to use everything in the 'petowner' module"); } @Override protected void execute(ExecutionContext executionContext) { newPermissions( ApplicationPermissionRule.ALLOW, ApplicationPermissionMode.CHANGING, Can.of(ApplicationFeatureId.newNamespace("petowner")) ); } } -
further down in the same class, update the fixture script for the "sven" end-user, and add them into our new role:
CustomRolesAndUsers.javaprivate static class SvenUser extends AbstractUserAndRolesFixtureScript { ... private static class RoleSupplier implements Supplier<Can<String>> { @Override public Can<String> get() { return Can.of( causewayConfiguration.getExtensions().getSecman().getSeed().getRegularUser().getRoleName(), // built-in stuff PetOwnerModuleSuperuserRole.ROLE_NAME, (1) SimpleModuleSuperuserRole.ROLE_NAME ); } @Inject CausewayConfiguration causewayConfiguration; } }1 reference new role. -
and in the
execute()method at top of the class, remember to include the call to the new fixture script:CustomRolesAndUsers.java@Override protected void execute(ExecutionContext executionContext) { executionContext.executeChildren(this, new SimpleModuleSuperuserRole(), new PetOwnerModuleSuperuserRole(), (1) new SvenUser()); }1 Remember to call the new module
Now run the application from the IDE or Maven:
-
The menubar should now show a new "Other" menu - these are the now-visible actions arising from the
PetOwnersdomain service. -
Create a new
PetOwnerusing .The resultant object will look identical in structure to a
SimpleObject, but you can confirm that it is indeed an instance of aPetOwnerby hovering over the title or icon and checking the tooltip.
The fact that the PetOwners domain service’s actions are displayed automatically is an important principle of the naked objects pattern as implemented by Apache Causeway.
Ex 2.4: Update menubar for PetOwners
Let’s now update the menu bar so that the actions to create PetOwners live under an appropriately named menu, "Pet Owners".
Solution
git checkout tags/3.2.0/02-04-update-menubar-for-petowners
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
-
Locate the
menubars.layout.xmlfile.You should find it in
src/main/resources(in thewebappmodule). -
Add in the following XML, under the
<mb3:primary>tag:menubars.layout.xml<mb3:primary> <mb3:menu> <mb3:named>Pet Owners</mb3:named> <mb3:section> <mb3:serviceAction objectType="petowner.PetOwners" id="create"/> <mb3:serviceAction objectType="petowner.PetOwners" id="findByName"/> <mb3:serviceAction objectType="petowner.PetOwners" id="findByNameLike"/> <mb3:serviceAction objectType="petowner.PetOwners" id="listAll"/> </mb3:section> </mb3:menu> ... </mb3:primary>The
objectTypeattribute corresponds to the@Namedvalue of thePetOwnersdomain service (its "logical type name"), while theidattribute matches the method name of each action.
Run the application; the menubar should be updated correctly.
Ex 2.5: Set up demo PetOwners
Fixture scripts are used for both prototyping and for integration testing, allowing us to quickly create demo data in the H2 in-memory database.
The persona pattern lets us define example data in an enum (the "what"), while a companion builder uses domain logic to set up the corresponding domain entities (the "how").
In this exercise we’ll modify the PetOwners_persona (copied from SimpleObjects_persona earlier) to create more realistic data.
We’ll also update the existing top-level DomainAppDemo fixture script, so that it will recreate our new PetOwners.
This will allow us to quickly create a whole set of PetOwner objects from the "Prototyping" menu.
Solution
git checkout tags/3.2.0/02-05-setup-demo-petowners
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
-
locate the
PetOwners_personaclass, and refactor the enum constants and values to create 10 realistic pet owner namesPetOwners_persona.java@RequiredArgsConstructor 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"); ... } -
create new
.pdffiles each pet owner, to store a "veterinary clinic service agreement" for each.The solution already provides these
.pdffiles, so you might want to borrow them. -
locate the
DomainAppDemoclass and update:DomainAppDemo.java@Override protected void execute(final ExecutionContext ec) { ec.executeChildren(this, moduleWithFixturesService.getTeardownFixture()); ec.executeChild(this, new PetOwner_persona.PersistAll()); (1) }1 change to set up PetOwners (rather thanSimpleObjects, as before)
Test that you can quickly create a whole set of PetOwner objects
from the "Prototyping" menu:
-
and select "Domain App Demo", or
-
.