Enterprise Java

Rich Domain Model with Guice

The anaemic domain model is a really common anti-pattern. In the world of ORM & DI frameworks we naturally seem to find ourselves with an ORM-managed “domain” that is all data and no behaviour; coupled with helper classes that are all behaviour and no data, helpfully injected in by our DI framework.

In this article I’ll look at one possible approach for implementing a rich domain model – this example uses Guice, although I’m sure Spring etc. would have different ways of achieving the same thing.

The problem

All the source code can be found on github. The “master” branch shows the original, badly factored code. The “rich-domain” branch shows the solution I describe.

Anaemic domain model

First, our anaemic domain model – TradeOrder.java. This class, as is traditional with Hibernate, has a load of annotations describing the data model, fields for all the data, accessors and mutators to get at the data and nothing else of any interest. I assume, in this domain, that TradeOrders do things. Maybe we place the order or cancel the order. Somewhere along the line, the key objects in our domain should probably have some behavior.

@Entity
@Table(name="TRADE_ORDER")
public class TradeOrder {
    @Id
    @Column(name="ID", length=32)
    @GeneratedValue
    private String id;

    @ManyToOne
    @JoinColumn(name="CURRENCY_ID", nullable=false)
    @ForeignKey(name="FK_ORDER_CURRENCY")
    @AccessType("field")
    private Currency currency;

    @Column(name="AMOUNT", nullable=true)
    private BigDecimal amount;

    public TradeOrder() { }

    public String getId() { return id; }

    public Currency getCurrency() { return currency; }
    public void setCurrency(Currency currency) { this.currency = currency; }

    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
}

Helper class

In this really simple example, we need to figure out the value of the order – i.e. the number of shares we want to buy (or sell) and the price per share we’re paying. Unfortunately, because this involves dependencies the behaviour doesn’t reside in the class it relates to, instead its been moved into an oh-so-helpful helper class.

Take a look at FiguresFactory.java. This class only has one public method – buildFrom. The goal of this method is to create a Figures from a TradeOrder.

public Figures buildFrom(TradeOrder order, Date effectiveDate)
 throws OrderProcessingException {
    Date tradeDate = order.getTradeDate();
    HedgeFundAsset asset = order.getAsset();

    BigDecimal bestPrice = bestPriceFor(asset, tradeDate);

    return order.getType() == TradeOrderType.REDEMPTION
        ? figuresFromPosition(
              order,
              lookupPosition(asset, order.getFohf(), tradeDate),
              lookupPosition(asset, order.getFohf(), effectiveDate),
              bestPrice)
        : getFigures(order, bestPrice, null);
}

Besides the “effective date” (whatever that might be), the only input this method takes is the TradeOrder. Using the copious number of getters on TradeOrder it asks for data to operate on, instead of telling the TradeOrder what it needs. In an ideal, object-oriented system, this would have been a method on TradeOrder called createFigures.

Why did we end up here? It’s all the dependency injection framework’s fault! Because the process of creating a Figures object requires us to resolve prices and currencies, we need to go and lookup this data – using injectable dependencies. Our anaemic domain can’t have dependencies injected, so instead we inject them into this little helper class.

What we end up with is a classic anaemic domain model. The TradeOrder has the data; while numerous helper classes, like FiguresFactory, contain the behaviour that operate on that data. It’s all very un-OO.

A better way

Data record

The first step is to create a simple value object to map rows from the database – I’ve called this TradeOrderRecord.java. This looks much like the original domain object, except I’ve removed the accessors & mutators to make it clear that this is a value object with no behaviour.

To make constructing these record objects easier, I’ve used karg, a library written by a colleague of mine – this requires us to declare the set of arguments we can use to construct the record with, and a constructor that accepts a list of arguments. This greatly simplifies construction and avoids us having a constructor which takes 27 strings (for example).

@Entity
@Table(name="TRADE_ORDER")
public class TradeOrderRecord {
    @Id
    @Column(name="ID", length=32)
    @GeneratedValue
    public String id;

    @Column(name="CURRENCY_ID")
    public String currencyId;

    @Column(name="AMOUNT", nullable=true)
    public BigDecimal amount;

    public static class Arguments {
     public static final Keyword<String> CURRENCY_ID = newKeyword();
     public static final Keyword<BigDecimal> AMOUNT = newKeyword();
    }

    protected TradeOrderRecord() { }

