= 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 is mixins.
+
These allow us to locate busines 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 we haven't yet seen, but provide a way for one module to react to (or to veto) actions performed in logic in another module.

In this part of the tutorial 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 implemented as a mixin within the `Pet` package.
That's a nice place for that functionality, because it can delete any `Pet`s for the `PetOwner` if any exist.

However, we also have added `Visit`, which has the same issue: we cannot delete a `Pet` if there are associated ``Visit``s.
And, in fact, we 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!

In this exercise we will move the responsibility to delete an action back to `PetOwner`, and then use subscribers for both `Pet` and `Visit` to cascade delete or to veto the action respectively if there are related objects.



=== Solution

[source,bash]
----
git checkout tags/07-01-delete-action-events
mvn clean install
mvn -pl spring-boot:run
----

To test this out:

* try deleting a `PetOwner` where none of their ``Pet``s have any ``Visit``s; the action should succeed, and the `PetOwner` and the ``Pet``s should all be deleted.

* now book a `Visit` for a `Pet`, then navigate back to the parent `PetOwner` and attempt to delete it.
This time the action should be vetoed, because of that `Visit`.


=== Tasks

* in `PetOwner_delete` remove the code that deletes the ``Pet``s.
In its place, define a subclass of xref:refguide:applib-classes:events.adoc#domain-event-classes[ActionDomainEvent] as a nested class of the mixin, and reference in the xref:refguide:applib:index/annotation/Action.adoc#domainEvent[@Action#domainEvent] attribute.
+
[source,java]
.PetOwner_delete.java
----
@Action(
        domainEvent = PetOwner_delete.ActionEvent.class,            // <.>
        semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
        commandPublishing = Publishing.ENABLED,
        executionPublishing = Publishing.ENABLED
)
@ActionLayout(
        associateWith = "name", position = ActionLayout.Position.PANEL,
        describedAs = "Deletes this object from the persistent datastore")
@RequiredArgsConstructor
public class PetOwner_delete {

    public static class ActionEvent                                 // <.>
            extends ActionDomainEvent<PetOwner_delete>{}

    private final PetOwner petOwner;

    public void act() {
        repositoryService.remove(petOwner);
        return;
    }

    @Inject RepositoryService repositoryService;
}
----
<.> 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]).

* create a subscriber in the `pets` package to delete all ``Pet``s when the `PetOwner_delete` action is invoked:
+
[source,java]
.PetOwnerForPetsSubscriber.java
----
@Service
public class PetOwnerForPetsSubscriber {

    @EventListener(PetOwner_delete.ActionEvent.class)
    public void on(PetOwner_delete.ActionEvent ev) {
        switch(ev.getEventPhase()) {
            case EXECUTING:                                             // <.>
                PetOwner petOwner = ev.getSubject();                    // <.>
                List<Pet> pets = petRepository.findByPetOwner(petOwner);
                pets.forEach(repositoryService::remove);
                break;
        }
    }

    @Inject PetRepository petRepository;
    @Inject RepositoryService repositoryService;
}
----
<.> events are emitted at different phases.
The `EXECUTING` phase is fired before the delete action itself is fired, so is the ideal place for us to perform the cascade delete.
<.> is the mixee of the mixin that is emitting the event.

* create a subscriber in the `visits` module to veto the `PetOwner_delete` if there are any `Pet`s of the `PetOwner` with at least one `Visit`:
+
[source,java]
.PetOwnerForVisitsSubscriber.java
----
@Service
public class PetOwnerForVisitsSubscriber {

    @EventListener(PetOwner_delete.ActionEvent.class)
    public void on(PetOwner_delete.ActionEvent ev) {
        switch(ev.getEventPhase()) {
            case DISABLE:
                PetOwner petOwner = ev.getSubject();
                List<Pet> pets = petRepository.findByPetOwner(petOwner);
                for (Pet pet : pets) {
                    List<Visit> visits = visitRepository.findByPetOrderByVisitAtDesc(pet);
                    int numVisits = visits.size();
                    if(numVisits > 0) {
                        ev.disable(String.format("%s has %d visit%s",
                                titleService.titleOf(pet),
                                numVisits,
                                numVisits != 1 ? "s" : ""));
                    }
                }
                break;
        }
    }

    @Inject TitleService titleService;
    @Inject VisitRepository visitRepository;
    @Inject PetRepository petRepository;
}
----


=== Optional Exercise


Improve the implementation of `PetOwnerForVisitsSubscriber` so that it performs only a single database query to find if there are any ``Visit`` for the `PetOwner`.



