Yesterday Colin Jack and Jimmy Bogard had a fantastic exchange on Twitter about dealing with collections in your Domain model, and Derick Bailey also blogged about his thoughts on the matter. While I didn't take part in the conversation, I did have a conversation with him at ALT.NET UK about his solution and blog post.
The Twitter conversation lead to a discussion about both proposed solutions with a friend of mine that is starting a Greenfield project and I thought I would join the bigger conversation too. I'm not going to show any code, as Colin and Derick already have great examples.
Custom Collections
Colin's solution involves using custom collection collections. This gives him the flexibility of implementing the interfaces he wants to deliver the functionality he requires: read only, management of adding/removing items, etc.
Personally, I'm not keen on this solution. I've been down this path before a few years ago, and it did not end well. I felt that it lead to a dilution of business rules, split between the Aggregate Root and the custom collection and added needless complexity where a simpler solution would do the same job. I also ran into a few issues with introducing new team members, as most of them went the IList<T> route if left on their own.
Colin writes:
I've found that it just results in my aggregate roots and key entities getting bogged down in lots of boring collection work so I prefer to farm out this work to custom collections.
I don't really agree that the collection work is boring; for me, the collection work will generally lead to apply some form of business rules or satisfying bi-directional relationships.
IEnumerable<T>
This solution is presented in Derick's blog post and is my current preferred solution. We get the functionality we want, a read-only collection exposed on the Entity and we control the interaction with the collection within the Aggregate root and that's where I feel the interaction should be controlled. I also think it's the simplest possible solution to achieve what we are looking for without compromising the Domain. I also find it more discoverable and readable when seen in the context of the Aggregate root instead external in a custom collection.
So...
As Derick noted in his post, "Like everything else in software development, there are multiple ways to solve the same problem." and I completely agree. I've been in my current position for 3months and while the code base and Domain is small, it's already starting to get fairly complex. My work is a debt management and solutions company, and after working with e-commerce/warehousing solutions development and marketing agencies for the past 6 years, the domain is completely alien to me. Encapsulating all the core logic in the Aggregate root has been really beneficial for me so far as it's fairly easy to locate and work with whenever the requirement's change... or when I just get it plain wrong.
Returning to the conversation with my friend, I pushed my own solution at him but made it clear that Colin's solution is worth keeping in mind. As my companies Domain gets bigger and more complex, I wouldn't think twice about refactoring to Colin's solution if it would ease any headaches that manifest with my current solution.