Wednesday, August 29, 2007

Example of mutable extension to immutable domain model

I must not have communicated the mutable extension idea clearly enough, as I've been accused of breaking the immutability and encapsulation of the domain model which would, I agree, make the domain model sloppy. I deplore mutability in domain models and anemic / 'behaviourally absent' nonsense such as setters / getters.

I haven't done either of those things (as far as production code is concerned) here's a worked example which demonstrates this.

Here's the immutable, encapsulated, distinctly unsloppy domain model:

class Person {
private final String name;
private final int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

Here's code that's only available for testing:

class MutablePerson extends Person {
private static final String DEFAULT_NAME = "Bob";
private static final int DEFAULE_AGE = 32;

public MutablePerson() {
super(DEFAULT_NAME, DEFAULT_AGE);
}

public MutablePerson withName(String name) {
return new MutablePerson(name, getAge());
}

public MutablePerson withAge(int age) {
return new MutablePerson(getName(), age);
}

private String getName() {
return accessor.get(this, "name", String.class);
}

private int getAge() {
return accessor.get(this, "age", Integer.class);
}

private final ReflectiveAccessor accessor = new ReflectiveAccessor();
}

Now it's true that the testing code has broken encapsulation but only as far as the testing code is concerned, not for the production code. The domain model itself is unchanged. It should be noted that there is a one-to-one relationship between the immutable domain model class and the mutable extension and that the break in encapsulation for a given domain model class propagates no further than the corresponding mutable extension; and again this break only exists in the testing code.

So I would say the testing code has a minor encapsulation breach which allows the testing code to have a more succinct form than possible with the test builder (it essentially removes the need for all the build() methods).

Unlike the Test Builder it would not be suitable to move the Mutable Extensions to the production code. Immutability & non-anemicness helps keep domain models from becoming 'bags-o-data', having a mutable version of each domain model in the production code would be too much of a temptation.

Tuesday, August 28, 2007

Mutable extension to immutable domain model: an alternative to the Test Data Builder pattern

Nat Pryce recently blogged about an alternative to the Object Mother pattern: creating a builder class for each class in the domain. He's able with this idea to get testing code to look like this:
Invoice invoiceWithNoPostcode = new InvoiceBuilder()
.withRecipient(new RecipientBuilder()
.withAddress(new AddressBuilder()
.withNoPostcode()
.build())
.build())
.build();

I think I can improve on this and achieve code that looks like this:

MutableInvoice invoiceWithNoPostcode = new MutableInvoice()
.withRecipient(new MutableRecipient()
.withAddress(new MutableAddress()
.withNoPostcode()));

Instead of creating a builder for each domain class I've created a mutable extension to each domain class, it is the mutable extension I'm using in the test.

I don't quite like the code above, I think the following looks better:
Invoice invoiceWithNoPostcode = new Invoice()
.withRecipient(new Recipient()
.withAddress(new Address()
.withNoPostcode()));

I've changed the code to use the same name for the mutable extension (with a different package of course) because I figure the tests can just refer to the mutable versions and it saves me having the 'Mutable' prefix on everything. Naturally I only make the mutable extensions available for testing.

Here's how the mutable Invoice extension might look:

package model.mutable;

public Invoice extends model.Invoice {
public Invoice(Recipient recipient, InvoiceLines invoiceLines) {
super(recipient, invoiceLines);
}

public Invoice() {
// choose the same defaults here as you would in the InvoiceBuilder
this(null, null);
}

public Invoice withRecipient(Recipient recipient) {
return new Invoice(recipient, getInvoiceLines());
}
}

I've had to add protected accessors to the domain model; this bothers me a little as they make it easier to slip towards an anemic data model. It might be worth using some reflective code in the extension class instead:

public class Invoice extends model.Invoice {
...

private InvoiceLines getInvoiceLines() {
return new ReflectiveAccessor().get(this, "invoiceLines", InvoiceLines.class);
}
}


public class ReflectiveAccessor {
public T get(Object fieldOwner, String fieldName, Class fieldClass) {
...
}
}