简体   繁体   English

DDD 关于创建和升级具有子实体的聚合根及其持久性的问题

[英]DDD Questions about creation and upgrade of aggregate root with child entitities and its persistence

In the application I am working on, the user can start an Order without Order Lines, but the order cannot be persisted without order lines and other properties where other aggregates are referenced (which in the database are foreign keys which do not accept null values, this is why).在我正在处理的应用程序中,用户可以在没有订单行的情况下启动订单,但是如果没有订单行和引用其他聚合的其他属性(在数据库中是不接受 null 值的外键,则订单无法持久化,这就是为什么)。 This order is kept in cache until the user confirms it, that is the moment to persist it.这个订单被保存在缓存中,直到用户确认它,这是持久化它的时刻。

I think creating an aggregate root via its constructor leaving it in an inconsistent state regarding its persistence should not be possible.我认为通过其构造函数创建聚合根,使其在不一致的 state 中关于其持久性是不可能的。 Is there something wrong with my thinking?我的想法有问题吗? because if this is possible, how do I handle the rules for its persistence?因为如果这是可能的,我该如何处理它的持久性规则? In the application service?在应用服务? In your repository?在您的存储库中? via a function (maybe "IsValidToPersist") in the aggregate root?通过聚合根中的 function(可能是“IsValidToPersist”)?

I see sense in the fact that the Order can be created without all the properties it needs for its persistence , in this case.在这种情况下,我认为可以在没有持久性所需的所有属性的情况下创建 Order 是有道理的。 I usually check for these needs in the aggregate root's constructors, throwing exceptions if the aggregate root is not ready to persist, among other business rules.我通常在聚合根的构造函数中检查这些需求,如果聚合根还没有准备好持久化,则抛出异常,以及其他业务规则。 I guess if this can be so, then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService" where it would persist.我想如果可以的话,那么一定有像“CreateOrderService”、“AddOrderLineService”和“SaveOrderService”/“AddOrderService”这样的应用程序服务会持续存在。 And not just an "AddOrderService" service where it is created and persisted at the same time.不仅仅是一个“AddOrderService”服务,它同时被创建和持久化。

To update an Order Line, should I create an "UpdateOrderLineService" service that will find the Order, update its order line through its functions?要更新订单行,我是否应该创建一个“UpdateOrderLineService”服务来查找订单,通过其函数更新其订单行? Or should it be all that it takes to update an Oder in a single service "UpdateOrderService"?或者是否应该在单个服务“UpdateOrderService”中更新 Oder? . . I think this second option complicates some things.我认为第二个选项使一些事情复杂化。 For example, it is difficult for the service to know which order lines to add, update and delete.例如,服务很难知道要添加、更新和删除哪些订单行。 Although that could be solved with a function in Order like "ChangeOrderLines" and simply replace them.虽然这可以用 function 来解决,比如“ChangeOrderLines”,然后简单地替换它们。 In the first option , should the Order be persisted with each update (or even addition or deletion) of order lines?在第一个选项中,订单是否应该随着订单行的每次更新(甚至添加或删除)而持久化? I can think of other options such as: return the updated Order or return the updated (or added) Order Lines (although this is not such a good idea).我可以考虑其他选项,例如:返回更新的订单或返回更新的(或添加的)订单行(尽管这不是一个好主意)。

My question, really is: is it convenient that exists services to handle the child entities of the aggregate root?我的问题,真的是:存在服务来处理聚合根的子实体是否方便?

Edit (specific information about app):编辑(有关应用程序的特定信息):

My response to @rascio contains more useful specific details for the application:我对@rascio 的回复包含该应用程序更有用的具体细节:

The application has a tour to "create" a Order.该应用程序有一个“创建”订单的游览。 Screen after screen the order is "created".一屏又一屏,订单被“创建”。 The first screen the user must write a number, without this number the order can't be created.第一个屏幕用户必须写一个数字,没有这个数字就不能创建订单。 Must be a valid number that it is persisted in db (like discount codes, for example).必须是在 db 中持久保存的有效数字(例如折扣代码)。 After that the user can chose the products and later, other things.之后,用户可以选择产品,然后再选择其他东西。 I need check and save the data user write or choose for each screen.我需要检查并保存用户为每个屏幕写入或选择的数据。 The last screen it's an abstract of order, where user can change things and confirm the order.最后一个屏幕是订单的摘要,用户可以在其中更改内容并确认订单。 It's a desktop application.这是一个桌面应用程序。 Without side effects.无副作用。 Really not a order, but is very similar.真的不是一个命令,而是非常相似。

