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

Ada*_*ion 5 java oop domain-driven-design

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

public class User {
  final LocalDate lastPasswordChangeDate;
  // final id, name, email, etc.
}
Run Code Online (Sandbox Code Playgroud)

我需要添加一个方法,如果必须更改用户的密码,则该方法将返回信息,而不是因为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());
  }
}
Run Code Online (Sandbox Code Playgroud)

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

代码SettingsSettingsRepository并不重要,但对于完整性,这里是:

public 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

pla*_*alx 4

对于系统范围的密码过期策略,您的方法并没有那么糟糕,只要您UserPasswordService是域服务,而不是应用程序服务。在 User 中嵌入密码过期策略将违反 SRP 恕我直言,这也好不了多少。

您还可以考虑类似的内容(工厂使用正确的设置进行初始化):

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


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

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

您还可以将 ISP 与您当前的设计结合使用,并PasswordExpirationPolicy在您的服务上实现一个接口UserPasswordService。这将为您提供稍后重构为实际策略对象的灵活性,而无需更改User与策略的交互方式。

如果你有一个Password值对象,你也可以通过类似的方式让事情变得更有凝聚力(密码创建日期将嵌入到密码 VO 中):

//class User
public boolean passwordMustBeChanged(PasswordExpirationPolicy policy) {
    return this.password.hasExpired(policy);
}
Run Code Online (Sandbox Code Playgroud)