How do you delete parts of an aggregate?

I seem to have gotten myself into a bit of a confusion of this whole DDDLinqToSql 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


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.

  • First, you should be exposing Interfaces to obtain references to your Aggregate Root (ie Order()). Use the Factory pattern to new-up a new instance of the Aggregate Root (ie 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 (ie 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. Ie, your "OrderLine" you pass into the RemoveLine(). You cannot allow the external client to control the internal state of your model (ie 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, ie 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


    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.

    链接地址: http://www.djcxy.com/p/42370.html

    上一篇: 用于实验性协议设计和开发的工具?

    下一篇: 你如何删除一个聚合的部分?