Edit 2 (more specific information about app):编辑 2(有关应用程序的更多具体信息):

There are two ways to create the order.有两种方法可以创建订单。 One going through the tour: introducing the number, the products and the other things.一个经历:介绍号码,产品和其他东西。 Screen after screen.一屏又一屏。 Once the tour is finished, the summary screen will be displayed where the user can modify and confirm it.游览完成后,将显示摘要屏幕,用户可以在其中修改和确认。 Or you can start creating it on this summary screen from scratch.或者您可以从头开始在此摘要屏幕上创建它。 You can also go to this summary screen from any of the other tour screens.您还可以从任何其他游览屏幕 go 到此摘要屏幕。 If the user had chosen only the number and the products in the tour, these will be the ones that appear in the summary and not the other things, which they can complete in the summary.如果用户在游览中只选择了数量和产品,这些将是出现在摘要中的那些,而不是其他他们可以在摘要中完成的东西。

It seems to me that your domain rules want your order to not have order line in certain states.在我看来,您的域规则希望您的订单在某些州没有订单行。

From your latest update it seems that to be created, your Order aggregate, has only the invariant related to the number the user needs to put.从您的最新更新看来,要创建的 Order 聚合仅具有与用户需要输入的数字相关的不变量。

This is part of your domain logic.这是您的域逻辑的一部分。

This order is kept in cache until the user confirms it, that is the moment to persist it.这个订单被保存在缓存中,直到用户确认它,这是持久化它的时刻。

This is not a normal "software" cache, this is your domain want to maintain your order with relaxed invariants, until the user do something.这不是一个普通的“软件”缓存,这是你的域想要用宽松的不变量来维护你的顺序,直到用户做某事。

When you "update" an aggregate it can change its internal state, and on different internal states different invariants can be applied.当您“更新”聚合时,它可以更改其内部 state,并且可以在不同的内部状态上应用不同的不变量。

I see sense in the fact that the Order can be created without all the properties it needs for its persistence, in this case.在这种情况下,我认为可以在没有持久性所需的所有属性的情况下创建 Order 是有道理的。

this is totally right to me.这对我来说完全正确。

I guess if this can be so, then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService" where it would persist.我想如果可以的话,那么一定有像“CreateOrderService”、“AddOrderLineService”和“SaveOrderService”/“AddOrderService”这样的应用程序服务会持续存在。

more than services I would speak in terms of your domain, these seems to be the commands your aggregate should support.就您的域而言,我想说的不仅仅是服务,这些似乎是您的聚合应该支持的命令。

Looking at what your wrote, it seems to me that a valid sequence of commands can be:查看您所写的内容,在我看来,有效的命令序列可以是:

CreateOrder(ANumber) -> AddProduct(Product) -> AddProduct(Product) -> AddOtherThings(OtherThings)

After each command the state of the aggregate is persisted.在每个命令之后,聚合的 state 被持久化。
Each command needs its own validation, so:每个命令都需要自己的验证,因此:

  • CreateOrder needs a valid number (not empty, etc...) CreateOrder需要一个有效的数字(不为空等...)
  • AddOrderLine Valid product, and the order should be finalized (you didn't write about this, but I'm supposing after you AddOtherThings or something else is done you cannot add any more products to the order AddOrderLine有效产品,订单应该完成(你没有写这个,但我想在你AddOtherThings或其他事情完成后你不能再向订单添加任何产品
  • AddOtherThings valid input and at least one product should be part of the order AddOtherThings有效输入并且至少一种产品应该是订单的一部分

(This is an example, commands should be modeled also depending what you wat to offer to the user in terms of a "single update persisted atomically", so also an UpdateOrderLines(OrderLine...)) can make sense in certain cases.) (这是一个示例,还应根据您向用户提供的“原子持久的单个更新”对命令进行建模,因此在某些情况下UpdateOrderLines(OrderLine...))也是有意义的。)

how do I handle the rules for its persistence?我如何处理它的持久性规则? In the application service?在应用服务? In your repository?在您的存储库中?

