Apache Causeway – GOING FURTHER

Dan Haywood
In this article we’ll continue our exploration of the Apache Causeway, a Spring Boot framework for domain-driven apps following the naked objects pattern. Our focus this time is the framework’s support for validation, on testing, and we’ll also look at some additional UI hints.

This article continues on from the previous article which introduced Apache Causeway. In this article we’ll continue our exploration of the framework’s features, focusing on validation, testing and some more UI hints. To help with our exploration, we’ll continue to use our version of the famous petclinic app. The prereqs are:

If you didn’t already, clone the repo, reset to the appropriate tag for this article, and then build the app:

git clone https://github.com/danhaywood/petclinic-javapro .
git checkout main
git reset --hard javapro/going-further/main

mvn clean install -T1C      # or mvnd clean install

Finally, load the code into your favourite Java IDE. You can run the app using PetClinicApp.

See it, Use it, Do it

As we learnt in the last article, in Causeway we use actions to implement behaviour on a domain object. These are public methods (e.g. updateName(...)), optionally with supporting methods. The action itself might do something simple (e.g. just updating the name of the PetOwner), but it could be something much more complicated (e.g. kicking off an invoice run).

Validating

Another type of business logic is preconditions; e.g. a customer can’t place an order if they have exceeded their credit limit; or e.g. we can’t apply a discount voucher to an order if the voucher has expired.

One type of precondition check is to validate action arguments. As a reminder, for the “add pet” action (the addPet(...) method), we had the following supporting method:

@MemberSupport
public String validate0AddPet(final String name) {
    if (getPets().stream().anyMatch(x -> Objects.equals(x.getName(), name))) {
        return "This owner already has a pet called '" + name + "'";
    }
    return null;
}

If this returns a non-null value, then this is rendered to the end-user as the reason that the end-user complete invoke the action:

Apache Causeway - Going Further - Validating the name of a pet

Disabling

Sometimes though the precondition depends not on the values of the action arguments, but instead on the intrinsic state of the system itself. For example, let’s suppose that there’s a rule that a PetOwner cannot have more than 3 Pets. To enforce our rule, we can write a disable supporting method:

@MemberSupport
public String disableAddPet() {
    final int maxPets = 3;
    return getPets().size() >= maxPets
            ? "This owner already has the maximum number of pets (" + maxPets + ")"
            : null;
}

Try adding this code and testing it out; you should find that the action button itself is disabled when the precondition fails:

Apache Causeway - Going Further - Disable add pet

TIP

Use git reset --hard javapro/going-further/disabling-action/solution to discard any local changes and switch to the solution

Hiding

Causeway has a third way to enforce preconditions, which is to hide the action itself.

@MemberSupport
public boolean hideAddPet() {
    final int maxPets = 3;
    return getPets().size() >= maxPets;
}

Try adding this code, you’ll find that if the precondition fails then the action button itself is no longer visible:

Apache Causeway - Going Further - Hide add pet

TIP

Use git reset --hard javapro/going-further/hiding-action/solution to discard any local changes and switch to the solution

Which of these to use? For my money, I would go with a “disable” precondition check: we want the end-user know that additional pets can be added in general, just not for this particular case. But the “hide” check also has its uses. Supposing that for inactive PetOwners we still wanted to retain an immutable archive of their Pet‘s history. In this case hiding the “add pet” action seems more appropriate, given that such PetOwners are intended to be strictly read-only.

We can also use supporting methods for (editable) properties. For example, in the previous article we added a what3words property. If we only wanted to show this if we didn’t have their emailAddress on file, we could write:

@MemberSupport
public boolean hideWhat3words() {
    return (getEmailAddress() != null);
}

TIP

Use git reset --hard javapro/going-further/hiding-property/solution to discard any local changes and switch to the solution

So the property is shown when there’s no emailAddress:

Apache Causeway - Going Further - With no email address

But the moment an email address is added, then the what3words property is not displayed:

Apache Causeway - Going Further - With email address

Summarizing, Causeway supports three different ways of enforcing preconditions, which you can remember as “see it, use it, do it”. In other words:

  • can you see the action’s button (or is it hidden?)
  • if visible, can you use the action (or is it disabled, grayed out)
  • if enabled, can you do (execute) the action (or are one or more of the action arguments invalid?)

The rapid feedback loop that Causeway enables is in part because, while prototyping, we don’t have to worry about persistence concerns; we use H2 as an in-memory database, and the ORM automatically creates the tables during bootstrap. You’ll also have noticed that we set up sample fixture data, let’s look at this next.

