问题描述
假设有两个类,一个是用户类,其中包含用户信息;另一个是用户类。另一类是支付交易类。 场景很简单,如果用户的年龄大于65岁,则创建A型支付交易;否则,请创建B型付款交易。
有几种方法可以做到这一点:
func CreateTransaction(user,transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
class User {
...
func CreateTransaction(transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
}
然后有一个CreateTransactionController方法,调用该函数的方法如下:
func CreateTransactinController(user,transaction) {
user.CreateTransaction()
}
我的问题是,因为逻辑实际上并不归任何对象所有,所以选项1是否被视为过程编程? (或贫血模式?) 1和2之间的差异是放置逻辑的不同地方吗?
谢谢!
解决方法
由于您将此问题标记为DDD,因此我将回答由域驱动的模型将如何实现这一点。
要回答的问题是Transaction
对象中是否包含User
。如果将其括起来,则意味着您始终要遍历用户记录以获取事务(并且永远不要直接访问事务)。如果事务本身具有生命周期,可以直接访问,可以控制域的其他部分,依此类推,则不能将其包含在User
中,并且是完整的聚合。
将transaction
括在user
中意味着用户拥有与交易相关的操作,因此选项2是正确的方法。
如果transaction
是一个不同的聚合,则可以使用Domain Service
(如您的选项1),但这是不正确的,因为您要处理两个聚合(user
和{{1} }) 同时。最好将此功能包含在transaction
聚合中。
下一个要解决的问题是如何决定交易类型。一种方法是:
- API请求将发送用户年龄作为请求的一部分
- 控制器调用服务并以整数形式传递用户的年龄
- 该服务调用一个工厂方法,该方法接受age作为整数,并初始化并返回正确的交易类型。
- 在请求通过时,后端的用户年龄可能已更改,或者请求可能不正确。您可以通过具有“纠正政策”来解决此问题,该政策将在以后创建付款交易后运行。如果用户的年龄与所选交易类型匹配,那么一切都很好。如果没有,则交易被撤消。
通常这是您处理依赖于多个聚合中的属性的更改的方式。您可以继续更改系统中聚合的状态,但是在稍后的时间检查相关的聚合数据,如果情况不一致,请撤消更改。
一种更好的方法是创建一个Transaction
,其显式任务是根据用户的年龄得出正确的付款类型。该规范包含您的业务逻辑(> 65),为年龄驱动的需求提供了上下文,并充当了控制逻辑的中心位置。
您可以在此处了解有关规格的更多信息:https://martinfowler.com/apsupp/spec.pdf