What I want to stress with this response is the fact that an aggregate can have multiple states, and each state can have its own invariants.我想用这个回应强调一个事实,一个聚合可以有多个状态,每个 state 可以有自己的不变量。 The role of the Entity/Aggregate in DDD is to maintain true these invariants, and disallow for updates that can move the data into an inconsistent state. DDD 中实体/聚合的作用是维护这些不变量的真实性,并且不允许将数据移动到不一致的 state 中的更新。 So these rules should be handled where you are handling the logic to change the aggregate internal state.因此,这些规则应该在您处理逻辑以更改聚合内部 state 的地方处理。

For your last question:对于你的最后一个问题:

My question, really is: is it convenient that exists services to handle the child entities of the aggregate root?我的问题,真的是:存在服务来处理聚合根的子实体是否方便?

These depends on what you mean by "services", application services?这些取决于您所说的“服务”,应用程序服务是什么意思? Domain services?域服务? Something else services?还有什么服务? I hate the "service" word as it doesn't have a precise meaning:)我讨厌“服务”这个词,因为它没有确切的含义:)
By the way as software components to do, this depends on your generic architecture approach, but what DDD suggests is that all mutations to an aggregate "pass" for the aggregate root, because to enforce the invariants you could need the state of the entire aggregate, for example an OrderLine entity can be updated only if the Order entity (aggregate root) is in a certain state.顺便说一句,作为软件组件,这取决于您的通用架构方法,但 DDD 建议的是聚合根的聚合“通过”的所有突变,因为要强制执行不变量,您可能需要整个聚合的 state ,例如,仅当 Order 实体(聚合根)位于某个 state 中时,才能更新 OrderLine 实体。
It doesn't matter what software components you do, the important thing is that they treat you entire aggregate as a single atomic unit of data, like it doesn't support concurrent modification, so that at each mutation invariants regarding the data of the aggregate can be checked.无论您使用什么软件组件,重要的是它们将您的整个聚合视为单个原子数据单元,就像它不支持并发修改一样,因此在每个突变不变量关于聚合的数据可以检查。

I think your confusion stems from an assumption that your DDD entities can directly support a user workflow.我认为您的困惑源于您的 DDD 实体可以直接支持用户工作流的假设。 This is not the case.不是这种情况。 The user workflow is a UI concern, not your aggregate's.用户工作流程是 UI 问题,而不是您的聚合问题。

the user can start an Order without Order Lines, but the order cannot be persisted without order lines用户可以在没有订单行的情况下启动订单,但在没有订单行的情况下无法保留订单

The first part of that requirement is a User experience requirement.该要求的第一部分是用户体验要求。 The second part is an invariant for your domain model.第二部分是您的域 model 的不变量。

"But this means that to support the UI I'd need to create similar objects as I already have in the domain model??!" “但这意味着要支持 UI,我需要创建与域 model 中已有的类似对象??!”

Yup!是的!

This order is kept in cache until the user confirms it, that is the moment to persist it.这个订单被保存在缓存中,直到用户确认它,这是持久化它的时刻。

Fine, but that cache does not belong in your domain model.很好,但是该缓存不属于您的域 model。 It belongs on the client.它属于客户端。

The services I might expect to see in your Bounded Context to support Order handling would include:我可能希望在您的有界上下文中看到支持订单处理的服务包括:

  • CreateOrder (a fully consistent order only can be provided, not an empty one) CreateOrder(只能提供完全一致的顺序,不能提供空的)
  • AddOrderLine (add a line to an existing order) AddOrderLine(向现有订单添加一行)
  • UpdateOrderLine (change the details of an existing order line) UpdateOrderLine(更改现有订单行的详细信息)
  • RemoveOrderLine (would automatically delete the order if it is the only line left -- assuming your domain rules are that an order cannot exists without lines). RemoveOrderLine(如果它是唯一剩下的行,将自动删除订单 - 假设您的域规则是没有行的订单不能存在)。
  • RemoveOrder (deletes the order entity and all of its lines). RemoveOrder(删除订单实体及其所有行)。

but it would NOT include:但它不包括:

  • SaveOrder保存订单

All of the previous services would be persisting at the completion of each command.所有先前的服务将在每个命令完成时持续存在。

is it convenient that exists services to handle the child entities of the aggregate root?存在服务来处理聚合根的子实体是否方便?