Fixtures

We specify which fixtures to be run in during bootstrap using causeway.testing.fixtures.initial-script property (in the application-dev.yml config file). It’s worth taking the time to think about the personas that can be used to guide the conversation with your expert users and then create corresponding fixture data; for example “Jamal” the pet owner (with just 1 pet) vs “Camila” the pet owner (with 3).

This fixture data is defined by extending the FixtureScript class, which defines a composite structure. In our case the class specified is DomainAppDemo:

public class DomainAppDemo extends FixtureScript {

    @Override
    protected void execute(final ExecutionContext ec) {
        ec.executeChildren(this, moduleWithFixturesService.getTeardownFixture());
        ec.executeChild(this, new Visit_persona.PersistAll());
    }

    @Inject ModuleWithFixturesService moduleWithFixturesService;
}

The interesting stuff is in Visit_persona class, however, which is an enum (defining the data) along with a corresponding builder class (the FixtureScript implementation) which knows how to call the app’s domain logic to actually create the data:

@RequiredArgsConstructor
public enum Visit_persona
implements Persona<Visit, Visit_persona.Builder> {

    JAMAL_VISITS(PetOwner_persona.JAMAL),
    CAMILA_VISITS(PetOwner_persona.CAMILA),
    ...

    private final PetOwner_persona petOwner_p;

    @Override
    public Builder builder() {
        return new Builder().setPersona(this);
    }
    ...
} 

As you can see, it’s common for one persona to reference another. The job of the visit persona is to create a random number of visits for the pets of each pet owner, but the pet owner’s own persona being executed as a prerequisite:

@RequiredArgsConstructor
public enum PetOwner_persona
implements Persona<PetOwner, PetOwner_persona.Builder> {

    JAMAL("Jamal Washington","jamal.pdf","J",new String[] {"Max"}),
    CAMILA("Camila González","camila.pdf",null,new String[] {"Mia", "Coco", "Bella"}),
    ...

    private final String name;
    private final String contentFileName;
    private final String knownAs;
    private final String[] petNames;
}

And here’s a fragment of the pet owner’s persona’s builder:

public static class Builder extends BuilderScriptWithResult<PetOwner> {

    @Getter @Setter private PetOwner_persona persona;

    @Override
    protected PetOwner buildResult(final ExecutionContext ec) {
        val petOwner = petOwners.create(persona.name, null, null, null);
        ...
    }
    ...
}

As well as being useful for prototyping, fixture scripts also play a key role in testing. Let’s explore that next.

Integration Testing

Apache Causeway is especially suitable for business applications with complicated domains, domain-driven applications in other words. And while prototyping is a key part of the process to help drive out the concepts and ubiquitous language, it’s equally important to ensure that the functionality you develop is properly tested, with both unit tests and integration tests.

There’s nothing particularly different about unit testing a Causeway app to any other modern Spring Boot application. Your tests don’t start a Spring context (unit tests aren’t annotated with @SpringBootTest), instead it’s your tests’ responsibility to instantiate the object under test and to mock out other collaborators. The typical tools here are JUnit, Mockito and AssertJ.

An integration test in comparison will be annotated with @SpringBootTest so that Spring itself wires up the application to be tested, including the relevant modules of the Causeway framework itself.

Causeway’s fixture support meanwhile is enabled by subclassing CausewayIntegrationTestAbstractWithFixtures, as we see in the petclinic app:

@SpringBootTest(classes = {WebAppIntegTestAbstract.TestApp.class})
@ActiveProfiles("test")
public abstract class WebAppIntegTestAbstract extends CausewayIntegrationTestAbstractWithFixtures {

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @EnableJpaRepositories
    @Import({
        CausewayModuleCoreRuntimeServices.class,
        CausewayModuleSecurityBypass.class,
        CausewayModulePersistenceJpaEclipselink.class,
        CausewayModuleTestingFixturesApplib.class,

        VisitModule.class
    })
    @PropertySources({
        @PropertySource(CausewayPresets.H2InMemory_withUniqueSchema),
        @PropertySource(CausewayPresets.UseLog4j2Test),
    })
    public static class TestApp {}
}

Note the reference to VisitModule.class, a Spring @Configuration class that identifies the domain services and objects of our application; the rest is standard Spring Boot boilerplate.

NOTE

