Our programming model 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:

public class Customer {                                                                                            // target
    ...
    public static class PlaceOderEvent extends ActionDomainEvent<Customer> {}                                      

    @Action(domainEvent = PlaceOrderEvent.class)                                                                   // event publishing 
    public Customer placeOrder(Product p, int quantity) { ... }                                                    // execution

    public boolean hidePlaceOrder() { return this.isBlackListed(); }                                               // supporting methods
    public String disablePlaceOrder() { return clockService.outsideShoppingHours(); }
    public String disable1PlaceOrder(Product p) { return p.isOutOfStock() ? "Out of stock": null; }                
    public Collection<Product> choices0PlaceOrder() { ... }                                                        
    public Product default0PlaceOrder() { return orderService.findLastProductPurchasedBy(this); }
    public int default1PlaceOrder() { return 1; }
    public String validate1PlaceOrder(int quantity) { return quantity <= 0 ? "Can only order +ve amounts": null; }
    public String validatePlaceOrder(Product p, int quantity) { return catalogService.runningLowOn(p) && quantity > 4 ? "We're running low on that product, no more than 4" : null; }

    ...
    @Inject CatalogService catalogService;
    @Inject ClockService clockService;
    @Inject OrderService orderService;
}

Notes:

  • For disable1PlaceOrder(), we could also hide individual parameters similarly
  • Instead of choices0PlaceOrder(), we could have used autoComplete0PlaceOrder(String)

Standard syntax

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).

Here's the same example as before, but with implementation stripped out so that it is easier to compare with the alternate syntaxes that follow:

public class Customer {                                                              // target

    @Action
    public Customer placeOrder(Product p, int quantity) { ... }                      // action execution

    public boolean hidePlaceOrder() { ... }                                          // supporting methods
    public String disablePlaceOrder() { ... }
    public String disable1PlaceOrder(Product p) { ... }
    public Collection<Product> choices0PlaceOrder() { ... }
    public Product default0PlaceOrder() { ... }
    public int default1PlaceOrder() { ... }
    public String validate1PlaceOrder(int quantity) { ... }
    public String validatePlaceOrder(Product p, int quantity) { ... }
}


Mixins syntax

Mixins change the target, by allowing this set of methods to be moved to a different object.  In other words, the target responsibility changes.

So the real target is simply:

public class Customer {}


and the action itself moves onto the mixin:

@Action                                                        
public class Customer_placeOrder() {                           

    private final Customer target;                                                // target
    public Customer_placeOrder(Customer target) { ... }

    public Customer act(Product p, int quantity) { ... }                          // action execution

    public boolean hideAct() { ... }                                              // supporting methods
    public String disableAct() { ... }
    public String disable1Act(Product p) { ... }
    public Collection<Product> choices0Act() { ... }
    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 the act(...) 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.

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::


targetbehaviourparameter
values
Notes
standardimplicitY
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
mixinsYY
The target is explicit, being the constructor of the mixin.
Parameters model

YSeparate class that captures the set of parameters that are passed to the supporting methods
Parameters on Act

YMinor variation
Parameters Everywhere

YAnother variation
Mixins + ParametersYYYCombines 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.

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() { ... }              
    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 the PlacerOrdersParameters class.

The above would also be supported with mixins:

@Action
public class Customer_placeOrder {

    private final Customer target;                                                // target
    public Customer_placeOrder(Customer target) { ... }

    @Value @Accessors(fluent = true)             
    public static class PlaceOrderParameters {                                    // to assist supporting methods
        Product product;
        int quantity;
    }

    public Customer act(Product p, int quantity) { ... }                          // execution

    public boolean hideAct() { ... }                                              // supporting methods
    public String disableAct() { ... }
    public String disable1Act(PlaceOrderParameters params) { ... }
    public Collection<Product> choices0Act() { ... }              
    public Product default0Act() { ... }
    public int default1Act() { ... }
    public String validate1Act(PlaceOrderParameters params) { ... }
    public 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:

public class Customer {                                                           // target

