Mixins

Introduction

A mixin acts like a trait or extension method, allowing one module to contribute behaviour or derived state to another object. The mixin class is an implementation of behaviour - an action, a property or a collection - that and is contributed to another class, either a domain entity or view model.

Or rather, the domain object appears in the UI to have the behaviour, but the underlying domain class being contributed to does not "know" this is happening. In this way what the end-user sees is the domain object’s actual behaviour along with all of its contributions from other modules. This makes for a very powerful feature in terms of decoupling, but at the same time is easy to implement for the developer.

In fact, the mixin class is bound to the contributee (or mixee) through a Java type, which could be a concrete class, an abstract class, an interface or even java.lang.Object. This is therefore a key technique to allow the app to stay decoupled, so that it doesn’t degrade into the proverbial "big ball of mud". There’s a lot more discussion on this topic in modularity section below and also on the modules page.

Mixins are also a convenient mechanism for grouping functionality even for a concrete type, helping to rationalize about the dependency between the data and the behaviour. Each mixin is in effect a single behavioural "responsibility" of the domain object.

Inside-out vs outside-in

We find mixins nicely balance inside-out vs outside-in ways of thinking about a system:

  • inside-out tends to focus on the structure, the nouns that make up the domain.

    For Apache Causeway, these would typically be entities.

  • outside-in tends to focus on the behaviour, that is the functionality that the system provides to automate the business processes; the verbs, in other words.

    For Apache Causeway, these would typically be actions.

So-called "behaviourally complete" objects have actions implemented by the entities …​ in other words, good ole-fashioned object-orientation, as your Mother taught you. That’s suitable in lots of cases, though the contract between the behaviour in the action vs the data structure is not explicit …​ the method has access to all the object’s state through the this keyword.

With mixins, though, make the contract between the behaviour and the structure explicit by moving the behaviour into its own class. If you do this for all of the behaviour, then the entity might be simply a data container, immutable in and of itself. In practice, you might not do this completely, but rather have a set of methods on the entity (not actions) that enforce some basic integrity rules (eg ensure that a startDate <= endDate, but don’t do much more than that). The mixin for its part exposes behaviour to the end-user as an action, and then manipulates the state of the mixee only by way of the interface exposed to it.

We’ve also found that mixins fit nicely with an agile development methodology. Often when there’s a new user story/feature to be implemented, then that new feature may correspond to a new mixin to be implemented.

DCI Architecture

DCI stands for Data-Context-Interaction and is presented as an evolution of object-oriented programming, but one where behaviour is bound to objects dynamically rather than statically in some context or other.

The idea was described in the DCI architecture, as formulated and described by Trygve Reenskaug and Jim Coplien. Reenskaug is credited as the inventor of the widely-known MVC pattern (he was also the external examiner for Richard Pawson’s PhD thesis on Naked Objects), while Coplien has a long history in object-orientation, C++ and patterns.

The mixin pattern is Apache Causeway' take on the same basic concept. The contributee’s type defines an internal API between the mixin behaviour and the internal data structure, while the contributee’s behaviour enables the interaction by the end-user. The simplification is that the context isn’t dynamic, instead the mixin and its contributee are associated statically by way of the contributee type.

Leveraging hot reloading

There are also practical reasons for moving behaviour out of entities even within the same module, because structuring your application this way helps support hot-reloading of Java classes (so that you can modify and recompile your application without having to restart it). This can provide substantial productivity gains.

The Hotspot JVM has limited support for hot reloading; generally you can change method implementations but you cannot introduce new methods. However, the DCEVM open source project will patch the JVM to support much more complete hot reloading support. There are also commercial products such as JRebel.

Programming Model

Mixins are implemented as classes that have a single argument constructor and, depending on the behaviour they contribute, are annotated using either @Action, a @Collection or a @Property.[1]

These classes have a single method which implements the behaviour; the name of this method is determined by whether it is an action, collection or property.

In addition, mixin classes also must define a public single-arg constructor. It is this argument’s parameter type which determines the domain class being contributed to.

The sections below provide complete examples of collection, property or action mixins, with contributions through a DocumentHolder interface.

Contributed Action

A mixin that contributes an action is defined as a class annotated @Action, with the action’s behaviour implemented in a method called "act".

For example, the following contributes an "addDocument" action to classes that implement/extend DocumentHolder:

@Action                                             (1)
@RequiredArgsConstructor                            (2)
public class DocumentHolder_addDocument {           (3)

    private final DocumentHolder holder;            (4)