We’ll explore modules and modularity in a later article.

Let’s now look at the definition of an integration test:

class Smoke_IntegTest extends WebAppIntegTestAbstract {

    @Inject PetOwners menu;
    @Inject PetOwnerRepository petOwnerRepository;
    @Inject TransactionService transactionService;

    @BeforeEach
    void fixture() {
        fixtureScripts.runPersonas(PetOwner_persona.values());
    }

    ...
}

Spring Boot takes care of injecting services into the test; some are from our domain (PetOwners, PetOwnerRepository) while others are from Causeway.

The FixtureScripts service (inherited from the superclass) is how we seed the database with our personas, in this case all 10 PetOwner personas. In our tests, we can then look up those objects and test their behaviour:

@Test
void update_name() {

    // given
    final PetOwner jamal = petOwnerRepository.findByName(PetOwner_persona.JAMAL.getName());

    // when
    final String newName = "Jamal Washington Jr.";
    wrap(jamal).updateName(newName);
    transactionService.flushTransaction();

    // then
    assertThat(wrap(jamal).getName()).isEqualTo(newName);
}

The above test is straightforward: given a PetOwner, we can change its name using the PetOwner#updateName(...) action. The next test though is more interesting, however:

@Test
void update_name_invalid() {
    // given
    final PetOwner jamal = petOwnerRepository.findByName(PetOwner_persona.JAMAL.getName());

    // when, then
    assertThatThrownBy(() -> wrap(jamal).updateName("Jamal Washington !!!"))
            .isInstanceOf(InvalidException.class)
            .hasMessage("Character '!' is not allowed.");
}

In this test we are checking the business rule that a PetOwner‘s name cannot have certain characters in it. And it’s that call to the inherited wrap(...) method that does the magic; Causeway creates a proxy for the domain object, and then invokes the action “as if” through the user interface; the proxy reaches into the Causeway metamodel and first checks all preconditions are met.

These preconditions might be defined imperatively (through a supporting hide/disable/validate method) or declaratively (as here, in the @Name annotation of the action’s parameter). They are all checked, and if any fail then a corresponding exception is thrown.


You might be wondering how the fixtures data that is set up for each test are deleted. The answer can be found in the CausewayIntegrationTestAbstractWithFixtures superclass:

@AfterEach
protected void tearDownFixtures() {
    fixtureScripts.run(moduleWithFixturesService.getTeardownFixture());
}

Briefly stated, this loops over all of the modules (Spring @Configurations) that make up the application, and runs their respective teardown’s, in the correct order. For example:

@Configuration
...
public class PetOwnerModule implements ModuleWithFixtures {
    ...
    @Override
    public FixtureScript getTeardownFixture() {
        return new TeardownFixtureJpaAbstract() {
            @Override
            protected void execute(ExecutionContext executionContext) {
                deleteFrom(Pet.class);
                deleteFrom(PetOwner.class);
            }
        };
    }
}

TIP

An alternative is to annotate the class using Spring Boot’s @Transactional annotation, which will run the entire integration test in a single transaction and have Spring roll it back at the end. There are pros and cons to each approach.

Let’s now change gears and learn a little more about how Causeway allows various UI hints that you can add to your application to give it a little polish.

Icons, Titles and other UI Hints

I remember the first time I encountered an early version of a naked objects framework, in an 6 hour workshop at a conference somewhere.

In the first exercise, we coded up a very simple Customer entity (or something similar), with just a couple of properties, which we then compiled and ran. I think it was for the second exercise, though, when we were asked to choose an icon for our object (rather than the default square, or whatever it was). This astonished me: surely such cosmetics come much later?

But no – it’s time worth spending to find a meaningful icon to represent the domain object, because it helps create a connection for the domain expert to whatever real world thing it is we’re trying to model in our application. These days, there are lots of great icon libraries, often with a free tier as well as paid for; see for example Icons8.

Defining the Icon

As you probably figured out already, the icon that is used in the UI is obtained by convention, in the same package as the domain class:

Apache Causeway - going further - domain object icon image

This icon is used for every instance of PetOwner.

In our petclinic application you might have noticed that different Pets have different icons, dependent on their species. In the application we can indeed see corresponding icon files:

Apache Causeway - going further - domain object Pet icons

The framework knows which icon to use through the iconName() method:

@ObjectSupport
public String iconName() {
    return getSpecies().name().toLowerCase();
}