    @Value @Accessors(fluent = true)                   
    public class PlaceOrderParameters {                                           // to assist supporting methods 
        @Parameter() @MemberOrder(1)
        Product product;
        @Parameter() @MemberOrder(2)
        int quantity;
    }

    @Action
    public Customer placeOrder(PlaceOrderParameters params) { ... }               // execution    

    public boolean hidePlaceOrder() { ... }                                       // supporting methods use PlaceOrderParameters
    public String disablePlaceOrder() { ... }
    public String disable1PlaceOrder(PlaceOrderParameters params) { ... }
    public Collection<Product> choices0PlaceOrder() { ... }              
    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 in PlaceOrderParameters 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:

@Action
public class Customer_placeOrder {

    private final Customer target;                                                // target
    public Customer_placeOrder(Customer target) { ... }

    @Value @Accessors(fluent = true)             
    public static class PlaceOrderParameters {                                    // to assist supporting methods
        @Parameter()
        Product product;
        @Parameter()
        int quantity;
    }

    @Action
    public Customer act(PlaceOrderParameters params) { ... }                             // execution

    public boolean hideAct() { ... }                                              // supporting methods
    public String disableAct() { ... }
    public String disable1Act(PlaceOrderParameters params) { ... }
    public Collection<Product> choices0Act() { ... }              
    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 of PlaceOrderParameters 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:

@Action
public class Customer_placeOrder {

    private final Customer target;                                                // target
    public Customer_placeOrder(Customer target) { ... }

    @Value @Accessors(fluent = true)             
    public static class PlaceOrderParameters {                                    // to assist supporting methods
        @Parameter()
        Product product;
        @Parameter()
        int quantity;
    }

    @Action
    public Customer act(PlaceOrderParameters params) { ... }                      // execution

    public boolean hideAct(PlaceOrderParameters params) { ... }                   // supporting methods
    public String disableAct(PlaceOrderParameters params) { ... }
    public String disable1Act(PlaceOrderParameters params) { ... }
    public Collection<Product> choices0Act(PlaceOrderParameters params) { ... }              
    public Product default0Act(PlaceOrderParameters params) { ... }
    public int default1Act(PlaceOrderParameters params) { ... }
    public String validate1Act(PlaceOrderParameters params) { ... }
    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):

@Action
public class Customer_placeOrder {

    private final Customer target;                                                
    public Customer_placeOrder(Customer target) { ... }

    @Value @Accessors(fluent = true)             
    public static class PlaceOrderParameters { ... }                                       // see below.                                   

    public static class PlaceOrderEvent extends ActionDomainEvent<Customer> {}                                      

    @Action
    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); }
    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 String validateAct(PlaceOrderParameters params) { params.validate(this); }
}

which would then beef up the parameters object:

@Action
public class Customer_placeOrder {

    private final Customer target;                                         // target
    ...

    @Value @Accessors(fluent = true)             
    public static class PlaceOrderParameters {                                    

        @Parameter()
        Product product;
        @Parameter()
        int quantity;

	    public Customer act(Customer customer) { ... }                     // execution

    	public boolean hideAct(Customer customer) { ... }                  // supporting methods                
	    public String disableAct(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) { ...}
    }

    ...

    @Action
    public Customer act(PlaceOrderParameters params) { return params.act(this); }                         // remainder is just boilerplate

    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); }
    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 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:

@Action
public class Customer_placeOrder {

    private final Customer target;                                              // target
    ...

    @Parameter() @MemberOrder(1)                                                // supporting methods support
    Product product;
    @Parameter() @MemberOrder(2)
    int quantity;

    @Action
    public Customer act() { ... }                                               // execution 

    public boolean hideAct() { ... }                                            // supporting methods
    public String disableAct() { ... }
    public String disable1Act() { ... }
    public Collection<Product> choices0Act() { ... }              
    public Product default0Act() { ... }
    public int default1Act() { ... }
    public String validate1Act() { ... }
    public String validateAct() { ... }
}

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:

@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 

@Action
public class Customer_placeOrder {

    @Action
    public Customer act(PlaceOrderParameters params) { ... }                             // execution