    public Document act(Document doc) {             (5)
        /// ...
    }
    public boolean hideAct() { /* ... */ }          (6)
}
1 indicates this class is an action mixin
2 It’s common to use Lombok to automatically create the constructor.
3 The action Id is "addDocument", ie everything after the "_". By convention, the prefix before the "_" is the mixee’s type name.
4 Mixee is injected into the mixin as a field through the constructor. Most mixins will interact with their mixee in some way.
5 method must be called "act"
6 supporting methods follow the usual naming conventions. However, mixins also support alternative conventions that you may prefer.

Contributed actions can accept parameters, and have any semantics. (This is not true for contributed collections, or properties; see below).

Contributed Collection

A mixin that contributes a collection is defined as a class annotated @Collection, with the collection implemented in a method called "coll".

For example, the following contributes a "documents" collection to classes that implement/extend DocumentHolder:

@Collection                                     (1)
@RequiredArgsConstructor
public class DocumentHolder_documents {

    private final DocumentHolder holder;

    public List<Document> coll() { /* ... */ }  (2)
        ...
    }
    public boolean hideColl() { /* ... */ }     (3)
}
1 indicates that this class is a collection mixin
2 method (which must be called "coll") must accept no arguments, and return a collection
3 supporting methods (discussed in business rules) follow the usual naming conventions (Because the collection is derived/read-only, the only supporting method that is relevant is hideColl()).

Contributed collections are derived, which means:

  • the "coll" method must take no-arguments.

  • the method should have no side-effects; otherwise the very act of rendering the domain object would change state.

  • it should (of course) return a java.util.Collection (or List, or Set, or SortedSet); however java.util.Maps are _not supported.

Contributed Property

A mixin that contributes a property is defined as a class annotated @Property, with the property implemented in a method called "prop".

For example, the following contributes a "mostRecentDocument" collection to classes that implement/extend DocumentHolder:

@Property                                           (1)
@RequiredArgsConstructor
public class DocumentHolder_mostRecentDocument {

    private final DocumentHolder holder;

    public Document prop() {                        (2)
        ...
    }
    public boolean hideProp() { /* ... */ }         (3)
}
1 indicates this is a property mixin
2 method (which must be called "prop") must accept no arguments, be query-only, and return a scalar value
3 supporting methods (discussed in business rules) follow the usual naming conventions (Because the property is derived/read-only, the only supporting method that is relevant is hideProp()).

Like collections, contributed properties are derived, which means:

  • the "prop" method must take no-arguments.

  • the method should have no side-effects; otherwise the very act of rendering the domain object would change state.

  • it should return a scalar value.

Action Parameters

If you have defined an action as a mixin, then the name of the method is "act" and so supporting methods (which match on the name) are easier to write. For example, the supporting method to hide the action will always be called "hideAct". However, it can still be tricky to match the supporting methods for individual parameters are matched by number, eg "validate0Act" to validate the 0-th argument.

If using mixins, there are two refinements to the programming model that can make your code easier to maintain.

By way of example, consider this mixin implemented the usual way:

@Action
@RequiredArgsConstructor
public class ShoppingCart_addItem {

    private final ShoppingCart shoppingCart;

    public ShoppingCart_addItem act(                                (1)
            @Parameter final Product selected,
            @Parameter final int quantity) {
        // ...
    }

    public boolean hideAct() {                                      (2)
        return shoppingCart.isCheckedOutAlready();
    }

    public List<Product> autoComplete0Act(String codeOrName) {      (3)
        return productService.findMatching(codeOrName);
    }

    public String validate0Act(Product selected) {                   (4)
        return selected.isInStock() ? null : "The selected product isn't in stock";
    }

    public String validate1Act(Product selected, int quantity) {     (5)
        if (productService.isInHighDemand(select) && quantity > 5) {
            return "Sorry, you may not purchase more than 5 items for products that are in high-demand";
        }
        if (quantity <= 0) {
            return "Sorry, can only add a positive number of items to the cart";
        }
        return null;
    }

    @Inject ProductService productService;
}
1 act(…​) is the action implementation
2 hideAct(…​) hides the action if required
3 autoComplete0Act(…​) returns a list for the 0-th parameter (Product)
4 validate0Act(…​) validates the 0-th parameter (Product)
5 validate1Act(…​) validates the 0-th parameter (Product)

The sections below describes refactor this mixin using the refined syntax.

Using the parameter name

In the first alternative, we use the parameter name (rather than its position) to match the supporting methods to the action parameters.

@Action
@RequiredArgsConstructor
public class ShoppingCart_addItem {

    private final ShoppingCart shoppingCart;

