Prototyping
Fixture Scripts (for Owner)
The hello world archetype sets up an application that by default is configured to use an in-memory database. That means that every time we restart the application, we start with an empty database. After a while it gets pretty tedious continually having to create domain objects while testing.
While we could reconfigure the application to run with an external database (so that the data survives application restarts), it would then open up data migration issues when the data changes in an incompatible way as we continue to develop the application.
A better approach is to stick with the in-memory database, but to automate the setup of data, something we can do with Apache Isis' "Fixture Scripts" library.
There’s another benefit of sticking with the in-memory database and using fixtures scripts - it means we can reuse those same fixture scripts when writing integration tests. The fixture script captures the "given" of the test scenario, and the test itself concentrates on the "when" and the "then". |
Exercise
-
Implement the
RecreateOwners
fixture script:package domainapp.dom.impl; // ... imports omitted public class RecreateOwners extends FixtureScript { public RecreateOwners() { super(null, null, Discoverability.DISCOVERABLE); (1) } @Override protected void execute(final ExecutionContext ec) { isisJdoSupport.deleteAll(Owner.class); (2) ec.addResult(this, (3) this.owners.create("Smith", "John", null)); (4) ec.addResult(this, this.owners.create("Jones", "Mary", "+353 1 555 1234")); ec.addResult(this, this.owners.create("Hughes", "Fred", "07777 987654")); } @Inject Owners owners; (4) @Inject IsisJdoSupport isisJdoSupport; (2) }
1 make script available in the UI 2 use framework-provided IsisJdoSupport
domain service to delete any existing objects (making the script re-runnable). (Domain services are injected into fixture scripts, same as domain objects).3 makes the results available to the caller. When prototyping, these are rendered in the UI. This could be useful for Selenium E2E tests, for example. 4 Use the existing functionality from the injected Owners
domain service. -
Also, implement
PetClinicFixtureScriptSpecProvider
, which configures the framework to include all fixtures under the specified package:
package domainapp.dom.impl;
// ... imports omitted
@DomainService(nature = NatureOfService.DOMAIN)
public class PetClinicFixtureScriptSpecProvider
implements FixtureScriptsSpecificationProvider {
@Override
public FixtureScriptsSpecification getSpecification() {
return FixtureScriptsSpecification.builder(getClass())
.withRunScriptDefault(RecreateOwners.class)
.build();
}
}
This now provides us with a Run Fixture Script menu item under the Prototyping menu:
from which we can select the Order Fixture Script:
When invoked this shows the three Order
domain objects just created:
Run with a different manifest
While running the fixture scripts is easy to do, we can go one better by running the fixture script automatically when the application starts. To do that we need to understand a little more about how the framework bootstraps our app.
The key concept is that of an "app manifest", which allows us to identify the code modules that make up the class, along with various configuration properties. It also allows us to optionally specify a fixture script to run.
The default app manifest is PetClinicAppManifest
(we actually renamed this earlier from the name generated by the archetype):
public class PetClinicAppManifest extends AppManifestAbstract2 {
public static final Builder BUILDER = Builder
.forModule(new PetClinicModule()) (1)
.withConfigurationPropertiesFile( (2)
PetClinicAppManifest.class, "isis-non-changing.properties")
.withAuthMechanism("shiro"); (3)
public PetClinicAppManifest() {
super(BUILDER);
}
}
1 | load all the entities and domain services accessible under this package. The framework uses classpath scanning to discover these classes. |
2 | load all configuration properties in the isis-non-changing.properties file, relative to this manifest class.
This is in addition to any (typically environment-specific) configuration properties loaded from the various properties files (eg isis.properties ) to be found in WEB-INF directory. |
3 | use Apache Shiro for authentication. We’ll ignore this for now; suffice to say that Apache Isis can be integrated with various authentication providers, with Shiro being a very flexible out-of-the-box implementation. |
The framework knows to use this app manifest because it is specified in WEB-INF/isis.properties
file:
isis.appManifest=domainapp.application.PetClinicAppManifest
However, we can write an alternative manifest that will also run our fixture script, and then use this new manifest either by editing the isis.properties
file or (better), run the app using a system property.
Solution
git checkout tags/140-run-with-a-different-manifest
mvn clean package jetty:run
and run using this system property:
-Disis.appManifest=\
domainapp.application.PetClinicAppManifestWithFixture
When you run the application, the fixture should have run already and so there should be some Owner
instances.
Exercise
-
implement
PetClinicAppManifestWithFixture
:public class PetClinicAppManifestWithFixture extends AppManifestAbstract2 { public static final Builder BUILDER = PetClinicAppManifest.BUILDER (1) .withFixtureScripts(RecreateOwners.class); (2) public PetClinicAppManifestWithFixture() { super(BUILDER); } }
1 reuses the builder of the original manifest, but ... 2 ... also automatically run the RecreateOwners
fixture script on bootstrap -
run using this system property:
-Disis.appManifest=\ domainapp.application.PetClinicAppManifestWithFixture
for example:
When you run the application, the fixture should have run already and so there should be some Owner
instances.