    public boolean hideAct(PlaceOrderParameters params) { ... }                          // supporting methods
    public String disableAct(PlaceOrderParameters params) { ... }
    public String disable1Act(PlaceOrderParameters params) { ... }
    public Collection<Product> choices0Act(PlaceOrderParameters params) { ... }              
    public Product default0Act(PlaceOrderParameters params) { ... }
    public int default1Act(PlaceOrderParameters params) { ... }
    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:

@Value @Accessors(fluent = true)             
public class PlaceOrderCommand {    
    @Target                        
    Customer customer;

    @Parameter()
    Product product;
    @Parameter()
    int quantity;
}

and then we could have a number of handlers, for example for the execution:

public class CustomerPlaceOrderHandler {

    @Action
    public Customer act(PlaceOrderCommand command) { ... }                             // execution - infer the name of the action from the type
}

and for the preconditions (no need for the "Act" suffix):

public class CustomerPlaceOrderValidationHandler {

    public boolean hide(PlaceOrderCommand command) { ... }                          // supporting methods
    public String disable(PlaceOrderCommand command) { ... }
    public String disable1(PlaceOrderCommand command) { ... }
    public String validate1(PlaceOrderCommand command) { ... }
    public String validate(PlaceOrderCommand command) { ... }
}

and for the UI hints:

public class CustomerPlaceOrderUiHintsHandler {

    public Collection<Product> choices0(PlaceOrderCommand command) { ... }              
    public Product default0(PlaceOrderCommand command) { ... }
    public int default1(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.

public class CustomerPlaceOrderHandler implements CommandActHandler<PlaceOrderCommand> {

    public Customer act(PlaceOrderCommand command) { ... }              
}

To hide entire action:

public class CustomerPlaceOrderHideActHandler implements CommandHideActHandler<PlaceOrderCommand> {
    public boolean hide(PlaceOrderCommand command) { ... }                          
}

To hide individual parameters:

public class CustomerPlaceOrderHideParamHandler implements CommandHideParamHandler<PlaceOrderCommand> {
    public String hide(PlaceOrderCommand command, int paramNum) { ... }
}

to disable entire action:

public class CustomerPlaceOrderDisableActHandler implements CommandDisableActHandler<PlaceOrderCommand> {
    public String disable(PlaceOrderCommand command) { ... }
}

To disable individual parameters:

public class CustomerPlaceOrderDisableHandler implements CommandDisableParamHandler<PlaceOrderCommand> {
    public String disable(PlaceOrderCommand command, int paramNum) { ... }
}

To validate entire parameter set:

public class CustomerPlaceOrderValidateActHandler implements CommandValidateActHandler<PlaceOrderCommand> {
    public String validate(PlaceOrderCommand command) { ... }
}

To validate individual parameters:

public class CustomerPlaceOrderValidateParamHandler implements CommandValidateParamHandler<PlaceOrderCommand> {
    public String validate(PlaceOrderCommand command, int paramNum) { ... }
}

And we keep going for the UI hints.

To return choices:

public class CustomerPlaceOrderChoicesParamHandler implements CommandChoicesParamHandler<PlaceOrderCommand> {
    public Collection<Object> choices(PlaceOrderCommand command, int paramNum) { ... }                              // bit ugly
}

To provide an autoComplete:

public class CustomerPlaceOrderAutoCompleteHandler implements CommandAutoCompleteParamHandler<PlaceOrderCommand> {
    public Collection<Object> autoComplete(PlaceOrderCommand command, int paramNum, String search) { ... }           // bit ugly
}

To return defaults: 

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:

@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 defaultOf(PlaceOrderCommand command, int paramNum) { ... }
}
  • No labels

8 Comments

  1. Regarding Mixins and Parameters combined, instead of referencing the target parameter by index, reference it by name instead, and maybe also change the naming convention more broadly (e.g like Java language spec for number literals ... 1_000_000)

    @Action
    public class Customer_placeOrder {
     
        private final Customer target;                                              // target
        ...
     
