如何避免贫血数据模型?可以将存储库注入实体吗?

时间:2015-06-12 10:10:29

标签: java oop domain-driven-design

我有一个不可变的User实体:

public class User {
  final LocalDate lastPasswordChangeDate;
  // final id, name, email, etc.
}

如果必须更改用户的密码,我需要添加一个返回信息的方法。它的更改时间不超过passwordValidIntervalInDays系统设置。

目前的做法:

public class UserPasswordService {
  private SettingsRepository settingsRepository;

  @Inject
  public UserPasswordService(SettingsRepository settingsRepository) {
    this.settingsRepository = settingsRepository;
  }

  public boolean passwordMustBeChanged(User user) {
    return user.lastPasswordChangeDate.plusDays(
        settingsRepository.get().passwordValidIntervalInDays
      ).isBefore(LocalDate.now());
  }
}

问题是如何使上面的代码更加面向对象并避免贫血域模型反模式?如果将passwordMustBeChanged方法移至User,如果如何访问SettingsRepository,是否应该将其注入User的构造函数,或Settings实例是否应该passwordMustBeChanged是提供给ctor,还是Settings方法需要提供Settings实例?

SettingsRepositorypublic class Settings { int passwordValidIntervalInDays; public Settings(int passwordValidIntervalInDays) { this.passwordValidIntervalInDays = passwordValidIntervalInDays; } } public class SettingsRepository { public Settings get() { // load the settings from the persistent storage return new Settings(10); } } 的代码并不重要,但对于完整性,这里是:

{{1}}

3 个答案:

答案 0 :(得分:4)

对于系统范围的密码过期策略,只要您的UserPasswordService是域服务而不是应用程序服务,您的方法就不会那么糟糕。在用户中嵌入密码到期策略将违反SRP恕我直言,这不是更好。

你也可以考虑类似的东西(工厂用正确的设置初始化):

PasswordExpirationPolicy policy = passwordExpirationPolicyFactory().createDefault();
boolean mustChangePassword = user.passwordMustBeChanged(policy);


//class User
public boolean passwordMustBeChanged(PasswordExpirationPolicy policy) {
    return policy.hasExpired(currentDate, this.lastPasswordChangeDate);
}

如果最终可以为单个用户指定策略,那么您只需将策略对象存储在User上。

您还可以在当前设计中使用ISP,并在PasswordExpirationPolicy服务上实施UserPasswordService界面。这将使您可以灵活地在以后重构为真实的策略对象,而无需更改User与策略交互的方式。

如果你有一个Password值对象,你也可以通过类似的东西(密码创建日期将嵌入密码VO)来使事情更具凝聚力:

//class User
public boolean passwordMustBeChanged(PasswordExpirationPolicy policy) {
    return this.password.hasExpired(policy);
}

答案 1 :(得分:1)

抛出另一个可能的解决方案就是实现一个长时间运行的进程,该进程可以进行到期检查并向PasswordExpiredHandler发送一个命令,该命令可能会标记用户的密码已过期。

答案 2 :(得分:0)

我偶然发现了一个文件,提供了我的问题的答案:

  

应用DDD时的一个常见问题是,当实体需要访问存储库或其他网关中的数据以执行业务操作时。一种解决方案是将存储库依赖项直接注入实体,但这通常是不受欢迎的。这样做的一个原因是因为它要求实现实体的普通旧(C#,Java等)对象成为应用程序依赖图的一部分。另一个原因是,由于违反了单一责任原则,因此对实体行为的推理更加困难。更好的解决方案是让应用程序服务检索实体所需的信息,有效地设置执行环境,并将其提供给实体。

http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/