views:

770

answers:

4

Hi All I seem to have gotten myself into a bit of a confusion of this whole DDD\LinqToSql business. I am building a system using POCOS and linq to sql and I have repositories for the aggregate roots. So, for example if you had the classes Order->OrderLine you have a repository for Order but not OrderLine as Order is the root of the aggregate. The repository has the delete method for deleting the Order, but how do you delete OrderLines? You would have thought you had a method on Order called RemoveOrderLine which removed the line from the OrderLines collection but it also needs to delete the OrderLine from the underlying l2s table. As there isnt a repository for OrderLine how are you supposed to do it?

Perhaps have specialized public repostories for querying the roots and internal generic repositories that the domain objects actually use to delete stuff within the aggregates?

public class OrderRepository : Repository<Order> {
    public Order GetOrderByWhatever();
}

public class Order {
    public List<OrderLines> Lines {get; set;} //Will return a readonly list
    public RemoveLine(OrderLine line) {
        Lines.Remove(line);
        //************* NOW WHAT? *************//
        //(new Repository<OrderLine>(uow)).Delete(line) Perhaps??
        // But now we have to pass in the UOW and object is not persistent ignorant. AAGH!
    }
}

I would love to know what other people have done as I cant be the only one struggling with this.... I hope.... Thanks

+2  A: 

You call the RemoveOrderLine on the Order which call the related logic. This does not include doing changes on the persisted version of it.

Later on you call a Save/Update method on the repository, that receives the modified order. The specific challenge becomes in knowing what has changed in the domain object, which there are several options (I am sure there are more than the ones I list):

  • Have the domain object keep track of the changes, which would include keeping track that x needs to be deleted from the order lines. Something similar to the entity tracking might be factored out as well.
  • Load the persisted version. Have code in the repository that recognizes the differences between the persisted version and the in-memory version, and run the changes.
  • Load the persisted version. Have code in the root aggregate, that gets you the differences given an original root aggregate.