        @Parameter() @MemberOrder(1)                                                // supporting methods support
        Product product;
        @Parameter() @MemberOrder(2)
        int quantity;
     
        @Action
        public Customer act() { ... }                                               // execution
     
        public boolean hide_act() { ... }                                           // supporting methods
        public String disable_act() { ... }
        public String validate_act() { ... }
    
        public String disable_act_quantity() { ... }                                // parameter supporting methods 
        public Collection<Product> choices_act_quantity() { ... }             
        public Product default_act_product() { ... }
        public int default_act_quantity() { ... }
        public String validate_act_quantity() { ... }
    
    }
    1. Not sure that I like the "_act" everywhere, seems to be rather redundant.  But the names in the methods I quite like.  So as a variation:

      @Action
      public class Customer_placeOrder {
        
          private final Customer target;                                              // target
          ...
        
          @Parameter() @MemberOrder(1)                                                // supporting methods support
          Product product;
          @Parameter() @MemberOrder(2)
          int quantity;
        
          @Action
          public Customer act() { ... }                                               // execution
        
          public boolean hide() { ... }                                           // supporting methods
          public String disable() { ... }
          public String validate() { ... }
       
          public String disable_quantity() { ... }                                // parameter supporting methods
          public Collection<Product> choices_quantity() { ... }            
          public Product default_product() { ... }
          public int default_quantity() { ... }
          public String validate_quantity() { ... }
       
      }

      But there's no need to require snake_casing, normal camelCasing could also be supported:

      @Action
      public class Customer_placeOrder {
        
          private final Customer target;                                              // target
          ...
        
          @Parameter() @MemberOrder(1)                                                // supporting methods support
          Product product;
          @Parameter() @MemberOrder(2)
          int quantity;
        
          @Action
          public Customer act() { ... }                                               // execution
        
          public boolean hide() { ... }                                           // supporting methods
          public String disable() { ... }
          public String validate() { ... }
       
          public String disableQuantity() { ... }                                // parameter supporting methods
          public Collection<Product> choicesQuantity() { ... }            
          public Product defaultProduct() { ... }
          public int defaultQuantity() { ... }
          public String validateQuantity() { ... }
       
      }


      Another option might be to pass in the parameter as a name:

      @Action
      public class Customer_placeOrder {
        
          private final Customer target;                                              // target
          ...
        
          @Parameter() @MemberOrder(1)                                                // supporting methods support
          Product product;
          @Parameter() @MemberOrder(2)
          int quantity;
        
          @Action
          public Customer act() { ... }                                               // execution
        
          public boolean hide() { ... }                                               // supporting methods
          public String disable() { ... }
          public String validate() { ... }
       
          public String hide(String paramName) { ... }                                // parameter supporting methods
          public String disable(String paramName) { ... }                             
          public String validate(String paramName) { ... }
          public Collection<? extends Object> choices(String paramName) { ... }            
          public Object defaultOf(String paramName) { ... }
       
      }

      Also: these variations aren't mutually exclusive, we could potentially support all of them if there was no strong consensus.

      1. To get rid of the @MemberOrder we could write, and this would be compatible with a java record declaration later on ...


        @Action
        public class Customer_placeOrder {
           
            private final Customer target;                                             // target
        
            private final Product product;                                             // params as fields
            private final int quantity;
        
            public Customer_placeOrder(
                Customer target,
        
            	@Parameter()                                 
        	    Product product,
        
            	@Parameter()
        	    int quantity
            ) { 
              this.customer = customer; 
              this.product = product; 
              this.quantity = quantity;
            }
           
            @Action
            public Customer act() { ... }                                           // execution
           
            public boolean hide() { ... }                                           // supporting methods
            public String disable() { ... }
            public String validate() { ... }
          
            public String disableQuantity() { ... }                                // parameter supporting methods
            public Collection<Product> choicesQuantity() { ... }           
            public Product defaultProduct() { ... }
            public int defaultQuantity() { ... }
            public String validateQuantity() { ... }
          
        }

        (Fixed syntax for the constructor)

        1. I've just fixed the syntax of this example - it was malformed.

