更新聚合内的实体
我正在阅读一个类似的问题:如何更新聚合内的实体,但我仍然不确定用户界面应如何与聚合内的实体交互。
假设我有一个用户
,以及一堆地址
。 User是聚合根,而Address只存在于聚合内。
在网络界面上,用户可以编辑他的地址。基本上,发生的情况是:
- 用户在其 Web 界面上看到地址列表,
- 他单击一个地址,然后被重定向到此页面:
edit-address?user=1&address=2
- 页面上,他会收到一个可以修改此地址的表格。
我决定绕过聚合根,这很简单:
- 我们将直接加载
Address
及其Id
- 我们将更新它,然后保存它
因为我们想要这样做对于 DDD 方式,我们有不同的解决方案:
我们要求用户通过 Id 获取此地址:
地址 = user.getAddress(id);
address.setPostCode("12345");
address.setCity("纽约");
em.persist(用户);
在我看来,这种方法的问题是,聚合根仍然无法对地址的处理进行更多的控制。它只是返回对其的引用,因此这与绕过聚合没有太大区别。
或者我们告诉聚合更新现有地址:
user.updateAddress(id, "12345", "纽约");
em.persist(用户);
现在聚合可以控制对该地址执行的操作,并且可以采取与更新地址相关的任何必要操作。
或者我们将地址视为值对象,并且我们不更新我们的
地址
,而是删除它并重新创建它 :user.removeAddress(id);
地址 = new Address();
address.setPostCode("12345");
address.setCity("纽约");
user.addAddress(地址);
em.persist(用户);
最后一个解决方案看起来很优雅,但意味着地址不能是实体。那么,如果它需要被视为一个实体,例如因为聚合中的另一个业务对象引用了它,该怎么办?
我很确定我在这里遗漏了一些东西来正确理解聚合概念以及它如何在现实生活中的示例中使用,所以请随时发表您的评论!
I was reading a similar question on SO: How update an entity inside Aggregate, but I'm still not sure how a user interface should interact with entities inside an aggregate.
Let's say I have a User
, with a bunch of Address
es. User is the aggregate root, while Address only exists within the aggregate.
On a web inteface, a user can edit his addresses. Basically, what happens is:
- The user sees a list of addresses on its web interface
- He clicks on an address, and gets redirected to this page:
edit-address?user=1&address=2
- On this page, he gets a form where he can modify this address.
I we decided to bypass the aggregate root, this would be straightforward:
- We would directly load the
Address
with itsId
- We would update it, then save it
Because we want to do it the DDD way, we have different solutions:
Either we ask the User to get this Address by Id:
address = user.getAddress(id);
address.setPostCode("12345");
address.setCity("New York");
em.persist(user);
The problem with this approach is, IMO, that the aggregate root still doesn't have much more control over what's done with the address. It just returns a reference to it, so that's not much different from bypassing the aggregate.
Or we tell the aggregate to update an existing address:
user.updateAddress(id, "12345", "New York");
em.persist(user);
Now the aggregate has control over what's done with this address, and can take any necessary action that goes with updating an address.
Or we treat the Address as a value object, and we don't update our
Address
, but rather delete it and recreate it:user.removeAddress(id);
address = new Address();
address.setPostCode("12345");
address.setCity("New York");
user.addAddress(address);
em.persist(user);
This last solution looks elegant, but means that an Address cannot be an Entity. Then, what if it needs to be treated as an entity, for example because another business object within the aggregate has a reference to it?
I'm pretty sure I'm missing something here to correctly understand the aggregate concept and how it's used in real life examples, so please don't hesitate to give your comments!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不,你不会错过任何东西 - 在大多数情况下,最好的选择是数字2(尽管我会将该方法称为
changeAddress
而不是updateAdress - 更新似乎不是 DDD),这与地址是实体还是值对象无关。使用通用语言,您宁愿说用户更改了他的地址,因此这正是您应该如何建模的 - 由
changeAddress
方法决定是否更新属性(如果地址是一个实体)或分配全新的对象(当它是 VO 时)。以下示例代码假设最常见的场景 - Address 作为 VO:
此示例中重要的是,一旦创建了 Address,它就被认为是有效的 - 聚合中不能有无效的 Address 对象。但请记住,您是否应该遵循此示例取决于您的实际域 - 没有单一的路径可供遵循。但这是最常见的一种。
是的,您应该始终通过遍历聚合根来对实体执行操作 - 其原因在 SO 的许多答案中都给出了(例如在此 基本聚合问题)。
某个东西是实体还是 VO 取决于需求和您的领域。大多数情况下,地址只是一个值对象,因为具有相同值的两个地址之间没有区别,并且地址在其生命周期内往往不会改变。但同样,这在大多数情况下都是如此,并且取决于您正在建模的领域。
另一个例子 - 对于大多数域来说,
Money
将是一个值对象 - 10$ 是 10$,除了金额之外它没有任何标识。但是,如果您要建模一个在账单级别上处理金钱的域,则每个账单都有自己的身份(用某种唯一的数字表示),因此它将是一个实体。No, you're not missing anything - in most cases the best option would be number 2 (although I'd call that method
changeAddress
instead ofupdateAdress
- update seems so not-DDD) and that's regardless whether an address is an Entity or Value Object. With Ubiquitous Language you'd rather say that User changed his address, so that's exactly how you should model it - it's thechangeAddress
method that gets to decide whether update properties (if Address is an Entity) or assign completely new object (when it's VO).The following sample code assumes the most common scenario - Address as VO:
What is important in this sample, is that once Address is created, it is considered valid - there can be no invalid Address object in your aggregate. Bare in mind however, that whether you should follow this sample or not depends on your actual domain - there's no one path to follow. This one is the most common one though.
And yes, you should always perform operations on your entities by traversing through aggregate root - the reason for this was given in many answers on SO (for example in this Basic Aggregate Question).
Whether something is an entity or VO depends on the requirements and your domain. Most of the time address is just a Value Object, because there's no difference between two addresses with the same values and addresses tend to not change during their lifetime. But again, that's most of the time and depends on domain you're modeling.
Another example - for most of the domains a
Money
would be a Value Object - 10$ is 10$, it has no identity besides amount. However if you'd model a domain that deals with money on a level of bills, each bill would have its own identity (expressed with a unique number of some sort) thus it would be an Entity.