    public TradeOrderRecord(KeywordArguments arguments) {
     this.currencyId = Arguments.CURRENCY_ID.from(arguments);
     this.amount = Arguments.AMOUNT.from(arguments);
    }
}

The rich domain

Our goal is to make TradeOrder a rich domain object – this should have all the behaviour and data associated with the domain concept of a “TradeOrder”.

Data

The first thing TradeOrder will need is, internally, to store all the data associated with a TradeOrder (at least as a starting point, the unused fields hint that we might be able to simplify this further).

public class TradeOrder {
    private final String id;
    private final String currencyId;
    private final BigDecimal amount;

We make the data immutable. Immutable state is generally a good thing – and here it forces us to be clear that this is a fully populated TradeOrder, and since it has an id, it is always associated with a row in the database. By making TradeOrder immutable the obvious question is – how do I update an order? Well, there are numerous ways we could choose to do that – but that is a different story for a different time.

We also do not need accessors. Since we plan on putting all the behaviour that relates to TradeOrder on the TradeOrder class itself, other classes should not need to ask for information, they should only need to tell us what they want to achieve.

Note: there is one (now deprecated) accessor – that hints at a further behaviour that ought to be moved.

Dependencies

Besides the fields to store data, TradeOrder will also have fields representing injectable dependencies.

private final CurrencyCache currencyCache;
private final PriceFetcher bestPriceFetcher;
private final PositionFetcher hedgeFundAssetPositionsFetcher;
private final FXService fxService;

Some people will find this offensive – mixing dependencies with data. However, personally, I think the trade-off is worth it – the benefit of being able to define our behaviours on the class they relate to is worth it.

Behavior

Now we have the data and the dependencies all in one place, it is relatively easy to move across the methods from FiguresFactory:

public Figures createFigures(Date effectiveDate) throws OrderProcessingException {
    BigDecimal bestPrice = bestPriceFor(this.asset, this.tradeDate);

    return this.type == TradeOrderType.REDEMPTION
        ? figuresFromPosition(
              fohf,
              lookupPosition(this.asset, fohf, this.tradeDate),
              lookupPosition(this.asset, fohf, effectiveDate), bestPrice)
        : getFigures(fohf, bestPrice, null);
}

Construction

The last thing we need to tackle is how to create instances of TradeOrder. Since the fields for data and dependencies are all marked as final, the constructor must initialise them all. This means we need a constructor that takes the dependencies and a TradeOrderRecord (i.e. the value object we read from the database):

@Inject
protected TradeOrder(CurrencyCache currencyCache,
                     PriceFetcher bestPriceFetcher,
                     PositionFetcher hedgeFundAssetPositionsFetcher,
                     FXService fxService,
                     @Assisted TradeOrderRecord record) {
    ...
}

This isn’t particularly pretty, but the key thing to note is the @Assisted annotation. This allows us to tell Guice that the other dependencies are injected normally, whereas the TradeOrderRecord should be passed through from a factory method. The factory interface itself looks like this:

public static interface Factory {
 TradeOrder create(TradeOrderRecord record);
}

We don’t need to implement this interface, Guice provides it automatically. TradeOrder.Factory becomes an injectable dependency we can use from elsewhere when we need to create an instance of TradeOrder. Guice will initialise the injectable dependencies as normal, and the assisted dependency – TradeOrderRecord – is passed through from the factory. So our calling code doesn’t need to worry that our rich domain needs injectable dependencies.

@Inject private TradeOrder.Factory tradeOrderFactory;
...
TradeOrderRecord record = tradeOrderDAO.loadById(id);
TradeOrder order = tradeOrderFactory.create(record);

Conclusion

By mixing dependencies and data together into a rich domain model we are able to define a class with the right behaviours. The obvious code smell in TradeOrder now is that the detailed mechanics of creating a Figures is probably a separate concern and should be broken out. That’s ok, we can introduce a new dependency to manage that – as long as the TradeOrder is still the starting point for constructing the Figures object.

By having the behaviours in a single place our domain model is easier to work with, easier to reason about and it’s easier to spot cases of duplication or similarity. We can then refactor sensibly, using a good object model, instead of introducing arbitrary distinctions into helper classes that are function libraries, not participants in the domain.

Reference: Implementing a rich domain model with Guice from our JCG partner David Green at Actively Lazy Blog.

Related Articles :

David Green

David Green is a developer and aspiring software craftsman. He has been programming for 20 years but only getting paid to do it for the last 10; in that time he has worked for a variety of companies from small start-ups to global enterprises.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button