我读到了关于DDD的文章,我意识到有时一个实体可能是VO,或者VO可能是一个实体。你可以根据上下文知道哪一个更好。我检查了不同的例子。例如,购物车DDD示例。产品是一个聚合根,购物车是一个集合根,物品是购物车的一个实体,所以如果你想把产品添加到购物车中,你可以这样做:
$cart->addProduct(id $id, $name, $price)
class Cart
{
private items;
function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
this->items[] = new Item(
new ItemProductId($id->ToString()),
new ItemName($name->ToString()),
new ItemPrice($price->ToString()),
new ItemCartId(this->id->ToString())
);
}
}
我认为这是一个VO有两个原因:
- 您不能修改值的项(仅当产品价格已被修改存在将修改其价格的事件)
- 该项没有id,它具有的引用产品(ItemProductId)和购物车的引用(ItemCartId)
我读到了关于DDD的文章,我意识到有时一个实体可能是VO,或者VO可能是一个实体。你可以根据上下文知道哪一个更好。
通常它非常清楚什么是实体,什么是值对象。如果它包含在赋值时固定的数据,那么它就是一个值对象。例如订单聚合上的"订单地址"。下订单时,会设置地址。"地址"可能是用户聚合中的一个实体(即他的常用地址列表),但对于订单来说,它是一个值对象,因为当用户编辑或删除他的一个地址时,它不应该更改。
cart->addProduct(id $id, $name, $price)
class Cart
{
private items;
function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
this->items[] = new Item(
new ItemProductId($id->ToString()),
new ItemName($name->ToString()),
new ItemPrice($price->ToString()),
new ItemCartId(this->id->ToString())
);
}
}
这是一个非常糟糕的例子。为什么value对象应该是ItemPrice
?这有什么特别的吗?为什么是字符串?价格通常只是一个数字值,但也涉及到一种货币,以字符串形式传递它有点比这更好。
最重要的是,它中有ItemCartId
a) 将数据持久性知识泄漏到您的域中。事实上,它包含在this->items[]
中,已经在实体(或聚合)和值对象之间建立了关系。ItemCartId
在域中没有任何意义,除了关系数据库引擎(=持久性知识)需要它
我认为这是一个VO:有两个原因
您不能修改价值的项目(只有当产品的价格已被修改时,才会有事件修改其价格)。
你确定吗?为什么电子商务企业希望在卡上有价格?
价格仅供参考,在下订单之前可能会发生变化。与可用性相同。
很多用户把东西放进购物车,第二天再检查。在那个时候,价格可能会发生变化。
如果产品在放入购物车后的一段时间内价格上涨,任何公司都不会想以放入购物车时的价格出售产品。这将意味着经济损失。
购物车中的价格是信息性的,而不是强制性的。你需要知道公司的具体流程。
项目没有id,它有一个产品引用(ItemProductId)和一个购物车引用(ItemCartId)再一次为什么您认为
ItemCartId
属于Item
对象?这泄露了持久性知识,因为它只对关系数据库系统很重要。
购物车中您真正需要的只是*产品或文章编号(不需要id,这通常是数据库知识)*数量
没有别的。如果您可能想在价格更改时更改用户,并显示旧价格和新价格,则也可以将价格(=货币值对象,而不是ItemPrice
)作为与旧状态进行比较的值。
最后,也许也是最重要的一点:考虑一下购物车是否是一个集合(或者是否适合ddd)。
毕竟,大多数购物车只是一个价值袋,里面没有很多商业逻辑。真正的逻辑(检查真实价格、产品可用性、询问运输地点、计算税款和运输成本)发生在结账过程中,而不是在把东西放进购物车时。
例如,您可以查看容器上的eShops演示项目,该项目展示了一个基于微服务和ddd的示例购物服务。
一些微服务应用DDD(如订购微服务),而其他微服务则不应用(目录微服务或购物篮(cart)微服务)。
应用DDD并不意味着一切都需要使用DDD。如果它是简单的基于crud的服务,那么这些服务就不需要DDD。DDD在拥有复杂系统和复杂业务逻辑的情况下会增加价值。
目录两者都没有,它只是显示来自不同系统的数据(即ERP,另一方面可能使用DDD构建)。
我不明白你到底在问什么,但你提供的代码可以改进。
首先,我建议你读沃恩·弗农的红书https://www.amazon.co.uk/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577:您可以找到3章来描述如何定义实体、值对象和聚合,以及一些拇指规则。
其中一个建议是,保持聚合尽可能小,以提高性能,并使代码易于阅读和维护。想象一下,你有一个包含Post实体列表的博客聚合:如果你在一个聚合中管理所有这些实体,例如,当你想修改博客作者时,你被迫毫无理由地检索博客的所有帖子,这意味着你正在进行联接并降低应用程序的速度。聚合增长得越多,这些查询的连接速度就越慢。
因此,在购物车的情况下,我建议您在没有任何物品或产品的情况下构建购物车,相反,您可以将CartId添加到物品中。购物车不知道它包含哪些物品,但物品知道它们在哪个购物车中。
关于值对象:这是一个工具,允许您将一些验证和业务逻辑封装在一个由其状态而非id(类似于实体)表示的类中,因此在购物车的情况下,如果您在其中放入两瓶相同的水,您如何知道它们是不同的?你需要知道它们是不同的吗?如果它们在物理上(或逻辑上)不同,它们是不同的吗?如果它们的某些属性不同,它们又是不同的?
在我看来,在你的情况下,一个物品或产品是实体,因为它们没有测量任何东西,当你把一个物品放两次时,你实际上有两个不同的物品(你使用id来识别它们)。
这不是必要的,有时你可以使用值对象,有时也可以使用实体,这取决于你的上下文。一个很好的例子来理解差异在于金钱:
-
如果你想测量一个金额,例如10美元,可能一个价值对象会对你有用,因为你不在乎它是账单还是其他,你只想测量10美元;在这种情况下,如果你有10美元,那么如果你有一张或另一张钞票就不重要了,重要的是这是10美元,而不是5
-
如果出于任何原因(你需要为警方追踪资金),你需要识别不同的实物纸币,你应该使用一个实体,因为任何打印的纸币都有一个唯一的序列号,在这种情况下,10美元纸币实际上与另一张10美元纸币不同
希望这能帮助到你。
再见!