eglasius
I assume this expanation means you are not using an ORM directly on your domain entities. I was hoping to be able to avoid the extra work you describe above, by having linq to sql automatically persist the changes i make to the domain object...
y, that's the real issue. I have been doing the above. I see comments all the time on Nhibernate supporting more advanced scenarios, but I haven't looked at how it plays with this type of scenario (which is not just using POCOs)
eglasius
Thank you; though not terribly ideal (LTS's fault, not your answer) this does seem to confirm my own hunches about how I'd need to do this...
Funka
A: 

First, you should be exposing Interfaces to obtain references to your Aggregate Root (i.e. Order()). Use the Factory pattern to new-up a new instance of the Aggregate Root (i.e. Order()).

With that said, the methods on your Aggregate Root contros access to its related objects - not itself. Also, never expose a complex types as public on the aggregate roots (i.e. the Lines() IList collection you stated in the example). This violates the law of decremeter (sp ck), that says you cannot "Dot Walk" your way to methods, such as Order.Lines.Add().

And also, you violate the rule that allows the client to access a reference to an internal object on an Aggregate Root. Aggregate roots can return a reference of an internal object. As long as, the external client is not allowed to hold a reference to that object. I.e., your "OrderLine" you pass into the RemoveLine(). You cannot allow the external client to control the internal state of your model (i.e. Order() and its OrderLines()). Therefore, you should expect the OrderLine to be a new instance to act upon accordingly.

public interface IOrderRepository
{
  Order GetOrderByWhatever();
}

internal interface IOrderLineRepository
{
  OrderLines GetOrderLines();
  void RemoveOrderLine(OrderLine line);
}

public class Order
{
  private IOrderRepository orderRepository;
  private IOrderLineRepository orderLineRepository;
  internal Order()
  {
    // constructors should be not be exposed in your model.
    // Use the Factory method to construct your complex Aggregate
    // Roots.  And/or use a container factory, like Castle Windsor
    orderRepository = 
            ComponentFactory.GetInstanceOf<IOrderRepository>();
    orderLineRepository = 
            ComponentFactory.GetInstanceOf<IOrderLineRepository>();
  }
  // you are allowed to expose this Lines property within your domain.
  internal IList<OrderLines> Lines { get; set; }  
  public RemoveOrderLine(OrderLine line)
  {
    if (this.Lines.Exists(line))
    {
      orderLineRepository.RemoveOrderLine(line);
    }
  }
}

Don't forget your factory for creating new instances of the Order():

public class OrderFactory
{
  public Order CreateComponent(Type type)
  {
    // Create your new Order.Lines() here, if need be.
    // Then, create an instance of your Order() type.
  }
}

Your external client does have the right to access the IOrderLinesRepository directly, via the interface to obtain a reference of a value object within your Aggregate Root. But, I try to block that by forcing my references all off of the Aggregate Root's methods. So, you could mark the IOrderLineRepository above as internal so it is not exposed.

I actually group all of my Aggregate Root creations into multiple Factories. I did not like the approach of, "Some aggregate roots will have factories for complex types, others will not". Much easier to have the same logic followed throughout the domain modeling. "Oh, so Sales() is an aggregate root like Order(). There must be a factory for it too."

One final note is that if have a combination, i.e. SalesOrder(), that uses two models of Sales() and Order(), you would use a Service to create and act on that instance of SalesOrder() as neither the Sales() or Order() Aggregate Roots, nor their repositories or factories, own control over the SalesOrder() entity.

I highly, highly recommend this free book by Abel Avram and Floyd Marinescu on Domain Drive Design (DDD) as it directly answers your questions, in a shrot 100 page large print. Along with how to more decouple your domain entities into modules and such.

Edit: added more code

eduncan911
Thanks for the response. I am not sure however about having behaviour of an order line exposed through methods on the order. It seems that GetOrderLineVatAmount should be on the line otherwise you have to pass the line into the function on the order. Also, why would Order have a ref to OrderRepos?
It all depends on how you see an "OrderLine". The code above reflects a "Value Object" approach. A Value Object is different than an Entity in the way that it does not have an Identity. Does each OrderLine have an Identity? If yes, does it really if you think about it?
eduncan911
OrderLine.LineNumberOrderLine.DescriptionOrderLine.CostOrderLine.PartNumberThe definition of a Value Object here is not in an identity; but, the summation of its values.But as I noted above in the answer, you can expose IOrderLineRepository() and your remote client does have rights to access.
eduncan911
Interesting. I always considered OrderLine to be an Identity because it needs an ID so a front end can list them and have some sort of identifier if you want to edit one. I guess you can use a composite key of orderid and linenumber (in the case that you are displaying lines from more than 1 order.
Seriously, check out Abel's book: http://www.infoq.com/minibooks/domain-driven-design-quickly It's a free download and really opened my eyes as I was bogged down in the DDD lingo as well. Only take s afew hours to skim through; but, because of the book, I now use Value Objects very often.
eduncan911
I like the idea/concept of having the OrderLine as a value object rather than an entity, but using LTS seems like it wants to deal with _everything_ as an entity. Thus we, as developers, (or at least I do!) make this compromise in order to use the technology. One other thing: I am also not sure, as Steve also mentioned, about Order having a reference to OrderRepos. This seems to nullify any possible notion of having "Persistence Ignorance" (POCOs) in the entity, does it not?
Funka
A: 

As a follow up.... I have switched to using nhibernate (rather than link to sql) but in effect you dont need the repos for the OrderLine. If you just remove the OrderLine from the collection in Order it will just delete the OrderLine from the database (assuming you have done your mapping correctly). As I am swapping out with in-memory repositories, if you want to search for a particular order line (without knowing the order parent) you can write a linq to nhibernate query that links order to orderline where orderlineid = the value. That way it works when querying from the db and from in memory. Well there you go...

Bummer, I was really hoping someone had a better answer to your (very excellent, and very pertinent!) question, without having to abandon Linq to Sql in order to do it!
Funka
A: 

After struggling with this exact issue, I've found the solution. After looking at what the designer generates with l2sl, I realized that the solution is in the two-way associations between order and orderline. An order has many orderlines and an orderline has a single order. The solution is to use two way associations and a mapping attribute called DeleteOnNull(which you can google for complete info). The final thing I was missing was that your entity class needs to register for Add and Remove events from the l2s entityset. In these handlers, you have to set the Order association on the order line to be null. You can see an example of this if you look at some code that the l2s designer generates.

I know this is a frustrating one, but after days of struggling with it, I've got it working.

B Nelson