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
,其中两个Car
和Engine
的总根源,但发动机是不是汽车的总的一部分,你可以不使用任何方法对引擎进行更改Car
(反之亦然).
3)在域驱动设计中,您不必使所有类聚合根或聚合子代.您只需要聚合根来封装一组有凝聚力的类之间的复杂交互.Customer
你提出的类听起来根本不应该是聚合根 - 只是一个包含引用Contract
和Address
聚合的常规类.
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知道存储地址的变化.
归档时间: |
|
查看次数: |
7894 次 |
最近记录: |