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
Pet
s for saidPetOwner
-
delete a
PetOwner
and itsPet
s andVisit
s, so long as there are no unpaidVisit
s. -
book a
Pet
in for aVisit
-
delete a
PetOwner
and itsPet
s, unless there areVisit
s.
This tutorial has worked solutions for all of these
Some ideas for future extensions to the domain:
-
enter an
outcome
andcost
of aVisit
-
allow a
PetOwner
to pay for aVisit
-
find
Visit
s not yet paid and overdue -
delete a
PetOwner
and itsPet
s andVisit
s, so long as there are no unpaidVisit
s. -
add a previous/next buttons when paging through upcoming
Visit
s -
make "lastVisit" non-editable and maintained automatically from
Visit
s
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/2.1.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.petowner
package:-
SimpleObject
→PetOwner
(entity)plus related
.layout.xml
,.png
and.columnOrder.txt
files -
SimpleObjects
→PetOwners
(domain service) -
SimpleObjectRepository
repository service →PetOwnerRepository
-
SimpleModule
→PetOwnerModule
(module)
-
-
fixture
package:-
PetOwner_persona
(fixture script)
-
-
types
package-
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/2.1.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
SimpleApp
class 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
AppManifest
class: most will beCausewayXxxModule
s -
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 thewebapp
module), add in a dependency to thepetowner
module: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@Import
to 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/2.1.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
CustomRolesAndUsers
fixture script class -
within it, copy the existing
SimpleModuleSuperuserRole
to 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
PetOwners
domain service. -
Create a new
PetOwner
using .The resultant object will look identical in structure to a
SimpleObject
, but you can confirm that it is indeed an instance of aPetOwner
by 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 PetOwner
s live under an appropriately named menu, "Pet Owners".
Solution
git checkout tags/2.1.0/02-04-update-menubar-for-petowners
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
-
Locate the
menubars.layout.xml
file.You should find it in
src/main/resources
(in thewebapp
module). -
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
objectType
attribute corresponds to the@Named
value of thePetOwners
domain service (its "logical type name"), while theid
attribute 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 PetOwner
s.
This will allow us to quickly create a whole set of PetOwner
objects from the "Prototyping" menu.
Solution
git checkout tags/2.1.0/02-05-setup-demo-petowners
mvn clean install
mvn -pl webapp spring-boot:run
Tasks
-
locate the
PetOwners_persona
class, 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
.pdf
files each pet owner, to store a "veterinary clinic service agreement" for each.The solution already provides these
.pdf
files, so you might want to borrow them. -
locate the
DomainAppDemo
class 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 PetOwner
s (rather thanSimpleObject
s, as before)
Test that you can quickly create a whole set of PetOwner
objects
from the "Prototyping" menu:
-
and select "Domain App Demo", or
-
.