【问题标题】:Domain Driven Implementation - Updating a single property inside Aggregate Root域驱动实现 - 更新聚合根中的单个属性
【发布时间】:2019-10-20 14:54:05
【问题描述】:

我是 DDD 的新手,我想就我在实施它时面临的一些挑战提供一些建议。

我正在使用 Typescript 开发应用程序。数据保存在关系数据库中。我们没有遵循 CQRS 模式,我们的读取和写入发生在同一个数据库中。

假设我有一个聚合 User 大致如下所示,

class User extends AggregateRoot {

id: number;
phone: Phone;
email: Email;
address: Address;

private constructor(id, phone, email, address){
    //setting the values
}

public static create(props) {

    return new User({...props});
}

public static update(props) {

    return new User({...props});
}

}

这里,PhoneEmailValueObjectsAddressEntity

class Phone extends ValueObject {

phNumber: string;

private constructor( ph ) {

    phNumber = ph;
}

public static create(ph){

    //do validations
    return new Phone(ph);
}
}

Email 类也类似于Phone

现在,一旦控制器收到更新手机请求,请求就会被转发到User Service层,服务大致如下所示,

public updatePhone( updatePhNoDto ) {

const userEntity = userRepository.getUser(updatePhNoDto.userId);

const userModel = User.update({
    id: updatePhNoDto.userId,
    phone: Phone.create(userEntity.phone),
    email: Email.create(userEntity.email),
    address: Address.create(userEntity.address)
});

userRepository.updateUser(userModel)
}

这里每次用户请求更新电话号码时,我都会从 RDBMS 中获取用户数据并对所有已验证的字段进行所有验证,然后调用方法 User.update()。 所以,这是我的问题:

  1. 不确定上述方法是否正确,因为我正在验证我已经验证过的东西,并且可能是不必要的数据库调用。因此,请向我建议最佳做法,以处理此类要求更新单个或仅几个字段的情况。
  2. 用户可以独立于他的其他信息更新他的地址。那么,Address 实体本身应该是Aggregate Root 吗?如果是,如果在一个http-request中同时请求更新UserInfo和Address,应该如何处理?
  3. 聚合根在删除中的作用是什么?里面应该如何建模?

如果您在设计中发现任​​何其他缺陷,请告诉我。

谢谢!

【问题讨论】:

    标签: design-patterns domain-driven-design aggregateroot ddd-service


    【解决方案1】:

    就像控制器有一个 UpdatePhone 端点一样,用户将有一个 UpdatePhone 方法,它只验证和更新电话号码。用户 AR 还会有 UpdateEmail、UpdateAddress 等。

    如果用户可以在前端一次更改多个用户属性,您可以使用控制器来解决这个问题。您将在控制器上有一个 UpdateUser 端点,该端点将决定哪些已更改,哪些未更改,然后调用 User 上的所有必要方法。一些伪代码:

    If (PhoneInfoUpdated) User.UpdatePhone({用户提交的电话字段}); If (EmailInfoUpdated) User.UpdateEmail({用户提交的电子邮件字段}); If (AddressInfoUpdated) User.UdateAddress({用户提交地址信息});

    (您可能只是在帖子中为了简洁而删除了它,但请记住这里有 2 个级别的数据验证。控制器验证日期类型,这样如果您期望整数,您实际上会得到整数,日期是日期,电话数字和电子邮件是正确的格式等。然后在 User.UpdateWhatever 方法中验证是否满足业务规则,例如电子邮件地址不是现有地址的副本等)

    我不明白地址如何在不归用户所有的情况下拥有自己的生命,但如果这是您的业务案例,那么它应该是 AR。因此,要更改地址,您应该有一个单独的地址 API 端点来执行正确的操作,而不是尝试通过用户端点发送它。如果前端直接调用 API,或者如果您使用 MVC 控制器接收回发,然后可以在 AR 上调用适当的 API 或适当的方法,则前端应该决定要调用的正确端点。

    至于删除,我从来都不喜欢实际删除,所以我建议添加一个活动标志(或已删除标志,具体取决于你在无休止的辩论的哪一边)。无论您是实际删除还是只是设置一个标志,您都应该有一个 User.Delete 方法。如果您真的要删除该行,我更喜欢将其作为 User 类的静态方法,这样您就不必检索用户然后删除它。如果您使用标志,Delete 方法应该在类上是公共的,因为它实际上只是像设置任何其他属性一样设置属性。

    【讨论】:

    • “我不明白一个地址如何在不被用户拥有的情况下拥有自己的生命” - 是的,我也有同感。因此,将其作为用户 AR 中的一个单独实体保留。所以,只是为了更新地址,就像你说的那样,在AR中有UpdateAddress这样的方法可以吗?
    • 是的,User.UpdateAddress 是最好的选择。
    • 太棒了。现在这又产生了另一个疑问。我在某处读到,所有域更改都应通过聚合根,聚合根应整体创建,而不是分段创建。您对此有何看法?
    • 是的,这是一条硬性规定,你不应该试图绕过。所有业务和验证逻辑都应封装在该 AR 中。因此,如果您“出于性能原因”尝试在较低级别进行更改,那么团队中的其他人(未来的团队成员?)将不知道那是存在的,并且会发现数据更改神秘地发生了。此外,如果将来验证或业务规则发生变化,在 AR 上下文之外进行更改将不会强制执行这些规则。
    • 话虽如此,请记住,在一个上下文中拥有作为 AR 的实体并在另一个上下文中拥有值对象是可以的。想象一个发票行项目。这是一个 AR,因为它需要检查库存、定价、SKU 等的逻辑。但它也是拥有它的订单上下文中的一个价值对象。将使用 LineItem 值对象列表创建订单,因为 Order 不知道如何更改订单项。要让 Order 更改订单项的数量,它必须创建一个 LineItem AR 并告诉它更改数量。
    猜你喜欢
    • 2019-06-27
    • 2010-11-30
    • 2010-11-01
    • 2013-04-09
    • 2013-06-07
    • 2013-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多