    public ShoppingCart_addItem act(
            @Parameter final Product selected,
            @Parameter final int quantity) {
        // ...
    }

    public boolean hideAct() {
        // ...
    }

    public List<Product> autoCompleteSelected(String codeOrName) {      (1)
        // ...
    }

    public String validateSelected(Product selected) {                  (2)
        // ...
    }

    public String validateQuantity(Product selected, int quantity) {    (3)
        // ...
    }

    // ...
}
1 The autoComplete method for the 0-th parameter name now uses its name "Selected", instead
2 The validate method for the 0-th parameter name uses the name "Selected"
3 The validate method for the 1-th parameter name uses the name "Quantity"

Note that the supporting methods no longer include the method name of the action itself (ie "act"). For this reason, it isn’t possible to use matching-by-name for non-mixin actions.

Using a "parameters" class

In the previous section we made the names of the supporting methods more obvious. Even so, for actions that take many parameters, it can still be problematic to ensure that the supporting methods parameter types are correct, and in the correct order.

In the second refinement, we create a data class to hold the parameters:

@Action
@RequiredArgsConstructor
public class ShoppingCart_addItem {

    private final ShoppingCart shoppingCart;

    public ShoppingCart_addItem act(
            @Parameter final Product selected,
            @Parameter final int quantity) {
        // ...
    }

    @Value @Accessors(fluent = true)
    static class Parameters {               (1)
        final Product selected;             (2)
        final int quantity;
    }

    public boolean hideAct() {
        // ...
    }

    public List<Product> autoCompleteSelected(String codeOrName) {
        // ...
    }

    public String validateSelected(Parameters parameters) {             (3)
        // ...
    }

    public String validateQuantity(Parameters parameters) {             (2)
        // ...
    }

    // ...
}
1 define a class using Lombok, but could also be a record:
record Parameters (Product selected, int quantity);
2 list of fields matches the parameters of the action method
3 All the supporting methods for the various parameters just take a Parameters object

As Nested Classes

While mixins primary use case is as a means of allowing contributions from one module to the types of another module, they are also a convenient mechanism for grouping functionality/behaviour against a concrete type. All the methods and supporting methods end up in a single construct, and the dependency between that functionality and the rest of the object is made more explicit.

We might therefore want to use a mixin within the same module as the mixee; indeed even within the same package or class as the mixee. In other words, we could define the mixin as nested static class of the mixee it contributes to.

In the previous examples the "_" is used as a separator between the mixin type and mixin name. However, to support mixins as nested classes, the character "$" is also recognized as a separator.

For example, the following refactors the "updateName" action — of the SimpleObject class in SimpleApp start app — into a nested mixin:

public class SimpleObject /* ... */ {

    @Action(semantics = IDEMPOTENT,
            commandPublishing = Publishing.ENABLED,
            executionPublishing = Publishing.ENABLED,
            associateWith = "name",
            domainEvent = updateName.DomainEvent.class)                 (2)
    public class updateName {                                           (1)

        public class DomainEvent extends
            SimpleModule.ActionDomainEvent<SimpleObject.updateName> {}  (2)

        public SimpleObject act(@Name final String name) {
            setName(name);                                              (3)
            return SimpleObject.this;
        }
        public String defaultName() {                                   (4)
            return getName();
        }
    }
    // ...
}
1 Mixin class is not static, so that the containing object is implicitly available.
2 Domain event can be declared within the mixin, again, not static.

Note that it is genericised on the mixin, not on the mixee

3 Acts on the owning instance.
4 Uses the alternate programming model matching the parameter by its name, not number

The mixin class name can be either "camelCase" or "PascalCase", as you prefer.

Modularity

Mixins separate behaviour away from the underlying mixee, with the mixee operating upon the mixee. The mixee itself knows nothing about its caller. This simple fact becomes very powerful in that the mixin could even be in a different module than the mixee, modularity being an important concern of maintainability.

Contributing to a class

The simple case is for the mixin to contribute directly to a mixee’s concrete class.

For example, suppose we have an orders module and a customers module. The orders module could provide a mixin that (so far as the end-user is concerned in the UI) would allow a customer to place an order:

`orders` module contributes to `customers`
Figure 1. orders module contributes to customers

In terms of code:

package domainapp.orders;                   (1)

import domainapp.customers.Customer;        (2)

@Action
@RequiredArgsConstructor
public class Customer_placeOrder {

    private final Customer customer;

    // ...
}
1 defined in the orders module
2 acts upon objects in the customers module

Contributing to an interface

In the previous section the Customer did not know that it was being contributed to; and the mixee had access to the entire (public) interface defined by the Customer type. A variation though is for the mixee to contribute by way of a (Java) interface.