          I'm not so sure of this syntax. 

          Although it \@MemberOrder is indeed gone, it's replaced by repetition of the parameter list and the field list, plus the boilerplate to assign values.

          Yes, Java 14 records will remove this, but they won't land until Java 17 in LTS, and so we probably can't take them for granted until the LTS after that, which will be Java 23 LTS in Sept 2024.  I think that's too far away to plan for at this stage.

          Still, I think we could accommodate both the no-arg constructor version (ie framework uses field injection to initialize) and the all-arg constructor version (ie framework uses constructor injection) ... then, once more, it's a matter of taste.

          1. Still I like the record idea, because it also dictates that the mixin's state is immutable, for the lifespan of a call to one of the methods it declares.

            By using lombok, we can once more remove the boilerplate of having to write the constructor. However, we would need the @Parameter annotation to be allowed on the corresponding field declarations.

            To emphasize my point: those fields are not mutable and must be passed in by the constructor. In addition any additional field declarations (that hold any internal state for the mixin) are strictly forbidden.

            This fits also nicely with the idea of providing additional properties or collections for more advanced action dialogs in the UI, which need to be strictly read-only: Interpreting a mixin as a Java record, those additional properties or collections can only be provided as bean style getters, but never by declaring additional fields within a mixin.


            1. I do like the idea of making things immutable if they can be, of course. And in regular production use, this is fine, because it's the framework that will instantiate the mixin.

              There is also the issue of injected services though.  Both records and Lombok's  \@Data annotation would require that any services are passed into the constructor.  Again, if its only ever the framework that instantiates the mixin, then this is fine.

              Howvever, in testing code, or when using the WrapperFactory, it's the programmer that instantiates the mixin, and so things get rather more cumbersome.

              If we require only the target to be immutable, and use fluent interface for the params, then we get:

              wrapperFactory.wrap(new Customer_placeOrder(customer))
                   .product(someProduct)
                   .quantity(1)
                   .act();

              vs:

              wrapperFactory.wrap(new Customer_placeOrder(customer, someProduct, 1)).act();


              Now suppose we need to inject in a domain service.  With mutable/fluent fields, we can inject the domain services during wrapping, so the syntax is no different:

              wrapperFactory.wrap(new Customer_placeOrder(customer))   // injection happens here transparently
                  .product(someProduct)
                  .quantity(1)
                  .act();

                But with an immutable object, we would also need to pass in all services into the constructor.

              wrapperFactory.wrap(new Customer_placeOrder(customer, someProduct, 1, theCustomerRepository).act();



  2. With ISIS-2362 - Getting issue details... STATUS , we are implementing following programming model, because these are low hanging fruits easy to pick now.

    @Action
    @RequiredArgsConstructor
    public class Customer_placeOrder {
    
        private final Customer target; 
        
        @Value @Accessors(fluent = true)            
        public static class Parameters {    // immutable parameter record object, constructor must correspond to 'act(...)'
            
            Product product;
            int quantity;
        }
        
        // this is (still) the contract 'with the meta-model'
        // no @Action annotation required, since already at type level, however its still allowed to improve readability
        public Customer act(
                
                @Parameter()
                @ParameterLayout()
                Product product,
                
                @Parameter()
                @ParameterLayout()
                int quantity) {
            
            // ...
            
            return target;
        }
        
        public boolean hide() { ... }               // supporting methods (no action name reference required)
        public String disable() { ... }
        public String validate(Parameters params) { ... }
      
        public boolean hideProduct(Parameters params) { ... }         // parameter supporting methods (exemplified on first parameter)
        public String disableProduct(Parameters params) { ... }                            
        public String validateProduct(Parameters params) { ... }
        public Collection<Product> choicesProduct(Parameters params) { ... }           
        public Collection<Product> autoCompleteProduct(Parameters params, String search) { ... }  // note: additional search parameter required
        public Product defaultProduct(Parameters params) { ... }
    
        public boolean hideQuantity(Parameters params) { ... }         // parameter supporting methods (exemplified on second parameter)
        ...
        
    }