This post is part of a series I am writing on Domain Driven Design (DDD).
Associations between objects are typically not well thought out during design. Most of the time, the associations are dictated by the underlying data model. This results in addition of a lot of associations and traversal paths that may not make sense in a domain.
For e.g. an Order has an associations to multiple Products. Your natural traversal would be from an Order to Product. I am yet to see a usecase where you pick a product and 'traverse' to a list of Orders to which it is associated to. Normally the kind of functionality supported would be to list 'all open orders for product' or 'all orders for this product from a given customer' etc and these typically would be queried from DB using a DAO.
According to Eric Evans, associations should
- Impose a traversal direction
- Add qualifiers and reduce multiplicity
- Not be present if it is non-essential
Unidirectional vs Bidirectional associations
A bidirectional association means that both Objects can only be understood together. There are real world usecases where such a necessity exists. For e.g. an Order needs to have a bi-directional association with its Settlement object. However in many cases like the first example a uni-directional association would suffice.
The advantages of unidirectional associations are
- It brings out the natural bias of the domain to one particular traversal direction
- Makes the association more communicative
- Provides a natural constraint on where to place domain logic since you can only place logic on the class which can reach out to all classes involved.
However there were some scenarios where I started using unidirectional associations but in the end wound up reverting them to bi-directional since it felt more natural.
- Use Uni-directional associations if your usecases dictate there are multiple entry points for same operation.
For e.g. an OrderList was initially modelled with a uni-directional association to list of Orders. Any domain logic that affects multiple orders can only be triggered from the OrderList.
One of the usecases detailed that cancelling the last Order in a OrderList, cancels the entire list. Another usecase said that cancelling an OrderList should in turn call cancel on each Order within the list.
If we had bi-directional associations, supporting the above 2 usecases led to some circular call issues.Order.cancel() calls OrderList.cancel() which internally would end up calling Order.cancel for all Orders ! One solution was to have Order.cancel and Order.doCancel where one would not trigger call to OrderList. This is plain ugly.
The best solution is to make all cancel Order calls be handled by the OrderList. So let Order.cancel() delegate the cancel call to OrderList.cancelOrder(OrderToCancel).
Since the Order does not have a back pointer to its OrderList, the Order had to use a repository to look up its OrderList. Moving to bi-directional we could use Hibernate's hydration support to get a reference to OrderList from Order and vice versa.
- Use Uni-directional association if there are usecases which require data from other object even though entry point is a different Object.
The problem is compounded when having *-to-many relationships and many is actually quite a large number which causes a resource drag during Object hydration.
For e.g. over course of a year, a single Customer can have thousands of Orders. Loading all Orders every time a customer object is created is bad for performance. But you would obviously need Customer data to fulfil a given Order and all Order data for a given customer to find credit worthiness.
In such cases maintain a bi-directional association indirectly by having Customer.getOrders load Order data, on demand by querying from database.
Associations when modelled correctly ensure that there is always only one way to access data. This enhances readability and as a consequence maintainability of code.
Technorati Tags: DDD, Domain Driven Design