Actions have four parts to them (responsibliities, I suppose):
- execution
- supporting methods for perform the validation etc
- target object that they act upon
- event publishing
Our programming model could provide several syntaxes to put these responsibilities in different places. It already supports two (standard actions, and mixins).To compare these currently provides two different syntaxes: standard, and mixins. This page recaps on those options and suggests several others. To compare the syntaxes, we'll use a concrete example:
...
- For disable1PlaceOrder(), we could also hide individual parameters similarly
- Instead of choices0PlaceOrder(), we could have used autoComplete0PlaceOrder(String)
UPDATE: we'll ignore the event publishing part ... in an earlier version of this paper we didn't see any value in trying to unify it with the other responsibilities.
Standard syntax
Standard syntax
The standard syntax uses regular methods on the target object. The standard syntax uses regular methods on the target object. Naming conventions are used to associate the action with supporting methods (default, choices, hide, disable and validate).
...
- Instead of
@Action
, the@Mixin(method="act")
could also be used, with additional annotations on theact(...)
method. I've chosen the version with the least boilerplate here. - Mixins are also used for derived properties or collections (ie query-only actions with no side-effects). These are specified using
@Property
or@Collection
- We now have two classes here: the mixin, and the domain event within.
...
- We now have two classes here: the mixin, and the domain event within.
Analysis
Actions have three parts (or responsibilities) to them:
- execution
- the target object that they act upon
- the set of parameters/arguments that are passed to the execution and to the supporting methods that perform validation etc.
(Actually, there's also event publishing, and an earlier version of this page also discussed that ... but we didn't see any point in changing how that worked).
The standard model and the mixin model have a quite different "feel" to them, though they only subtly change where these responsibilities reside: for the standard model, the target object is implicit (ie "this") whereas with mixins the target object is explicit (passed into the constructor). In other respects the programming models are the same.
Playing around with where these responsibilities live allow us to create a number of other programming models. The table below summarises and names these options::
target | behaviour | parameter values | Notes | |
---|---|---|---|---|
standard | implicit | Y | The target is implicit ("this"), and the set of parameter values (arguments) are only implicit in the signatures of the execute action and the supporting methods | |
mixins | Y | Y | The target is explicit, being the constructor of the mixin. | |
Parameters model | Y | Separate class that captures the set of parameters that are passed to the supporting methods | ||
Parameters on Act | Y | Minor variation | ||
Parameters Everywhere | Y | Another variation | ||
Mixins + Parameters | Y | Y | Y | Combines the concepts of a mixin along with a parameters model |
Targetless Mixins + Targeted Parameters | Y | Y | Y | Splits out state and behaviour |
Command handlers Commands | Y | Y | Y | Variation that splits behaviour into separate interfaces |
The rest of the page describes these options in more detail.
Parameters model syntax (proposed)
Per this thread on slack, we could introduce a Parameters object (in Java 14+, this might be a record) to bring together all of the parameters into a single object. This would make it easier to avoid issues with numbering etc.
...
Code Block |
---|
public class CustomerPlaceOrderChoicesParamHandler implements CommandChoicesParamHandler<PlaceOrderCommand> { public Collection<Object> choices(PlaceOrderCommand command, int paramNum) { ... } // bit ugly } |
To provide an autoComplete:
...
Code Block |
---|
public class CustomerPlaceOrderDefaultParamHandler implements CommandDefaultParamHandler<PlaceOrderCommand> { public Object defaultdefaultOf(PlaceOrderCommand command, int paramNum) { ... } // 'default' is a reserved word } |
Of course, there's nothing to prevent a single class from implementing all of these interfaces:
Code Block |
---|
@Action public class CustomerPlaceOrderHandler implements CommandActHandler<PlaceOrderCommand>, CommandHideActHandler<PlaceOrderCommand>, CommandHideParamHandler<PlaceOrderCommand>, CommandDisableActHandler<PlaceOrderCommand>, CommandDisableParamHandler<PlaceOrderCommand>, CommandValidateActHandler<PlaceOrderCommand>, CommandValidateParamHandler<PlaceOrderCommand>, CommandChoicesParamHandler<PlaceOrderCommand>, CommandAutoCompleteParamHandler<PlaceOrderCommand>, CommandDefaultParamHandler<PlaceOrderCommand> { public Customer act(PlaceOrderCommand command) { ... } public boolean hide(PlaceOrderCommand command) { ... } public String hide(PlaceOrderCommand command, int paramNum) { ... } public String disable(PlaceOrderCommand command) { ... } { public String disable(PlaceOrderCommand command, int paramNum) { ... } public String validate(PlaceOrderCommand command) { ... } public String validate(PlaceOrderCommand command, int paramNum) { ... } public Collection<Object> choices(PlaceOrderCommand command, int paramNum) { ... } public Collection<Object> autoComplete(PlaceOrderCommand command, int paramNum, String search) { ... } public Object defaultdefaultOf(PlaceOrderCommand command, int paramNum) { ... } } |
...