Yes, as I have outlined above.是的,正如我上面所概述的。 But that does not extend to helping the user through a 'work-in-progress' during which entities may violate your domain invariants.但这并没有扩展到通过“正在进行的工作”来帮助用户,在此期间实体可能会违反您的域不变量。 That's a task for the client.这是客户的任务。

I would suggest not having foreign key constraints which cross aggregate boundaries.我建议不要有跨越聚合边界的外键约束。 Enforcing such constraints in the DB infrastructure means that at least one of these two things is true:在数据库基础设施中强制执行此类约束意味着以下两件事中的至少一项是正确的:

  • the constraint isn't being enforced in your domain logic, in which case your domain logic isn't fully capturing your domain约束未在您的域逻辑中强制执行,在这种情况下,您的域逻辑没有完全捕获您的域
  • the constraint is being enforced in your domain logic, in which case why are you duplicating things?约束正在您的域逻辑中强制执行,在这种情况下,您为什么要复制内容?

It's also the case that if you have things like ON DELETE CASCADE as part of that constraint, you're violating the principle that all access to the aggregate is through the root (because the DB definitely isn't going through the root), but this might not necessarily be a problem if there's no operations which could lead to that sort of "behind your back" modification of aggregates.同样的情况是,如果您将ON DELETE CASCADE之类的东西作为该约束的一部分,那么您就违反了对聚合的所有访问都是通过根目录的原则(因为数据库肯定不会通过根目录),但是如果没有可能导致聚合的那种“背后”修改的操作,这可能不一定是问题。

In this case, it's clear that there's a disagreement between your domain logic (which says "an order without lines is OK") and your DB infra (which says "nope").在这种情况下,很明显您的域逻辑(表示“没有行的订单是可以的”)和您的数据库基础设施(表示“不”)之间存在分歧。 If you can't drop the foreign key constraint, then you should duplicate the constraint in your domain logic and not allow an order to be constructed without lines.如果您不能删除外键约束,那么您应该在域逻辑中复制该约束,并且不允许在没有行的情况下构建订单。 That may entail an OrderInProgress aggregate which might not have the same invariants as an Order.这可能需要一个 OrderInProgress 聚合,它可能不具有与 Order 相同的不变量。

There's two main ways to model this IMO: model 这个IMO有两种主要方法:

  1. An Order always needs some order lines or else it's not an Order . Order总是需要一些订单行,否则就不是Order In that case you are making it an invariant of the Order to even exist, so that thing "without order lines" is NOT an Order .在这种情况下,您将使其成为Order甚至存在的不变量,因此“没有订单行”的东西不是Order Perhaps it's a ShoppingCart ?也许它是一个ShoppingCart Perhaps it's an OrderDraft ?也许它是OrderDraft It would be modeled as a concept "similar" to an Order , from which the real Order would most likely be created.它将被建模为与Order “相似”的概念,最有可能从中创建真正的Order

  2. You make the invariant dependent not only on the mere Order 's existance, but it's state.您使不变量不仅取决于Order的存在,而且取决于 state。 For instance, you could see it as an accepted order must have line items .例如,您可以将其视为已接受的订单必须有行项目 In that case perhaps the order starts as in progress and needs line items only to transition towards the accepted/submitted/etc.在这种情况下,订单可能会以正在进行的方式开始,并且只需要行项目才能过渡到接受/提交/等。 state. state。 In that case the rules would apply upon calling order.accept() for instance.在这种情况下,规则将适用于例如调用order.accept()

"then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService" “那么必须有像“CreateOrderService”、“AddOrderLineService”和“SaveOrderService”/“AddOrderService”这样的应用程序服务

I wouldn't call it "Service" if there's one per use case.如果每个用例都有一个,我不会称它为“服务”。 I'd call it XXXUseCase or XXXHandler , but the Service suffix is generally used for wider interfaces such as:我将其称为XXXUseCaseXXXHandler ,但Service后缀通常用于更广泛的接口,例如:

class OrderService {
    void create(...) { }
    void addLineItem(...) {}
    //...
}

Make sure that the use case names aligns with the domain's language (the Ubiquitous Language) rather than technical jargon (eg saveOrder).确保用例名称与域的语言(通用语言)而不是技术术语(例如 saveOrder)保持一致。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM