领域驱动设计 (Linq to SQL) - 如何删除聚合的某些部分?
我似乎对整个 DDD\LinqToSql 业务感到有点困惑。 我正在使用 POCOS 和 linq to sql 构建一个系统,并且我有聚合根的存储库。 因此,例如,如果您有 Order->OrderLine 类,那么您就有了 Order 的存储库,但没有 OrderLine,因为 Order 是聚合的根。 存储库有删除Order的delete方法,但是如何删除OrderLines呢? 您可能认为您有一个名为RemoveOrderLine 的Order 方法,该方法从OrderLines 集合中删除该行,但它还需要从底层l2s 表中删除OrderLine。 由于没有 OrderLine 存储库,您该怎么做?
也许有专门的公共存储库用于查询域对象实际用来删除聚合中内容的根和内部通用存储库?
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 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您可以在订单上调用RemoveOrderLine,从而调用相关逻辑。 这不包括对其持久版本进行更改。
稍后,您在存储库上调用保存/更新方法,该方法接收修改后的订单。 具体的挑战在于了解域对象中发生了什么变化,有几个选项(我确信比我列出的要多):
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):
首先,您应该公开接口以获得对聚合根(即 Order())的引用。 使用工厂模式来新建聚合根的新实例(即 Order())。
话虽如此,聚合根上的方法控制对其相关对象(而不是其本身)的访问。 另外,切勿将复杂类型公开为聚合根(即您在示例中所述的 Lines() IList 集合)。 这违反了递减律 (sp ck),即您不能“点走”到方法,例如 Order.Lines.Add()。
而且,您还违反了允许客户端访问聚合根上内部对象的引用的规则。 聚合根可以返回内部对象的引用。 只要外部客户端不允许持有对该对象的引用。 即,您传递给RemoveLine() 的“OrderLine”。 您不能允许外部客户端控制模型的内部状态(即 Order() 及其 OrderLines())。 因此,您应该期望 OrderLine 是一个新实例,以便进行相应的操作。
不要忘记用于创建 Order() 新实例的工厂:
您的外部客户端确实有权通过接口直接访问 IOrderLinesRepository,以获取聚合根中值对象的引用。 但是,我尝试通过强制我的引用全部脱离聚合根的方法来阻止这种情况。 因此,您可以将上面的 IOrderLineRepository 标记为内部,这样它就不会暴露。
实际上,我将所有聚合根创作分组到多个工厂中。 我不喜欢“一些聚合根将拥有复杂类型的工厂,而其他则不会”的方法。 在整个领域建模中遵循相同的逻辑要容易得多。 “哦,所以 Sales() 是一个像 Order() 一样的聚合根。它也必须有一个工厂。”
最后一点是,如果有一个组合,即 SalesOrder(),它使用 Sales() 和 Order() 两个模型,您将使用一个服务来创建 SalesOrder() 的实例并对其进行操作,因为 Sales()或 Order() 聚合根,或其存储库或工厂,都拥有对 SalesOrder() 实体的控制权。
我强烈推荐这本免费的书,作者是 Abel Avram 和 Floyd Marinescu域驱动设计 (DDD),因为它以 100 页的大字体直接回答您的问题。 以及如何将域实体更多地解耦为模块等。
编辑:添加更多代码
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.
Don't forget your factory for creating new instances of the Order():
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
在与这个确切的问题作斗争之后,我找到了解决方案。 在查看了设计器使用 l2sl 生成的内容后,我意识到解决方案在于订单和订单行之间的双向关联。 一个订单有多个订单行,一个订单行有一个订单。 解决方案是使用两种方式关联和一个名为“DeleteOnNull”的映射属性(您可以在 google 上搜索完整信息)。 我遗漏的最后一件事是您的实体类需要注册 l2s 实体集中的添加和删除事件。 在这些处理程序中,您必须将订单行上的订单关联设置为空。 如果您查看 l2s 设计器生成的一些代码,您可以看到这样的示例。
我知道这是一件令人沮丧的事情,但经过几天的努力,我已经成功了。
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.
作为后续....
我已改用 nhibernate (而不是链接到 sql),但实际上您不需要 OrderLine 的存储库。 如果您只是从 Order 集合中删除 OrderLine,它只会从数据库中删除 OrderLine(假设您已正确完成映射)。
当我换出内存存储库时,如果您想搜索特定订单行(不知道订单父级),您可以编写一个 linq to nhibernate 查询,将订单链接到 orderline,其中 orderlineid = 值。 这样,它在从数据库和内存中查询时就可以工作。 好吧,你去吧...
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...