域驱动设计:如何访问聚合根的子节点

Chr*_*ris 25 .net nhibernate design-patterns domain-driven-design repository

如果我有一个Order类作为聚合根和1000个行项.

如何只加载1000个订单项中的一个?据我所知,订单项只能通过Order类访问,并具有"本地"标识.我还会在OrderRepository中创建一个像"GetLineItemById"的存储库方法吗?

编辑评论答案: 目前我不认为有一个不可变的孩子是合理的.如果我的Customer类包含多个地址,合同以及更多子集合,该怎么办?我想要执行CRUD方法的庞大实体.

我会

public class Customer
{
    public IEnumerable<Address> Addresses { get; private set; }
    public IEnumerable<Contracts> Contracts { get; private set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果用户纠正地址或合同财产的街道,我是否必须这样做?

public class Customer
{
    public void SetStreetOfAddress(Address address, street){}

    public void SetStreetNumberOfAddress(Address address, streetNumber){}
}
Run Code Online (Sandbox Code Playgroud)

然后,客户类将充满子操作方法.所以我宁愿这样做

addressInstance.Street = "someStreet";
Run Code Online (Sandbox Code Playgroud)

我想我误解了整个概念.. :)

Jef*_*nal 20

1)通过简单的只读属性或get方法访问聚合根的子节点没有任何问题.

重要的是确保与子项的所有交互都由聚合根调解,以便有一个可预测的单一位置来保证不变量.

所以Order.LineItems很好,只要它返回一个(公共)不可变对象的不可变集合.同样地Order.LineItems[id].有关示例,请参阅规范的Evans批准的ddd示例的源代码,其中聚合根Cargo类公开其若干子级,但子级entites是不可变的.

2)聚合根可以包含对其他聚合根的引用,它们只是不能相互改变.

如果你有"蓝皮书"(领域驱动设计),请参阅第117页上的例子,这表明你可能怎么也得Car.Engine,其中两个CarEngine的总根源,但发动机是不是汽车的总的一部分,你可以不使用任何方法对引擎进行更改Car(反之亦然).

3)在域驱动设计中,您不必使所有类聚合根或聚合子代.您只需要聚合根来封装一组有凝聚力的类之间的复杂交互.Customer你提出的类听起来根本不应该是聚合根 - 只是一个包含引用ContractAddress聚合的常规类.

  • 关于这一点,人们不能说教 - 如果有经证实的性能问题,所有的赌注都会被取消,你可以做你需要的东西来解决问题; 在这些情况下,没有理由成为DDD纯粹主义者.另一方面,不要假设加载1000个项目会严重损害性能:如果您的数据库设计合理且负载合理,那么这应该不是问题.毕竟,你不是在写Twitter,或者你不会使用NHibernate! (4认同)
  • 我有一个很大的问题.要将子行为放在根目录中,您必须在根目录上有一个方法,而在子目录上有另一个方法.你如何保护儿童方法免受不必要的使用?如果你把它作为内部,它可以被同一个组件中的任何其他东西使用?你如何强制通过root进行调用? (4认同)
  • 我读了蓝皮书。但我的印象是每个类(或更好的实例)都必须在一个集合中。你在哪里读到可能有任何聚合之外的类/实例?在我看来,这将使它们聚合和聚合根。 (2认同)

G-W*_*Wiz 10

当你说"如何只加载 1000个订单项中的一个?"时加载?你的意思是"从数据库加载"?换句话说,如何从数据库中只加载聚合根的一个子实体?

这有点复杂,但您可以让您的存储库返回聚合根的派生,其根目录是延迟加载的.例如

namespace Domain
{
    public class LineItem
    {
        public int Id { get; set; }
        // stuff
    }

    public class Order
    {
        public int Id { get; set; }

        protected ReadOnlyCollection<LineItem> LineItemsField;
        public ReadOnlyCollection<LineItem> LineItems { get; protected set; }
    }

    public interface IOrderRepository
    {
        Order Get(int id);
    }
}

namespace Repositories
{
    // Concrete order repository
    public class OrderRepository : IOrderRepository
    {
        public Order Get(int id)
        {
            Func<IEnumerable<LineItem>> getAllFunc = () =>
                {
                    Collection<LineItem> coll;
                    // { logic to build all objects from database }
                    return coll;
                };
            Func<int, LineItem> getSingleFunc = idParam =>
                {
                    LineItem ent;
                    // { logic to build object with 'id' from database }
                    return ent;
                };

            // ** return internal lazy-loading derived type **
            return new LazyLoadedOrder(getAllFunc, getSingleFunc);
        }
    }