This interface needs to be in a module that both the mixee and mixin has access to, and so there are three options:

  • interface is defined the mixin’s module

  • interface is defined the mixee’s module

  • interface is defined in some common/base module

The sections below explore each of these alternatives.

Interface defined in mixin’s module

For example, suppose that the docmgmt module provides document management capabilities, provided to any DocumentHolder. The Customer class might then take advantage of this, saying in effect: "yes, I want to hold some documents, please manage that concern for me".

`Customer` elects to have contributions made to it by `docmgmt` module
Figure 2. Customer elects to have contributions made to it by docmgmt module

Therefore customer (mixee) --- [depends upon]→ docmgmt (mixin).

In terms of code:

  • in the docmgmt module:

    package domainapp.docmgmt;
    
    public interface DocumentHolder { ... }

    and

    package domainapp.docmgmt;
    
    @Collection
    @RequiredArgsConstructor
    public class DocumentHolder_documents {
    
        private final DocumentHolder documentHolder;
    
        // ...
    }
  • in the customers module:

    package domainapp.customer;
    
    import domainapp.docmgmt.DocumentHolder;
    
    public class Customer implements DocumentHolder {
        // ...
    }

Interface defined in mixee’s module

Alternatively, the interface could be in the mixees’s module.

For example, suppose the customer module defines HasAddress, also implemented by Customer. The mailmerge module provides the ability to send out mailshots, but it only needs the information exposed through HasAddress, not all of Customer.

`mailmerge` module contributes to `customers`
Figure 3. mailmerge module contributes to customers

Therefore mailmerge (mixin) --- [depends upon]→ customer (mixee).

In terms of code:

  • in the customers module:

    package domainapp.customer;
    
    public interface HasAddress {
        // ...
    }
    }

    and

    package domainapp.customer;
    
    public class Customer implements HasAddress {
        // ...
    }
  • and in the mailmerge module:

    package domainapp.mailmerge;
    
    @Action
    public class HasAddress_mailshot {
        private final HasAddress hasAddress;
        // ...
    }

Interface defined in a common module

With this design in the previous section, there’s nothing to prevent our "mailmerge" module from accessing the rest of Customer. We can fix this by moving the interface to some common location:

`mailmerge` module contributes to `customers`
Figure 4. mailmerge module contributes to customers

Therefore neither mailmerge (mixin) nor customer (mixee) depend upon each other; instead both depend upon address.

In terms of code:

  • in the address module:

    package domainapp.address;
    
    @Action
    public interface HasAddress {
        private final HasAddress hasAddress;
        // ...
    }
  • in the customers module:

    package domainapp.customer;
    
    import domainapp.address.HasAddress;
    
    @Action
    public class Customer implements HasAddress {
        // ...
    }
  • in the mailmerge module:

    package domainapp.mailmerge;
    
    @Action
    public class HasAddress_mailshot {
        private final HasAddress hasAddress;
        // ...
    }

Taking this idea to its logical conclusion, that common type could even be java.lang.Object. And indeed, the framework itself defines a handful of mixins that do exactly this.

Micro-frontends

A micro-frontend is an extension of the microservices idea, but for the user interface. Rather than having a monolithic front-end UI that calls to multiple backends, instead the user interface that is composed of vertical slices of functionality, each slice calling its corresponding backend microservice. UI infrastructure pulls all of these fragments together into a coherent user interface.

Microservices are (of course) just one means to implement modularity and enable product ownership. In the context of an Apache Causeway app, we are more likely to use individual Maven modules as our, well, as our modules. Each such module provides a set of mixins, and these mixins in effect show up to the end-user as slices of functionality. The framework itself brings all this together, acting as the UI infrastructure.

Programmatic usage

When a domain object is rendered, the framework will automatically instantiate all required mixins and delegate to them dynamically. If writing integration tests or fixtures, or (sometimes) just regular domain logic, then you may need to instantiate mixins directly.

For this you can use the FactoryService#mixin(…​) method.

For example:

DocumentHolder_documents mixin =
    factoryService.mixin(DocumentHolder_documents.class, customer);

Alternatively, you can use ServiceInjector to inject domain services after the mixin has been instantiated. You’ll need to use this method if using nested non-static mixins:

SimpleObject.updateName mixin =
    serviceInjector.injectServicesInto( simpleObject.new updateName() );

1. Mixin classes are also required to be annotated with @DomainObject(nature=MIXIN). The @Action, @Collection and @Property are in fact meta-annotations that satisfy this requirement for us.