Business Rules
Introduction
When a domain object is rendered in the UI or the end-user interacts with the domain object through the UI, the framework applies a series of precondition business rules to each object member.
-
is the object member visible?
Members that are not visible are simply omitted from the page. If all the members in a fieldset (property group) are hidden, then the fieldset is not shown. If all the members in a tab are hidden, then the tab is not shown. If all the members of the object are hidden, then a "404" style message ("no such object") is returned to the user.
-
if the object member is visible, is the object member enabled?
An enabled property can be edited (otherwise it is read-only), and an enabled action can be invoked (otherwise it’s button is "greyed-out"). Note that collections are always read-only.
-
for enabled object members, if the user then interacts with that member, are the supplied values valid (can the user "do it").
For an editable property this means validating the proposed new value of the property. For an invokable action this means validating that arguments being used to invoke the action.
These can be summarised as "see it, use it, do it".
The framework provides a multitude of ways to implement these business rules.
The simplest mechanism is to just implement the business rules imperatively in the domain object, or in a mixin for the object.
A more sophisticated approach, useful for decoupling functionality, is using domain events. The domain event is emitted multiple times, for the various types of precondition checks (and if not vetoed, they are also emitted on the execution of the action itself).
Visibility
Property or Collection
To hide a property or collection:
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
private String status;
public boolean hideStatus() { (1)
// ...
}
1 | "hide" prefix, suffix matches up with the property name, and returns a boolean .
Returns true to hide the property, false means it is visible |
Object Action
An object action can be hidden similarly:
public Customer placeOrder(Product p, int quantity) {
// ...
}
public boolean hidePlaceOrder() { (1)
// ...
}
1 | "hide" prefix, suffix matches up with the action’s name, and takes no parameters.
Returns true to hide the action, false means it is visible |
Action Parameter
It is also possible to hide an action parameter, based on the value of some other earlier parameter:
Each hideNXxx()
method can declare parameters for the previous N-1 parameters, though it need not declare them all.
For example:
public class Order {
public Order shipTo(
boolean sameAsBillingAddress,
String addressLine1,
String addressLine2,
String addressCity,
String addressPostalCode,
String addressCountry
) {
// ...
}
public boolean hide1ShipTo(boolean sameAsBillingAddress) { (1)
return sameAsBillingAddress;
}
public boolean hide2ShipTo(boolean sameAsBillingAddress) {
return sameAsBillingAddress;
}
public boolean hide3ShipTo(boolean sameAsBillingAddress) {
return sameAsBillingAddress;
}
public boolean hide4ShipTo(boolean sameAsBillingAddress) {
return sameAsBillingAddress;
}
public boolean hide5ShipTo(boolean sameAsBillingAddress) {
return sameAsBillingAddress;
}
}
1 | "hide" prefix, N-th param, suffix matches action’s name, parameters are subset up to Nth, same type.
Returns true to hide the action parameter, false means it is visible |
In this case, the user can use the shipTo(…)
action to specify where to ship the Order
to.
However, if they check the first boolean parameter (ie, to ship the Order
to the billing address already held on file), then the remaining parameters will all be hidden.
If the action is implemented as a mixin, then the |
See also
-
hide…() method prefix
Usability
Property
By default properties cannot be edited, as specified by the causeway.applib.annotation.domain-object.editing configuration property.
This policy can be overridden for an individual property using the @Property#editing annotation; this makes sense for properties where there are no business rules governing their content, for example "notes" or "comments" fields.
For example:
import lombok.Getter;
import lombok.Setter;
@Property(editing = Editing.ENABLED) (1)
@Getter @Setter
private String notes;
1 | Allows the property to be enabled (even if property editing is disabled globally). |
It’s also possible to make all properties of a domain object editable using @DomainObject, though use cases for this are rare.
For very simply "CRUD-y" like applications, you might wish to switch the global policy, so that all properties are enabled by default, then selectively disable them.
To disable an otherwise property (so that it cannot be edited):
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
private String notes;
public String disableNotes() { (1)
return isArchived()
? "The notes of archived objects cannot be modified"
: null;
}
1 | "disable" prefix, suffix matches property name, returns String.
A non-null return string is taken as the reason why the property cannot be edited. |
To make all of the properties of a domain object unmodifiable, use:
@DomainObject(
editing=Editing.DISABLED
)
public class Customer {
// ...
}
Collections
Collections are always read-only; there is no notion of adding or removing from a collection implicitly. To add to or remove from a collection, an action should be defined.
Actions
To disable an object action:
public Customer placeOrder(Product p, int quantity) {
// ...
}
public String disablePlaceOrder() { (1)
// ...
}
1 | "disable" prefix, suffix matches action name, and takes no parameters.
A non-null return string is taken as the reason why the action cannot be edited. |
Action Parameters
It is also possible to disable an action parameter, so that it is disabled (greyed out) based on the value of earlier parameters. For example:
public Product categorize(
Category category,
Subcategory subcategory) {
// ...
}
public String disable1Categorize( (1)
Category category) {
return category == null || category.hasSubcategories()
? null
: "The selected category has no subcategories"
}
1 | "disable" prefix, N-th param, suffix matches action’s name, parameters are subset up to Nth, same type.
A non-null return string is taken as the reason why the action argument cannot be changed. |
If the action is implemented as a mixin, then the |
For more information
For more information, see disable…() section in the appropriate reference guide.
It’s also possible to return locale-specific strings, by returning TranslatableString instead of java.lang.String
.
Validity
Properties
Property edits can also be validated:
import lombok.Getter;
import lombok.Setter;
@Property(editing = Editing.ENABLED) (1)
@Getter @Setter
private String status;
public String validateStatus(String status) { (2)
// ...
}
1 | if required |
2 | "validate" prefix, suffix matches up with the property name, takes and returns a string. A non-null value is the reason why the |
Action parameters
Action arguments can be validated either singly or as a set. For example:
public Customer placeOrder(Product p, int quantity) {
// ...
}
public String validate0PlaceOrder(Product p) { (1)
// ...
}
public String validate1PlaceOrder(int quantity) { (2)
// ...
}
public String validatePlaceOrder(Product p, int quantity) { (3)
// ...
}
1 | "validate" prefix, N-th param; suffix matches action, param of correct type. Validates the 0th argument of the action, ie Product .A non- null return string is taken as the reason why the action argument is invalid. |
2 | similarly, N-th param. Validates the 1st argument of the action, ie int quantity |
3 | validates all the arguments of the action together. A non- null return string is taken as the reason why the action arguments taken together are invalid. |
The framework validates each argument separately; only if all are valid does it check all the arguments together.
If the action is implemented as a mixin, then the |
For more information
For more information, see the validate…() section in the appropriate reference guide. The reference guide also explains how to define validation declaratively, using the @Parameter#mustSatisfy() or @Property#mustSatisfy() attributes.
It’s also possible to return locale-specific strings, by returning TranslatableString instead of java.lang.String
.
Alternative Programming Models
If you define an action as a mixin, then there are two other ways in which the supporting methods for parameters can be specified:
-
using the name of the parameter (rather than its number)
-
using a Java record (or static data class) to capture all the argument values, rather than separate parameters.
You may find that these alternatives make for more maintainable code. Check out the mixin docs for more details