24

I am using repository pattern in a .NET C# application that does not use an ORM. However the issue I am having is how to fill One-to-many List properties of an entity. e.g. if a customer has a list of orders i.e. if the Customer class has a List property called Orders and my repository has a method called GetCustomerById, then?

  • Should I load the Orders list within the GetCustomerById method?
  • What if the Order itself has another list property and so on?
  • What if I want to do lazy loading? Where would I put the code to load the Orders property in customer? Inside the Orders property get{} accessor? But then I would have to inject repository into the domain entity? which I don't think is the right solution.

This also raises questions for Features like Change Tracking, Deleting etc? So i think the end result is can I do DDD without ORM ?

But right now I am only interested in lazy loading List properties in my domain entities? Any idea?

Nabeel

I am assuming this is a very common issue for anyone not using an ORM in a Domain Driven Design? Any idea?

nabeelfarid
  • 4,156
  • 5
  • 42
  • 60
  • 1
    This may be of use: http://codebetter.com/petervanooijen/2009/03/17/ddd-and-repositories-without-nhibernate-but-with-lazy-loading/ – Paolo Feb 16 '11 at 15:02
  • 1
    Sounds like you need an ORM. Most ORMs have these features. It is a heavy investment to write / maintain your own. – rcravens Feb 16 '11 at 15:09
  • You are asking can I do this without ORM, but instead you try to make your own ORM. – Alex Burtsev Feb 18 '11 at 05:36
  • I have same questions as you, and I have blogged about it http://solveme.wordpress.com/2009/11/11/ddd-without-any-orm-tool-is-it-possible/ Wish some day DDD community would release a sample application that works without any ORM – Sudhir N Apr 12 '12 at 05:49
  • @Paolo the link in your comment is broken – half of a glazier Mar 14 '23 at 08:40

3 Answers3

15

can I do DDD without ORM ?

Yes, but an ORM simplifies things.

To be honest I think your problem isn't to do with whether you need an ORM or not - it's that you are thinking too much about the data rather than behaviour which is the key for success with DDD. In terms of the data model, most entities will have associations to most another entities in some form, and from this perspective you could traverse all around the model. This is what it looks like with your customer and orders and perhaps why you think you need lazy loading. But you need to use aggregates to break these relationships up into behavioural groups.

For example why have you modelled the customer aggregate to have a list of order? If the answer is "because a customer can have orders" then I'm not sure you're in the mindset of DDD.

What behaviour is there that requires the customer to have a list of orders? When you give more thought to the behaviour of your domain (i.e. what data is required at what point) you can model your aggregates based around use cases and things become much clearer and much easier as you are only change tracking for a small set of objects in the aggregate boundary.

I suspect that Customer should be a separate aggregate without a list of orders, and Order should be an aggregate with a list of order lines. If you need to perform operations on each order for a customer then use orderRepository.GetOrdersForCustomer(customerID); make your changes then use orderRespository.Save(order);

Regarding change tracking without an ORM there are various ways you can do this, for example the order aggregate could raise events that the order repository is listening to for deleted order lines. These could then be deleted when the unit of work completed. Or a slightly less elegant way is to maintain deleted lists, i.e. order.DeletedOrderLines which your repository can obviously read.

To Summarise:

  • I think you need to think more about behaviour than data
  • ORM's make life easier for change tracking, but you can do it without one and you can definitely do DDD without one.

EDIT in response to comment:

I don't think I'd implement lazy loading for order lines. What operations are you likely to perform on the order without needing the order lines? Not many I suspect.

However, I'm not one to be confined to the 'rules' of DDD when it doesn't seem to make sense, so... If in the unlikely scenario that there are a number of operations performed on the order object that didn't require the order lines to be populated AND there are often a large number of order lines associated to an order (both would have to be true for me to consider it an issue) then I'd do this:

Have this private field in the order object:

private Func<Guid, IList<OrderLine>> _lazilyGetOrderLines;

Which would be passed by the order repository to the order on creation:

Order order = new Order(this.GetOrderLines);

Where this is a private method on the OrderRepository:

private IList<OrderLine> GetOrderLines(Guid orderId)
{
    //DAL Code here

}

Then in the order lines property could look like:

public IEnumberable<OrderLine> OrderLines
{ 
    get 
    {
         if (_orderLines == null)
            _orderLines = _lazilyGetOrderLines(this.OrderId);

         return _orderLines;
    }
}

Edit 2

I've found this blog post which has a similar solution to mine but slightly more elegant:

