Domain events

Whenever a domain object (or list of domain objects) is to be rendered, the framework fires off multiple domain events for every property, collection and action of the domain object. In the cases of the domain object’s properties, the events that are fired are:

  • hide phase: to check that the property is visible (has not been hidden)

  • disable phase: to check that the property is usable (has not been disabled)

  • validate phase: to check that the property’s arguments are valid (to modify/clear its value)

  • pre-execute phase: before the modification of the property

  • post-execute: after the modification of the property

Subscribers subscribe through the EventBusService and can influence each of these phases.

By default the event raised is PropertyDomainEvent.Default. For example:

import lombok.Getter;
import lombok.Setter;

public class ToDoItem {

    @Property()
    @Getter @Setter
    private LocalDate dueBy;

    // ...
}

The domainEvent() element allows a custom subclass to be emitted allowing more precise subscriptions (to those subclasses) to be defined instead.

For example:

import lombok.Getter;
import lombok.Setter;

public class ToDoItem {

    public static class DueByChangedEvent
        extends PropertyDomainEvent<ToDoItem, LocalDate> { }  (1)

    @Property(domainEvent=ToDoItem.DueByChangedEvent)
    @Getter @Setter
    private LocalDate dueBy;

    // ...
}
1 inherit from PropertyDomainEvent<T,P> where T is the type of the domain object being interacted with, and P is the type of the property (LocalDate in this example)

The benefit is that subscribers can be more targeted as to the events that they subscribe to.

The framework provides a no-arg constructor and will initialize the domain event using (non-API) setters rather than through the constructor. This substantially reduces the boilerplate required in subclasses because no explicit constructor is required.

Subscribers

Subscribers (which must be domain services) subscribe to events posted through the EventBusService.

Subscribers can be either coarse-grained (if they subscribe to the top-level event type):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {

    @EventListener(PropertyDomainEvent.class)
    public void on(PropertyDomainEvent ev) {
        // ...
    }
}

or can be fine-grained (by subscribing to specific event subtypes):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {

    @EventListener(ToDoItem.DueByChangedEvent.class)
    public void on(ToDoItem.DueByChangedEvent ev) {
        // ...
    }
}

The subscriber’s method is called (up to) 5 times:

  • whether to veto visibility (hide)

  • whether to veto usability (disable)

  • whether to veto execution (validate)

  • steps to perform prior to the property being modified

  • steps to perform after the property has been modified.

The subscriber can distinguish these by calling ev.getEventPhase(). Thus the general form is:

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {

    public void on(PropertyDomainEvent ev) {
        switch(ev.getEventPhase()) {

            case HIDE:                      (1)
                break;
            case DISABLE:                   (2)
                break;
            case VALIDATE:                  (3)
                break;

            case EXECUTING:
                break;
            case EXECUTED:
                break;
        }
    }
}
1 call ev.hide() or ev.veto("") to hide the property
2 call ev.disable("…​") or ev.veto("…​") to disable the property
3 call ev.invalidate("…​") or ev.veto("…​") if proposed new value for property is invalid

It is also possible to abort the transaction during the executing or executed phases by throwing an exception. If the exception is a subtype of RecoverableException then the exception will be rendered as a user-friendly warning (eg Growl/toast) rather than an error.

Default, Doop and Noop events

If the domainEvent() element is not explicitly specified (is left as its default value, PropertyDomainEvent.Default), then the framework will, by default, post an event.

If this is not required, then the causeway.applib.annotation.property.domain-event.post-for-default configuration property can be set to "false"; this will disable posting.

On the other hand, if the domainEvent has been explicitly specified to some subclass, then an event will be posted. The framework provides PropertyDomainEvent.Doop as such a subclass, so setting the domainEvent attribute to this class will ensure that the event to be posted, irrespective of the configuration property setting.

And, conversely, the framework also provides PropertyDomainEvent.Noop; if domainEvent attribute is set to this class, then no event will be posted.

Raising events programmatically

Normally events are only raised for interactions through the UI. However, events can be raised programmatically either by calling the EventBusService API directly, or by emulating the UI by wrapping the target object using the WrapperFactory domain service.