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:
...
Code Block |
---|
public class Customer { // target @Action public staticCustomer class PlaceOderEvent extends ActionDomainEvent<Customer> {} placeOrder(Product p, int quantity) { ... } // action execution public boolean hidePlaceOrder() { ... } @Action(domainEvent = PlaceOrderEvent.class) // eventsupporting publishingmethods public CustomerString placeOrder(Product p, int quantitydisablePlaceOrder() { ... } public String disable1PlaceOrder(Product p) { ... } public Collection<Product> choices0PlaceOrder() { // action execution ... } public booleanProduct hidePlaceOrderdefault0PlaceOrder() { ... } public int default1PlaceOrder() { ... } public String // supporting methods public String disablePlaceOrder(validate1PlaceOrder(int quantity) { ... } public String disable1PlaceOrdervalidatePlaceOrder(Product p), { ... } public Collection<Product> choices0PlaceOrder(int quantity) { ... } public Product default0PlaceOrder() { ... } public int default1PlaceOrder() { ... } public String validate1PlaceOrder(int quantity) { ... } public String validatePlaceOrder(Product p, int quantity) { ... } } |
...
} |
Mixins syntax
Mixins Mixins change the target, by allowing this set of methods to be moved to a different object. In other words, the target responsibility changes.
...
Code Block |
---|
@Action public class Customer_placeOrder() { private final Customer target; // target public Customer_placeOrder(Customer target) { ... } public staticCustomer class PlaceOrderEvent extends ActionDomainEvent<Customer> {} act(Product p, int quantity) { ... } // action execution public boolean hideAct() { ... } @Action(domainEvent = PlaceOrderEvent.class) // eventsupporting publishingmethods public CustomerString act(Product p, int quantitydisableAct() { ... } public String disable1Act(Product p) { ... } public Collection<Product> choices0Act() { ... } public Product default0Act() // action execution { ... } public booleanint hideActdefault1Act() { ... } public String // supporting methods public String disableAct(validate1Act(int quantity) { ... } public String disable1ActvalidateAct(Product p), { ... } public Collection<Product> choices0Act(int quantity) { ... } public Product default0Act() { ... } public int default1Act() { ...} public String validate1Act(int quantity) { ... } public String validateAct(Product p, int quantity) { ... } } |
Notes:
- 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.
Parameters 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.
This syntax changes the way in which supporting methods are associated back to the main execution method.
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.
This syntax changes the way in which supporting methods are associated back to the main execution method.
Code Block | ||
---|---|---|
| ||
public class Customer { // target
@Value @Accessors(fluent = true)
public class PlaceOrderParameters { // to assist supporting methods
Product product;
int quantity;
}
@Action
public Customer placeOrder(Product p, int quantity) { ... } // execution
public boolean hidePlaceOrder() { ... } // supporting methods use PlaceOrderParameters
public String disablePlaceOrder() { ... }
public String disable1PlaceOrder(PlaceOrderParameters params) { ... }
public Collection<Product> choices0PlaceOrder() { ... } | ||
Code Block | ||
| ||
public class Customer { public Product default0PlaceOrder() { ... } public int default1PlaceOrder() { ... } public String validate1PlaceOrder(PlaceOrderParameters params) { ... } public String validatePlaceOrder(PlaceOrderParameters params) { ... } } |
Notes:
- The
@Value
@Accessors(fluent=true)
allows us to use a syntax that is very similar to Java 14 records. - There is some duplication here: the list of the parameter types appears both in the
placeOrder(...)
method, as well as in thePlacerOrdersParameters
class.
The above would also be supported with mixins:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; // target @Value @Accessors(fluent = true) public class PlaceOrderParameters { // to assist supporting methods Product product; // target int quantity; public Customer_placeOrder(Customer target) { ... } public@Value static@Accessors(fluent class= PlacerOrderEventtrue) extends ActionDomainEvent<Customer> {} public static class PlaceOrderParameters { @Action(domainEvent = PlaceOrderEvent.class) // to assist supporting methods Product product; int quantity; // event publishing} public Customer placeOrderact(Product p, int quantity) { ... } // execution // execution public boolean hidePlaceOrderhideAct() { ... } // supporting methods use PlaceOrderParametersmethods public String disablePlaceOrderdisableAct() { ... } public String disable1PlaceOrderdisable1Act(PlaceOrderParameters params) { ... } public Collection<Product> choices0PlaceOrderchoices0Act() { ... } public Product default0PlaceOrderdefault0Act() { ... } public int default1PlaceOrderdefault1Act() { ... } public String validate1PlaceOrdervalidate1Act(PlaceOrderParameters params) { ... } public String validatePlaceOrdervalidateAct(PlaceOrderParameters params) { ... } } |
Notes:
- The
@Value
@Accessors(fluent=true)
allows us to use a syntax that is very similar to Java 14 records. - There is some duplication we now have three classes here: the list of the parameter types appears both in the
placeOrder(...)
method, as well as in thePlacerOrdersParameters
class.mixin, the domain event, and the parameters object.
Parameters on Act syntax (proposed)
This is a variant of the previous, but uses the parameters class in the action as wellThe above would also be supported with mixins:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; // target public Customer_placeOrder(Customer target) { ... }// target @Value @Accessors(fluent = true) public static class PlaceOrderParameters { // to assist supporting methods @Parameter() @MemberOrder(1) Product product; @Parameter() @MemberOrder(2) int quantity; } @Action public staticCustomer class PlaceOrderEvent extends ActionDomainEvent<Customer> {placeOrder(PlaceOrderParameters params) { ... } // execution public boolean hidePlaceOrder() { ... } @Action(domainEvent = PlaceOrderEvent.class) // supporting methods use PlaceOrderParameters public String disablePlaceOrder() // event publishing{ ... } public CustomerString actdisable1PlaceOrder(Product p, int quantityPlaceOrderParameters params) { ... } public Collection<Product> choices0PlaceOrder() { ... } // execution public booleanProduct hideActdefault0PlaceOrder() { ... } public int default1PlaceOrder() { ... } public String validate1PlaceOrder(PlaceOrderParameters params) { ... } public String validatePlaceOrder(PlaceOrderParameters params) { ... } } |
Notes:
- this removes the duplication between the
placeOrder(...)
parameter list and the list of members inPlaceOrderParameters
class. - the
@Parameter
andsyntax would be required by the framework to identify PlaceOrderParameters as a container of parameters (as opposed to a reference object or custom value type)@MemberOrder
As a mixin, this becomes:
Code Block |
---|
@Action public class // supporting methodsCustomer_placeOrder { publicprivate Stringfinal disableAct() { ... } public String disable1Act(PlaceOrderParameters params) { ... } Customer target; public Collection<Product> choices0Act() { ... } public Product default0Act() { ... } public int default1Act() { ... }// target public String validate1Act(PlaceOrderParameters paramsCustomer_placeOrder(Customer target) { ... } public@Value String validateAct(PlaceOrderParameters params) { ... } } |
Notes:
- we now have three classes here: the mixin, the domain event, and the parameters object.
Parameters on Act syntax (proposed)
This is a variant of the previous, but uses the parameters class in the action as well:
Code Block |
---|
public class Customer {@Accessors(fluent = true) public static class PlaceOrderParameters { // to assist supporting methods @Parameter() Product product; // target @Parameter() int quantity; } @Action public @ValueCustomer @Accessors(fluent = true)act(PlaceOrderParameters params) { ... } public class PlaceOrderParameters {// execution public boolean hideAct() { ... } // to assist supporting methods // @Parameter() @MemberOrder(1)supporting methods public String disableAct() { Product product;... } public String @Parameterdisable1Act()PlaceOrderParameters @MemberOrder(2params) { ... } public int quantity; } Collection<Product> choices0Act() { ... } public static class PlacerOrderEvent extends ActionDomainEvent<Customer> {} public Product default0Act() { ... } public int default1Act() { ... } public String validate1Act(PlaceOrderParameters params) { ... } public String validateAct(PlaceOrderParameters params) { ... } } |
Notes:
- we still have three classes here (mixin, parameters and domain event), but we have removed the duplication between the
act(...)
parameter list and the list of members ofPlaceOrderParameters
class
Parameters everywhere syntax (proposed)
The previous syntax only passes in parameters to some of the supporting methods. For consistency, we could imagine it being passed in always.
Just focusing on the mixin syntax, this would become:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; @Action(domainEvent = PlaceOrderEvent.class) // event publishing public Customer placeOrder(PlaceOrderParameters params) { ... } // execution public boolean hidePlaceOrder() { ... } // supporting methods use PlaceOrderParameterstarget public String disablePlaceOrder(Customer_placeOrder(Customer target) { ... } public@Value String disable1PlaceOrder(PlaceOrderParameters params) { ... } @Accessors(fluent = true) public Collection<Product> choices0PlaceOrder() { ... } public static class PlaceOrderParameters { public Product default0PlaceOrder() { ... } public int default1PlaceOrder() { ... } public String validate1PlaceOrder(PlaceOrderParameters params) { ... } public String validatePlaceOrder(PlaceOrderParameters params) { ... } } |
Notes:
- this removes the duplication between the
placeOrder(...)
parameter list and the list of members inPlaceOrderParameters
class. - the
@Parameter
and@MemberOrder
syntax would be required by the framework to identify PlaceOrderParameters as a container of parameters (as opposed to a reference object or custom value type)
As a mixin, this becomes:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target;// to assist supporting methods @Parameter() Product product; @Parameter() int quantity; } @Action public Customer act(PlaceOrderParameters params) { ... } // targetexecution public boolean Customer_placeOrderhideAct(CustomerPlaceOrderParameters targetparams) { ... } @Value @Accessors(fluent = true) // supporting methods public staticString class PlaceOrderParametersdisableAct(PlaceOrderParameters params) { ... } public String disable1Act(PlaceOrderParameters params) { ... } public Collection<Product> choices0Act(PlaceOrderParameters params) { ... } // to assist supporting methods public Product default0Act(PlaceOrderParameters params) { @Parameter() @MemberOrder(1) ... } public int default1Act(PlaceOrderParameters params) { Product product;... } public String @Parametervalidate1Act()PlaceOrderParameters @MemberOrder(2params) { ... } public String validateAct(PlaceOrderParameters params) { ... } } |
Discussion
With the parameters object passed in everywhere, I could see myself starting to move functionality onto that object. So as an idiom, we might see the following sort of code (in a mixin):
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target;int quantity; } public static class PlaceOrderEvent extends ActionDomainEvent<Customer> {} @Action(domainEvent = PlaceOrderEvent.class) public Customer_placeOrder(Customer target) { ... } @Value @Accessors(fluent = true) // event publishing public static Customerclass act(PlaceOrderParameters params) { ... } // execution public boolean hideAct() { ... } // see below. // supporting methods public String disableAct() { ... } public Stringstatic disable1Act(PlaceOrderParameters params) { ... } class PlaceOrderEvent extends ActionDomainEvent<Customer> {} public Collection<Product> choices0Act() { ... } public Product default0Act() { ... } public int default1Act() { ... }@Action public StringCustomer validate1Actact(PlaceOrderParameters params) { return ... }params.act(this); } public Stringboolean validateActhideAct(PlaceOrderParameters params) { ... } } |
Notes:
- we still have three classes here (mixin, parameters and domain event), but we have removed the duplication between the
act(...)
parameter list and the list of members ofPlaceOrderParameters
class
Parameters everywhere syntax (proposed)
The previous syntax only passes in parameters to some of the supporting methods. For consistency, we could imagine it being passed in always.
Just focusing on the mixin syntax, this would become:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; return params.hide(this); } public String disableAct(PlaceOrderParameters params) { return params.disable(this); } public String disable1Act(PlaceOrderParameters params) { return params.disable1(this); } public Collection<Product> choices0Act(PlaceOrderParameters params) { return params.choices0(this); } // target public Product Customer_placeOrderdefault0Act(CustomerPlaceOrderParameters targetparams) { return ...params.default0(this); } @Valuepublic int @Accessorsdefault1Act(fluentPlaceOrderParameters = trueparams) { return params.default1(this); } public staticString class PlaceOrderParametersvalidate1Act(PlaceOrderParameters params) { return params.validate1(this); } public String validateAct(PlaceOrderParameters params) { params.validate(this); } } |
which would then beef up the parameters object:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; // to assist supporting methods @Parameter() @MemberOrder(1) Product product; @Parameter() @MemberOrder(2)// target ... int quantity; } @Value @Accessors(fluent = true) public static class PlaceOrderEvent extends ActionDomainEvent<Customer> {} public static class PlaceOrderParameters { @Action(domainEvent = PlaceOrderEvent.class) @Parameter() Product product; @Parameter() // eventint publishingquantity; public Customer act(PlaceOrderParametersCustomer paramscustomer) { ... } // execution public boolean hideAct(PlaceOrderParametersCustomer paramscustomer) { ... } // supporting methods public String disableAct(PlaceOrderParametersCustomer paramscustomer) { ... } public String disable1Act(PlaceOrderParametersCustomer paramscustomer) { ... } public Collection<Product> choices0Act(PlaceOrderParametersCustomer paramscustomer) { ... } public Product default0Act(PlaceOrderParametersCustomer paramscustomer) { ... } public int default1Act(PlaceOrderParametersCustomer paramscustomer) { ... } public String validate1Act(PlaceOrderParametersCustomer paramscustomer) { ... } public String validateAct(PlaceOrderParametersCustomer paramscustomer) { ... } } |
Discussion
With the parameters object passed in everywhere, I could see myself starting to move functionality onto that object. So as an idiom, we might see the following sort of code (in a mixin):
Code Block |
---|
@Action public class Customer_placeOrder {} } private... final Customer target; @Action public Customer act(PlaceOrderParameters params) { return params.act(this); } // remainder is just boilerplate public boolean Customer_placeOrderhideAct(CustomerPlaceOrderParameters targetparams) { return ... } @Value @Accessors(fluent = true) params.hide(this); } public static class PlaceOrderParameters { ... } public String disableAct(PlaceOrderParameters params) { return params.disable(this); } //public see below. String disable1Act(PlaceOrderParameters params) { return params.disable1(this); } public Collection<Product> choices0Act(PlaceOrderParameters params) { return params.choices0(this); } public staticProduct class PlaceOrderEvent extends ActionDomainEvent<Customer>default0Act(PlaceOrderParameters params) {} return params.default0(this); } public int default1Act(PlaceOrderParameters params) { return params.default1(this); } public String validate1Act(PlaceOrderParameters params) { return params.validate1(this); } public String validateAct(PlaceOrderParameters params) { params.validate(this); } } |
Notes:
- the target is still outside of the parameters object
- Event publishing also outside
- Everything else has moved inside the parameters object
- This implies that we would need dependency injection for the parameters object
- The rest of the code in the mixin is just boilerplate. It's possible that the Lombok @Delegate annotation might be used to remove some of this boilerplate, didn't investigate further.
Mixins and Parameters combined (proposed)
The previous section describes an idiom to work within the new Parameter object programming model. But the next step along the journey would be to formally recognise this pattern. This would amount to collapsing the mixin concept and the parameters concept into the same thing. Said another way, mixins start to become stateful, keeping track of the parameter argument values as well as the target object:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; @Action(domainEvent = PlaceOrderEvent.class) public Customer act(PlaceOrderParameters params) { return params.act(this); } public boolean hideAct(PlaceOrderParameters params) { return params.hide(this); } public String disableAct(PlaceOrderParameters params) { return params.disable(this); } public String disable1Act(PlaceOrderParameters params) { return params.disable1(this); } // target public Collection<Product> choices0Act(PlaceOrderParameters params) { return params.choices0(this); } public Product default0Act(PlaceOrderParameters params) { return params.default0(this); } ... @Parameter() @MemberOrder(1) public int default1Act(PlaceOrderParameters params) { return params.default1(this); } public String validate1Act(PlaceOrderParameters params) { return params.validate1(this); } public String validateAct(PlaceOrderParameters params) { params.validate(this); } } |
which would then beef up the parameters object:
Code Block |
---|
@Action public class Customer_placeOrder { private final Customer target; // supporting methods support Product product; @Parameter() @MemberOrder(2) int quantity; @Action public Customer act() { ... } // target ... @Value @Accessors(fluent = true) public static class PlaceOrderParameters { // execution public boolean hideAct() { ... } @Parameter() @MemberOrder(1) Product product; // @Parameter() @MemberOrder(2)supporting methods public String disableAct() { int quantity; ... } public CustomerString actdisable1Act(Customer customer) { ... } public Collection<Product> choices0Act() { ... } // execution public booleanProduct hideActdefault0Act(Customer customer) { ... } public int default1Act() { ... } public String validate1Act() { // supporting methods ... } public String disableActvalidateAct(Customer customer) { ... } public String disable1Act(Customer customer) { ... } public Collection<Product> choices0Act(Customer customer) { ... } public Product default0Act(Customer customer) { ... } public int default1Act(Customer customer) { ... } public String validate1Act(Customer customer) { ... } public String validateAct(Customer customer) { ...} } ... public static class PlaceOrderEvent extends ActionDomainEvent<Customer> {}} |
Notes:
- here the supporting methods would simply read from the fields of the mixin that represent the parameters of the mixin itself.
- the domain event class is still separate
@MemberOrder
is required because the JVM does not guarantee the order in the bytecode is the same as in the source file.
Target-less Mixins + Targeted Parameters
Traditionally mixins hold all of the behaviour and a little bit of the state - namely the target object. Meanwhile parameters hold the rest of the state, but without the target.
Another way to divide the responsibilities would be to move the target from the mixin, and add it into the parameters object. In other words, the former would just be the behaviour, the latter would be just the state.
Thus we have an extended parameters object, that also takes the target:
Code Block |
---|
@Value @Accessors(fluent = true) public class PlaceOrderParameters { @Target // a new annotation Customer customer; @Parameter() Product product; @Parameter() int quantity; } |
Meanwhile the mixin provides just the behaviour, of both the action and also the various supporting methods. The supporting methods all need to take the PlaceOrderParameters, because it now contains the target, at least
Code Block |
---|
@Action public class Customer_placeOrder { @Action(domainEvent = PlaceOrderEvent.class) // event publishing public Customer act(PlaceOrderParameters params) { return params.act(this);... } // remainder is just// boilerplateexecution public boolean hideAct(PlaceOrderParameters params) { return params.hide(this); } ... } // supporting methods public String disableAct(PlaceOrderParameters params) { return params.disable(this);... } public String disable1Act(PlaceOrderParameters params) { return params.disable1(this);... } public Collection<Product> choices0Act(PlaceOrderParameters params) { return params.choices0(this);... } public Product default0Act(PlaceOrderParameters params) { return params.default0(this); } public int default1Act(PlaceOrderParameters params) { return params.default1(this); } public String validate1Act(PlaceOrderParameters params) { return params.validate1(this);.. } public Stringint validateActdefault1Act(PlaceOrderParameters params) { params.validate(this); } } |
Notes:
- the target is still outside of the parameters object
- Event publishing also outside
- Everything else has moved inside the parameters object
- This implies that we would need dependency injection for the parameters object
- The rest of the code in the mixin is just boilerplate. It's possible that the Lombok @Delegate annotation might be used to remove some of this boilerplate, didn't investigate further.
Mixins and Parameters combined (proposed)
The previous section describes an idiom to work within the new Parameter object programming model. But the next step along the journey would be to formally recognise this pattern. This would amount to collapsing the mixin concept and the parameters concept into the same thing. Said another way, mixins start to become stateful, keeping track of the parameter argument values as well as the target object:
... }
public String validate1Act(PlaceOrderParameters params) { ... }
public String validateAct(PlaceOrderParameters params) { ... }
} |
Command Handlers
Building on the previous example, having split up the behaviour from the state completely, we realise that there's no need to keep all of the methods of the mixin together.
We could rename the "parameters object" as a command:
Code Block |
---|
@Value @Accessors(fluent = true)
public class PlaceOrderCommand {
@Target |
Code Block |
@Action public class Customer_placeOrder { private final Customer target; Customer customer; @Parameter() Product product; @Parameter() int quantity; } |
and then we could have a number of handlers, for example for the execution:
Code Block |
---|
public class CustomerPlaceOrderHandler { @Action //public target Customer act(PlaceOrderCommand command) { ... } @Parameter() @MemberOrder(1) // execution - infer the name of the action from the type } |
and for the preconditions (no need for the "Act" suffix):
Code Block |
---|
public class CustomerPlaceOrderValidationHandler { public boolean hide(PlaceOrderCommand command) { ... } // supporting methods support Product product; @Parameter() @MemberOrder(2) // intsupporting quantity; methods public staticString class PlaceOrderEvent extends ActionDomainEvent<Customer>disable(PlaceOrderCommand command) {} ... } public String disable1(PlaceOrderCommand command) { ... } public String validate1(PlaceOrderCommand command) { ... } public String validate(PlaceOrderCommand command) { ... } } |
and for the UI hints:
Code Block |
---|
public class CustomerPlaceOrderUiHintsHandler { @Action(domainEvent = PlaceOrderEvent.class) public Collection<Product> choices0(PlaceOrderCommand command) { ... } public Product default0(PlaceOrderCommand command) { // event publishing... } public Customerint actdefault1(PlaceOrderCommand command) { ... } } |
Command Handler Contracts
Command handlers in other frameworks often have a single method, called something like "apply" or "accept". We can't quite get there because we not only need to execute the action, but also do the validation and UI hint stuff.
We could though introduce some API to define this contract.
Code Block |
---|
public class CustomerPlaceOrderHandler implements CommandActHandler<PlaceOrderCommand> { public Customer act(PlaceOrderCommand command) { ... } } |
To hide entire action:
Code Block |
---|
public class CustomerPlaceOrderHideActHandler implements CommandHideActHandler<PlaceOrderCommand> { public boolean hide(PlaceOrderCommand command) { ... } // execution public boolean hideAct() { ... } } |
To hide individual parameters:
Code Block |
---|
public class CustomerPlaceOrderHideParamHandler implements CommandHideParamHandler<PlaceOrderCommand> { public String hide(PlaceOrderCommand command, int paramNum) { ... } } |
to disable entire action:
Code Block |
---|
public class CustomerPlaceOrderDisableActHandler implements CommandDisableActHandler<PlaceOrderCommand> { public String disable(PlaceOrderCommand command) { ... } } |
To disable individual parameters:
Code Block |
---|
public class CustomerPlaceOrderDisableHandler //implements supportingCommandDisableParamHandler<PlaceOrderCommand> methods{ public String disableAct(disable(PlaceOrderCommand command, int paramNum) { ... } } |
To validate entire parameter set:
Code Block |
---|
public class CustomerPlaceOrderValidateActHandler implements CommandValidateActHandler<PlaceOrderCommand> { public String disable1Actvalidate(PlaceOrderCommand command) { ... } } |
To validate individual parameters:
Code Block |
---|
public class CustomerPlaceOrderValidateParamHandler implements CommandValidateParamHandler<PlaceOrderCommand> { public Collection<Product>String choices0Act(validate(PlaceOrderCommand command, int paramNum) { ... } } |
And we keep going for the UI hints.
To return choices:
Code Block |
---|
public class CustomerPlaceOrderChoicesParamHandler implements CommandChoicesParamHandler<PlaceOrderCommand> { public Collection<Object> choices(PlaceOrderCommand command, public Product default0Act(int paramNum) { ... } public int default1Act() { ... } public String validate1Act() { ... } public String validateAct() { ... }// bit ugly } |
Notes:
- here the supporting methods would simply read from the fields of the mixin that represent the parameters of the mixin itself.
- the domain event class is still separate
Mixin and Parameters and Domain Event combined (proposed - but a step too far?)
A further possible unification is to make the mixin also act as the domain event. I am not sure this is a good idea... my mental model is that the mixin acts as the "command", representing an intention to perform something, where as the event is a record of a fact that occurred. But maybe this is too theoretical, and actually it would make sense to combine the, If so, it would look something like:
To provide an autoComplete:
Code Block |
---|
public class CustomerPlaceOrderAutoCompleteHandler implements CommandAutoCompleteParamHandler<PlaceOrderCommand> {
public Collection<Object> autoComplete(PlaceOrderCommand command, int paramNum, String search) { ... } // bit ugly
} |
To return defaults:
Code Block |
---|
public class CustomerPlaceOrderDefaultParamHandler implements CommandDefaultParamHandler<PlaceOrderCommand> {
public Object defaultOf(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
| ||
Code Block | ||
| ||
@Action public class Customer_placeOrder extends ActionDomainEvent<Customer> { // ??? this looks very odd private final Customer target; implements CommandActHandler<PlaceOrderCommand>, // target ... CommandHideActHandler<PlaceOrderCommand>, @Parameter() @MemberOrder(1) CommandHideParamHandler<PlaceOrderCommand>, // supporting methods support ProductCommandDisableActHandler<PlaceOrderCommand>, product; @Parameter() @MemberOrder(2) int quantity; @Action(domainEvent = Customer_placeOrder.class) CommandDisableParamHandler<PlaceOrderCommand>, CommandValidateActHandler<PlaceOrderCommand>, // ??? I wonder if this is valid syntacticallyCommandValidateParamHandler<PlaceOrderCommand>, public Customer act() { ... } CommandChoicesParamHandler<PlaceOrderCommand>, // execution CommandAutoCompleteParamHandler<PlaceOrderCommand>, ??? these methods would be available toCommandDefaultParamHandler<PlaceOrderCommand> the subscriber{ public booleanCustomer hideActact(PlaceOrderCommand command) { ... } public boolean hide(PlaceOrderCommand command) { ... } // supporting methods ??? these methods would be available to the subscriber public String hide(PlaceOrderCommand publiccommand, Stringint disableAct(paramNum) { ... } public String disable1Actdisable(PlaceOrderCommand command) { ... } { public Collection<Product>String choices0Act(disable(PlaceOrderCommand command, int paramNum) { ... } public String validate(PlaceOrderCommand command) { ... } public String validate(PlaceOrderCommand command, public Product default0Act(int paramNum) { ... } public Collection<Object> choices(PlaceOrderCommand command, int default1Act(paramNum) { ... } public Collection<Object> autoComplete(PlaceOrderCommand command, int paramNum, String validate1Act(search) { ... } public StringObject validateAct(defaultOf(PlaceOrderCommand command, int paramNum) { ... } } |