Skip to content

2012 03 16 re mix the mixin based composition pattern

Fabian Schmied edited this page Mar 16, 2012 · 1 revision

Published on March 16th, 2012 at 14:05

The Mixin-Based Composition Pattern

At rubicon, we implement various applications based on our business case processing product called ActaNova. ActaNova is a piece of software that implements electronic records management (and more) in a standard way. Those specific applications we implement on top of it extend the standard domain model, UI, database schema, and so on with application-specific behavior and structure.

This has quite a few interesting effects on the technical implementation. For example, we need a way to allow the UI to be extended (and modified) by modules added to the application. In this post, however, I’d like to concentrate on the idea of building a domain model that is extensible by modules within the application. And I’ll use a pattern based on mixins (as provided by re-mix), which we call the Mixin-Based Composition Pattern.

The pattern allows us to do two things:

  • Define a domain class as an aggregation of functionality defined by different modules within the application, and
  • extend the domain class from modules added to the application.

Consider a simple Person class, as it could be defined by an electronic records management software. Note that I’m not talking about the actual current or future implementation of ActaNova here, I’m just inventing an example that lets me illustrate the pattern. Let’s say a Person has the following features, implemented by various modules within the records management product:

  • It is an entity in the domain model, so it has got an ID.
  • It also has a first name, last name, middle names, and date of birth as standard properties.
  • It is versioned, which means that changes made to its data don’t simply overwrite any previous values, but put them into a history object. Versioning is implemented in a generic fashion by the Versioning module.
  • It is also tenant-bound, which means that it belongs to a specific tenant within the application (and only in the context of that tenant can it be loaded, edited, and changed. Tenant handling is implemented in a generic fashion by the Tenant module.

A specific employee records management application is built around the standard records management software. It creates an Employee domain class that derives from Person. Different modules extend the standard Person class as follows:

  • The Employee domain class has a job description property in addition to the inherited Person properties.
  • The Human Resources module adds the current salary of the employee.
  • The Organizational Structure module adds the current manager of the employee.

Here’s a picture of how you could model this domain by using mixins:

ComposedObjects.png

As you can see, the Person class aggregates the versioning and tenant binding functionality defined by the respective modules by using generic mixins exposed from those modules. The Employee class derives from the Person class and is extended by mixins from within the Human Resources and the Organizational Structure modules. This is a combination of simulating multiple inheritance via mixins (reusing code by including mixins into your classes) and use-case slicing, also implemented via mixins (implementing different slices of your code separately, use case by use case). (And in case you’re wondering, the OnLoaded and OnCommitting methods in the picture are considered to be infrastructure methods exposed by all domain objects.)

Using re-mix, this all works really nicely, with one caveat: when you have an instance of Person or Employee and want to access those properties added by mixins, you need to cast the instance to IVersionedObject, IPaidEntity, and so on. Which is not very nice, which is why the Mixin-Based Composition Pattern adds the following conventions:

  • A composed object (such as Person) must provide a This property of a type TComposedInterface. The TComposedInterface type must be an interface derived from all the interfaces introduced by used mixins. (In the example, that would be an ICompletePerson interface, inheriting from IVersionedObject and ITenantBoundObject.) The TComposedInterface can also provide properties defined by the core object itself. (I.e., ICompletePerson can also provide the ID, FirstName, LastName, MiddleNames, and DateOfBirth properties.)
  • Mixins that extend a specific class must also provide a conversion method As...() as an extension method for instances of that target class. The conversion method returns the instance cast to the interface introduced by the mixin.

These conventions allow client code to access all members added by used mixins via the This property and all members added by extending mixins via the As...() extension methods. This makes it very convenient to work with a domain implemented via mixins. Here’s an example of how the Employee class shown above would be used:

var employee = ObjectFactory.Create<Employee\>();

Console.WriteLine (employee.ID);

Console.WriteLine (employee.FirstName);

// Properties added by mixins included by the Person/Employee classes

Console.WriteLine (employee.This.TenantID);

Console.WriteLine (employee.This.VersionID);

// Properties added by mixins extending the Person/Employee classes

Console.WriteLine (employee.AsOrganizedEntity().Manager);

Console.WriteLine (employee.AsPaidEntity().Salary);

re-mix has a ComposedObject<TComposedInterface> base class that provides the This property, so implementing the Person class could be as simple as this:

public interface ICompletePerson : IVersionedObject<Person>, ITenantBoundObject {}

[Uses (typeof (VersioningMixin<Person\>))]
[Uses (typeof (TenantBindingMixin))]
public class Person : ComposedObject<ICompletePerson>
{
  public Person (int id)
  {
    ID = id;
    MiddleNames = new List<string\>();
  }

  public int ID { get; private set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public List<string\> MiddleNames { get; private set; }
  public DateTime DateOfBirth { get; set; }
  // etc.
}

(I’ve given the IVersionedObject interface a generic parameter, just to show that this is possible.)

But even if you can’t (or don’t want to) use the ComposedObject base class, implementing the This property by hand is very simple:

public class ...
    : IHasCompleteInterface<TComposedInterface\>
{
  // etc.

  public TComposedInterface This
  {
    get { return (TComposedInterface) this; }
  }
}

The IHasCompleteInterface marker interface just causes re-mix to include the given interface on the domain object type (without the domain programmer having to implement its methods).

Here’s an example of one of those As...() extension methods, as it would come with the HumanResources module:

public static class EmployeeExtensions
{
  public static IPaidEntity AsPaidEntity (this Employee employee)
  {
    return (IPaidEntity) employee;
  }
}

No magic there, either, just some syntactic sugar to avoid users having to make those casts.

To summarize, by implementing the Mixin-Based Composition Pattern, you can:

  • build your domain classes by means of aggregating functionality from different modules in the application by using mixins exposed from those modules,
  • access members added by those mixins nicely via the This property,
  • extend your domain model from external modules by having mixins in there that extend the domain classes, and
  • access members added by those mixins nicely via the extension methods provided with the mixins.

This makes for very extensible, yet nicely factorized and simple to use domain models.

(One word about persistence and O/R mapping: Our own O/R mapper “re-store” recognizes mixed entities, and it persists properties added by the mixins into the database. It’s of course possible to implement the same behavior for other O/R mappers and persistence frameworks as well if they provide the necessary extensibility points. Whether to put the mixin data into the same database tables as the core domain data is another topic and depends on the specific situation.)