    // lazy-loading internal derivative of Order, that sets LineItemsField
    // to a ReadOnlyCollection constructed with a lazy-loading list.
    internal class LazyLoadedOrder : Order
    {
        public LazyLoadedOrder(
            Func<IEnumerable<LineItem>> getAllFunc,
            Func<int, LineItem> getSingleFunc)
        {
            LineItemsField =
                new ReadOnlyCollection<LineItem>(
                    new LazyLoadedReadOnlyLineItemList(getAllFunc, getSingleFunc));
        }
    }

    // lazy-loading backing store for LazyLoadedOrder.LineItems
    internal class LazyLoadedReadOnlyLineItemList : IList<LineItem>
    {
        private readonly Func<IEnumerable<LineItem>> _getAllFunc;
        private readonly Func<int, LineItem> _getSingleFunc;

        public LazyLoadedReadOnlyLineItemList(
            Func<IEnumerable<LineItem>> getAllFunc,
            Func<int, LineItem> getSingleFunc)
        {
            _getAllFunc = getAllFunc;
            _getSingleFunc = getSingleFunc;
        }

        private List<LineItem> _backingStore;
        private List<LineItem> GetBackingStore()
        {
            if (_backingStore == null)
                _backingStore = _getAllFunc().ToList(); // ** lazy-load all **
            return _backingStore;
        }

        public LineItem this[int index]
        {
            get
            {
                if (_backingStore == null)        // bypass GetBackingStore
                    return _getSingleFunc(index); // ** lazy-load only one from DB **

                return _backingStore[index];
            }
            set { throw new NotSupportedException(); }
        }

        // "getter" implementations that use lazy-loading
        public IEnumerator<LineItem> GetEnumerator() { return GetBackingStore().GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public bool Contains(LineItem item) { return GetBackingStore().Contains(item); }

        public void CopyTo(LineItem[] array, int arrayIndex) { GetBackingStore().CopyTo(array, arrayIndex); }

        public int Count { get { return GetBackingStore().Count; } }

        public bool IsReadOnly { get { return true; } }

        public int IndexOf(LineItem item) { return GetBackingStore().IndexOf(item); }

        // "setter" implementations are not supported on readonly collection
        public void Add(LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void Clear() { throw new NotSupportedException("Read-Only"); }

        public bool Remove(LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void Insert(int index, LineItem item) { throw new NotSupportedException("Read-Only"); }

        public void RemoveAt(int index) { throw new NotSupportedException("Read-Only"); }
    }
}
Run Code Online (Sandbox Code Playgroud)

OrderRepository.Get(int)调用者会收到一些实际上只是Order对象的东西,但实际上是一个LazyLoadedOrder.当然,为此,您的聚合根必须提供一个或两个虚拟成员,并围绕这些扩展点进行设计.

编辑以解决问题更新

在地址的情况下,我会将其视为一个值对象,即一起被视为单个值的不可变数据组合.

public class Address
{
  public Address(string street, string city)
  {
    Street = street;
    City = city;
  }
  public string Street {get; private set;}
  public string City {get; private set;}
}
Run Code Online (Sandbox Code Playgroud)

然后,为了修改聚合,您需要创建一个新的Address实例.这类似于DateTime的行为.您还可以向Address添加方法方法,SetStreet(string)但这些方法应返回Address的新实例,就像DateTime的方法返回DateTime的新实例一样.

在您的情况下,不可变的Address值对象必须与Addresses集合的某种观察相结合.一种简单明了的技术是在单独的集合中跟踪添加和删除的AddressValues.

public class Customer
{
    public IEnumerable<Address> Addresses { get; private set; }

    // backed by Collection<Address>
    public IEnumerable<Address> AddedAddresses { get; private set; } 

    // backed by Collection<Address>
    public IEnumerable<Address> RemovedAddresses { get; private set; }

    public void AddAddress(Address address)
    {
      // validation, security, etc
      AddedAddresses.Add(address);
    }

    public void RemoveAddress(Address address)
    {
      // validation, security, etc
      RemovedAddresses.Add(address);
    }

    // call this to "update" an address
    public void Replace(Address remove, Address add)
    {
      RemovedAddresses.Add(remove);
      AddedAddresses.Add(add);
    }
}
Run Code Online (Sandbox Code Playgroud)

或者你也可以用地址支持地址ObservableCollection<Address>.

这确实是一个纯DDD解决方案,但你提到了NHibernate.我不是NHibernate专家,但我想你必须添加一些代码让NHibernate知道存储地址的变化.