【问题标题】:domain driven design depends on static methods?领域驱动设计依赖于静态方法?
【发布时间】:2013-12-17 02:41:20
【问题描述】:

我已经在线/离线阅读了很多关于域驱动设计的一般验证和业务规则的位置。我无法理解的是,实体如何在不诉诸静态方法或拥有服务的情况下提供执行验证和业务规则的方法?这对于不需要实例化域对象但我们需要验证最终将用于设置对象属性的值的情况尤其重要。

我注意到诸如http://lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/ 之类的博客帖子依赖于.NET 的特定扩展方法,而Java 等编程语言中没有这种扩展方法。我个人不喜欢静态方法,因为它们不能被覆盖且难以测试。

我是否可以在没有静态方法的情况下做到这一点,或者为了使用其验证和业务规则方法而必须实例化一个不必要的域对象。如果不是,这是否意味着领域驱动设计非常依赖静态方法?

谢谢

【问题讨论】:

  • 你能发一两个例子吗?
  • 我们以网站注册为例。
  • 我们只举一个电子商务网站注册的例子。如果注册客户要求客户指定网站帐户的用户名。如果注册成功,将通过电子邮件通知客户(会员详细信息需要与注册步骤中提供的其他信息一起查看) 因为根据 DDD,客户实体(由客户类表示)必须包含验证用户名的方法,如果在审核注册之前未创建 Customer 对象,如何调用此方法?

标签: java domain-driven-design


【解决方案1】:

使用 ValueObjects 不是实体。

在注册的情况下,可以引入一个 UserName 值对象。接收注册时创建一个用户名对象。在 UserName 的构造函数中实现验证。

查看questionpresentation 了解更多详情。

编辑1:
1.如何处理不同验证规则应用于不同上下文的情况。
例如:某类会员的用户名不能有数字,而其他类型的会员则需要?

也许不同的工厂方法可以做到这一点。像 UserName.forGoldenCardMember(...) 或 UserName.forPlainMember(...)。或者制作 MemberType (可能是一个层次结构)来验证 UserName。

另一种替代方案是使用 AggregateFactory(在本例中为 AccountFactory)。

2.构造函数是唯一放置验证码的地方吗?我确实在网上阅读了两种观点:对象必须始终有效与不总是有效。两者都提出了很好的论点,但还有其他方法吗?

我个人更喜欢有效的方法。传递一个可能无效的值对象会损害封装性。

编辑2:
要求 a) 基于上下文的验证业务规则(成员类型不同的用户名规则) b) 继续验证所有业务规则,即使其中一个失败

坚持单一职责原则,使用值对象(本例为成员类型)。 可以引入 AggregateFactory 来简化应用层(更粗粒度)。

class AccoutFactory {
    Account registerWith(Username username, MemberType type, ....) {
        List<String> errors = new ArrayList<String>();
        errors.addAll(type.listErrorsWith(username));
        errors.add(//other error report...

        if (CollectionUtils.isEmpty(errors)) {
            return new Account(username,....);
        } else {
            throw new CannotRegisterAccountException(errors);
        }
    }
}

编辑3: 对于 cmets 中的问题
a)用户名对象不应该是具有返回错误的方法的对象吗 listErrorsWith()?毕竟是用户名对不同的会员类型有不同的规则?

我们可以从另一个角度来检查这个问题:MemberTypes 对用户名有不同的规则。这可以用多态性替换 Username.listErrosWith(String, MemeberType) 中的 if/else 块;

b) 如果我们在 MemberType 中有方法,则知识不会被封装在 Username 中。另外,我们正在讨论确保 Username 始终有效。

我们可以在没有 MemberType 规则的情况下定义 Username 的有效性。假设“hippoom@stackoverflow.com”是一个有效的用户名,它适合 GoldenCard 会员,但不适合 SilverCard 会员。

c) 我仍然看不到如何执行返回错误列表的验证,而无需从构造函数或静态方法抛出的异常中获取列表。恕我直言,两者看起来都不理想。

是的,listErrorsWith():List 的签名看起来很奇怪,我宁愿使用没有返回值的 validate(username) (失败时抛出异常)。但这将迫使客户端捕获每个验证步骤以一次运行验证。

【讨论】:

  • 我阅读了问题的答案并观看了演示。我可以看到为什么要引入 Username 对象。然而,几个问题:
    1.如何处理不同验证规则应用于不同上下文的情况。
    例如:某类会员的用户名不能有数字,而其他类型的会员则必须?
    2.构造函数是唯一放置验证码的地方吗?我确实在网上阅读了两种观点:对象必须始终有效与不总是有效。两者都提出了很好的论点,但还有其他方法吗?
  • for 1) 这不是说工厂方法必须是静态方法吗?我不太喜欢静态方法,因为覆盖和测试它更加困难。实际上,我在考虑是否必须在您之前提到的问题中将类似 ChangeEmailCommand 的内容传递给构造函数。它还将包含验证上下文名称旁边的用户名所需的其他运行时参数(例如成员的类型)。然而,不知何故,通过这样的物体感觉很奇怪。这是我对 DDD 与服务层方法的担忧之一。或许你能给我更多启发?
  • @user3054039 它不一定是静态方法。通常首先启动聚合工厂。但根据我个人的经验,用于验证的静态方法对可测试性几乎没有损害。
  • 谢谢你的回复 Hippoom...我很困惑,在这种情况下,聚合工厂如何与值对象相关联?工厂是否应该创建用户名?想到的另一个问题是,如果我们要在构造函数中进行验证(始终有效),如何将错误列表传递给要显示的 UI(例如,在尝试实例化注册应用程序对象时)?
  • 回到我之前的问题:假设 Username 是放置验证而不是 MemberType 的地方。老实说,我看不出我们如何避免在静态方法/构造函数中进行验证/必须先实例化用户名并在使用用户名字符串调用 Username.setValue() 之前在用户名字符串上调用非静态 validate()。你怎么看?
【解决方案2】:

如果您决定在应用程序中使用 DDD,则需要构建更复杂的解决方案。我同意@Hippoom,你不应该为此目的使用实体。

我会建议这个解决方案:

DTO -> 服务层(ValidationService -> 转换器)-> 持久层(存储库)

一些解释: 当您从客户端收到带有所有必要参数的 DTO 时,您应该在服务层中对其进行验证(例如,使用 ValidationService 之类的其他服务),如果出现问题,可能会引发异常。如果一切正常,您可以在 Converter 中从您的 DTO 创建实体并将其保存在 Repository 中。

如果您想要灵活的 ValidationService 解决方案,我建议您使用 Drools

【讨论】:

  • 我确实阅读了很多关于服务层与域对象 (DDD) 的内容。从概念上讲,我同意域对象方法。但是,在我看来,与使用服务层方法相比,DDD 需要更多的努力来遵循。两者都存在优势和劣势。我想更多的是开发团队的偏好将决定采用哪一个。你怎么看?
  • @user3054039 是的,应该选择哪种方法完全取决于您。我认为,对于庞大的应用程序,服务层的使用更加方便,它应该是灵活的,为一组问题提供通用解决方案,并且可以重新配置而无需构建新的二进制文件。
猜你喜欢
  • 2012-12-10
  • 2014-09-12
  • 2011-03-25
  • 2011-10-06
  • 2018-06-19
  • 1970-01-01
  • 1970-01-01
  • 2011-05-26
  • 1970-01-01
相关资源
最近更新 更多