= Modularity

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Keeping applications modular is key to their long-term maintainability.
If every class potentially can depend on any other class, we'll end up with a "big ball of mud" that becomes almost impossible to change.

Instead, we need to ensure that the dependency graph between packages remains acyclic.
The framework provides two main tools:

* the first we've already seen: mixins.
+
These allow us to locate business logic in one module that "appears" to reside in another module.
Examples are the `visits` mixin collection and `bookVisit` mixin action that are both contributed by the `visits` module to the `Pet` entity in the `pets` module.

* the second is domain events.
+
These provide a way for one module to react to (or to veto) actions performed in logic in another module.

In this section we'll look at domain events.



[#exercise-7-1-refactor-petowners-delete-action]
== Ex 7.1: refactor PetOwner's delete action

Currently the `delete` action for `PetOwner` is broken: although the owner's ``Pet``s are automatically deleted when the `PetOwner` is itself deleted, if there are any ``Visit``s then the foreign key in the database will prevent deletion.

In one sense this is good: we probably don't want to allow a `PetOwner` and their ``Pet``s from being deleted if there are ``Visit``s in the database; they might not have paid!
However, we ought to have the business logic in the domain layer rather than rely on the database's foreign key.

In this exercise we'll use domain events to cascade delete or to veto the action respectively if there are related objects.



=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/07-01-delete-action-events
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* (optional) confirm that although it's not possible to delete a `PetOwner` if there are corresponding ``Visit``s, the error we get back is a database exception

* in `PetOwner`, modify the `delete` action so that it emits a specific domain event type.
+
[source,java]
.PetOwner.java
----
    public static class DeleteActionDomainEvent
            extends org.apache.causeway.applib.events.domain.ActionDomainEvent<PetOwner> {}     // <.>

    @Action(
            semantics = NON_IDEMPOTENT_ARE_YOU_SURE,
            domainEvent = DeleteActionDomainEvent.class                                         // <.>
    )
    @ActionLayout(
            describedAs = "Deletes this object from the persistent datastore")
    public void delete() { ... }
----
<.> specifies the domain event to emit when the action is called
<.> declares the action event (as a subclass of the framework's xref:refguide:applib-classes:events.adoc#domain-event-classes[ActionDomainEvent]).
+
NOTE: in fact, domain events are always emitted; but by default a generic `ActionDomainEvent` is used rather than a specific subclass.

* create a subscriber in the `visit` module to to veto an attempt to invoke the `PetOwner_delete` action if there are any visits
+
[source,java]
.PetOwner_delete_subscriber.java
----
@Component
public class PetOwner_delete_subscriber {

    @EventListener(PetOwner.DeleteActionDomainEvent.class)  // <.>
    void on(PetOwner.DeleteActionDomainEvent event) {       // <1>
        PetOwner subject = event.getSubject();              // <.>
        switch (event.getEventPhase()) {                    // <.>
            case HIDE:
                break;
            case DISABLE:                                   // <.>
                List<Visit> visits = visitRepository.findByPetOwner(subject);
                if (!visits.isEmpty()) {
                    event.veto("This owner has %d visit%s", visits.size(), (visits.size() == 1 ? "" : "s"));
                }
                break;
            case VALIDATE:
                break;
            case EXECUTING:
                break;
            case EXECUTED:
                break;
        }
    }

    @Inject VisitRepository visitRepository;
}
----
<.> subscribes to the event using Spring `@EventListener`
<.> returns the effective originator of the event.
This works for both regular actions and mixin actions
<.> the subscriber is called multiple times, for the various phases of the execution lifecycle; more on this below
<.> if there are any ``Visit``s for this pet owner, then veto the interaction.
In the user interface, the "delete" button will be disabled, that is greyed out.
The returned string is used as the tooltip to explain _why_ the button is disabled.

The event lifecycle allows subscribers to veto (in other words, specify preconditions) in three different ways:

* hide - will hide the action's button in the UI completely
* disable - will disable (grey) out the action's button
* validate - will prevent the button from being pressed.
this case applies when validating the action arguments.

If the action is not vetored, then the subscriber is also possible to perform additional steps:

* executing - the action is about to execute.
* executed - the action is just execute

For example, if the business rule had instead been to simply delete all ``Visit``s, then this could have been implemented in the "executing" phase.

TIP: it's also worth knowing that these events are fired for properties and collections as well as actions.
Therefore subscribers can substantially dictate what is accessible for any given domain object.