http://thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance

David Masters
  • 8,069
  • 2
  • 44
  • 75
  • Hi David, I completely agree with you regarding the concepts of Behaviour and Aggregates. My example might not be a good one that led you to this explanation. However if we consider a slightly better example Order with OrderLines, then how would you implement lazy loading? – nabeelfarid Feb 16 '11 at 17:30
  • 1
    @nabeelfarid I've updated my answer to include an example of how I'd implement lazy loading without an ORM. It's obviously not as elegant as using an ORM, and as I say, I don't think I'd do it for your scenario. But it provides lazy loading without the need for the order aggregate to reference the repository directly. – David Masters Feb 16 '11 at 19:00
  • 1
    With an ODBMS doing DDD without an ORM is even easier :) – Stephan Eggermont Feb 16 '11 at 22:13
  • 1
    @Stephan Eggermont Yep - I'm working on a project that uses CQRS with event sourcing... no data access code required! It's awesome :) – David Masters Feb 16 '11 at 22:34
  • I have referred your idea in http://stackoverflow.com/questions/20739446/business-rule-split-among-two-classes – LCJ Dec 23 '13 at 09:38
4

1) Should I load the Orders list within the GetCustomerById method?

It's probably a good idea to separate the order mapping code from the customer mapping code. If you're writing your data access code by hand, calling that mapping module from the GetCustomerById method is your best option.

2) What if the Order itself has another list property and so on?

The logic to put all those together has to live somewhere; the related aggregate repository is as good a place as any.

3) What if I want to do lazy loading? Where would I put the code to load the Orders property in customer? Inside the Orders property get{} accessor? But then I would have to inject repository into the domain entity? which I don't think is the right solution.

The best solution I've seen is to make your repository return subclassed domain entities (using something like Castle DynamicProxy) - that lets you maintain persistence ignorance in your domain model.

Jeff Sternal
  • 47,787
  • 8
  • 93
  • 120
  • Hi Jeff.. after reading about Castle Dynamic Proxy, it sounds like a good idea.. However I can't seem to find any good examples of how to actually use it. – nabeelfarid Feb 16 '11 at 18:25
1

Another possible answer is to create a new Proxy object that inherits from Customer, call it CustomerProxy, and handle the lazy load there. All this is pseudo-code, so it's to give you an idea, not just copy and paste it for use.

Example:

public class Customer
{
    public id {get; set;}
    public name {get; set;}
    etc...
    public virtual IList<Order> Orders {get; protected set;}
}

here is the Customer "proxy" class... this class does not live in the business layer, but in the Data Layer along with your Context and Data Mappers. Note that any collections you want to make lazy-load you should declare as virtual (I believe EF 4.0 also requires you to make props virtual, as if spins up proxy classes at runtime on pure POCO's so the Context can keep track of changes)

internal sealed class CustomerProxy : Customer
{
   private bool _ordersLoaded = false;
    public override IList<Order> Orders
    {
        get
        {
            IList<Order> orders = new List<Order>();
            if (!_ordersLoaded)
            {
                //assuming you are using mappers to translate entities to db and back
                //mappers also live in the data layer
                CustomerDataMapper mapper = new CustomerDataMapper();
                orders = mapper.GetOrdersByCustomerID(this.ID);
                _ordersLoaded = true;

                // Cache Cases for later use of the instance
                base.Orders = orders;
            }
            else
            {
                orders = base.Orders;
            }
            return orders;
        }
   }
}

So, in this case, our entity object, Customer is still free from database/datamapper code calls, which is what we want... "pure" POCO's. You've delegated the lazy-load to the proxy object which lives in the Data layer, and does instantiate data mappers and make calls.

there is one drawback to this approach, which is calling client code can't override the lazy load... it's either on or off. So it's up to you in your particular usage circumstance. If you know maybe 75% of the time you'll always needs the Orders of a Customer, than lazy-load is probably not the best bet. It would be better for your CustomerDataMapper to populate that collection at the time you get a Customer entity.

Again, I think NHibernate and EF 4.0 both allow you to change lazy-loading characteristics at runtime, so, as per usual, it makes sense to use an ORM, b/c a lot of functionality is provided for you.

If you don't use Orders that often, then use a lazy-load to populate the Orders collection.

I hope that this is "right", and is a way of accomplishing lazy-load the correct way for Domain Model designs. I'm still a newbie at this stuff...

Mike

Michael McCarthy
  • 1,502
  • 3
  • 18
  • 45