As you can see, this method returns “budgerigar”, “cat” etc., which Causeway then concatenates to the class name in order to find the relevant icon file.

Defining a Title

Another very important UI hint is to specify a title for each domain object. This is rendered at the top of each page:

Apache Causeway - going further - domain object title

It’s not necessary for titles to be unique, but they should be unique “enough”; the point is for the end-user to be confident that they are looking at the object they intended to.

In terms of code, the title can be defined either declaratively using the @Title annotation or imperatively using the title() method. The Pet class uses the former, on its name property:

@PetName
@Title
@Column(name = "name", length = PetName.MAX_LEN, nullable = false)
@Getter @Setter
@PropertyLayout(fieldSetId = "identity", sequence = "2")
private String name;

The PetOwner class meanwhile defines its title using the title() method:

@ObjectSupport
public String title() {
    return getName() + (getKnownAs() != null ? " (" + getKnownAs() + ")" : "");
}

Viewing the Title in Tables

As you already know, when Causeway renders a collection of objects in a table, you navigate to each object by clicking the icon in the left-hand column.

Apache Causeway going further table icon navigation

If you wish, this first column can also include the title, which gives a larger (and perhaps more obvious) target for the end-user to click on. This is done through global configuration settings, as defined in application.yml:

causeway:
  viewer:
    wicket:
      max-title-length-in-standalone-tables: 40
      max-title-length-in-parented-tables: 40

If you do this then you will likely also want to suppress any properties (in this case name) from appearing as columns in the table; this can be done using the @PropertyLayout#hidden annotation:

@Name
@PropertyLayout(hidden = Where.ALL_TABLES)
@Column(length = Name.MAX_LEN, nullable = false, name = "name")
@Getter @Setter @ToString.Include
private String name;

You will probably also want to adjust the CSS, using application.css file that is included up automatically by the framework:

.collectionContentsAsAjaxTablePanel table.contents thead .title-column,
.collectionContentsAsAjaxTablePanel table.contents tbody .title-column {
    text-align: left;
    width: auto;
}

The end result of these changes is:

Apache Causeway - going further - wider icon/title column

TIP

Use git reset --hard javapro/going-further/icon-and-title-column/solution to discard any local changes and switch to the solution

Let’s finish up this article by looking at another nice bit of UI cosmetics, the prompt style.

Prompt Styles

Click on Pet Owners > Create action, and a prompt form will appear as a sidebar:

Apache Causeway - going further - sidebar prompt style

Stop the application and modify this action, adding the @ActionLayout annotation:

@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
@ActionLayout(promptStyle = PromptStyle.DIALOG_MODAL)
public PetOwner create( ... ) { ... }

This time we get a prompt as a modal dialog in the middle:

Apache Causeway - going further - modal dialog prompt style

TIP

Use git reset --hard javapro/going-further/prompt-style-service/solution to discard any local changes and switch to the solution

Causeway’s default for menu (domain service) actions is actually the modal dialog, but the petclinic app overrides in the application.yml file:

causeway:
  viewer:
    wicket:
      dialog-mode-for-menu: sidebar

With the actions of domain objects, the default is inline:

Apache Causeway - going further - domain object action prompt

If you want, you could override this as follows:

@Action(semantics = IDEMPOTENT)
@ActionLayout(
        ...
        promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner updateName(...) { ... }

resulting in the prompt being displayed in the sidebar:

Apache Causeway - going further - domain object dialog sidebar

TIP

Use git reset --hard javapro/going-further/prompt-style-object/solution to discard any local changes and switch to the solution

Wrapping Up

In this article we’ve taken a more in-depth look at how Causeway enforces preconditions for actions (hide/disable/validate, or see it/use it/do it). And because complicated domains demand comprehensive testing, we’ve learnt how the framework supports this with its rich fixture library (that can also be used for prototyping), and have learnt how Causeway builds on top of @SpringBootTest to allow integration tests to execute directly on the domain objects, avoiding the cost of executing (e.g. Selenium) through a UI.

We also learnt a bit more how to refine and customize the UI generated by Causeway, in terms of titles and icons, and action prompt styles.

I hope you’ve enjoyed reading this article and learning more the Apache Causeway framework; look out for further articles in upcoming issues.

Useful links:

Total
0
Shares
Previous Post

High Speed JPA

Next Post

LEARNING Java Automation the SOLID Way: A Beginners Guide to Learn Page Object Model with Java SOLID principles

Related Posts