如何避免贫血数据模型?存储库可以注入实体吗?
How to avoid anemic data model? Can repositories be injected into entities?
我有一个不可变的 User
实体:
public class User {
final LocalDate lastPasswordChangeDate;
// final id, name, email, etc.
}
如果必须更改用户密码 i.d,我需要添加一个方法来 return 信息。它没有更改超过 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
实例提供给 ctor,还是 passwordMustBeChanged
方法需要提供 Settings
实例?
Settings
和SettingsRepository
的代码并不重要,但为了完整起见,这里是:
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);
}
}
对于系统范围的密码过期策略,只要您的 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);
}
如果最终可以为单个用户指定策略,那么您可以简单地将策略对象存储在 User
。
您也可以在您当前的设计中使用 ISP,并在您的 UserPasswordService
服务上实现一个 PasswordExpirationPolicy
接口。这将使您以后可以灵活地重构为真实的策略对象,而无需更改 User
与策略的交互方式。
如果你有一个 Password
值对象,你也可以通过类似的东西(密码创建日期将嵌入密码 VO):
//class User
public boolean passwordMustBeChanged(PasswordExpirationPolicy policy) {
return this.password.hasExpired(policy);
}
只是抛出另一种可能的解决方案是实施一个长 运行 过程,该过程可以进行过期检查并向 PasswordExpiredHandler 发送一个命令,该命令可以将用户标记为密码过期。
我偶然发现了一份文档,它提供了我的问题的答案:
A common problem in applying DDD is when an entity requires access to data in a repository or other gateway in order to carry out a business operation. One solution is to inject repository dependencies directly into the entity, however this is often frowned upon. One reason for this is because it requires the plain-old-(C#, Java, etc…) objects implementing entities to be part of an application dependency graph. Another reason is that is makes reasoning about the behavior of entities more difficult since the Single-Responsibility Principle is violated. A better solution is to have an application service retrieve the information required by an entity, effectively setting up the execution environment, and provide it to the entity.
http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/
我有一个不可变的 User
实体:
public class User {
final LocalDate lastPasswordChangeDate;
// final id, name, email, etc.
}
如果必须更改用户密码 i.d,我需要添加一个方法来 return 信息。它没有更改超过 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
实例提供给 ctor,还是 passwordMustBeChanged
方法需要提供 Settings
实例?
Settings
和SettingsRepository
的代码并不重要,但为了完整起见,这里是:
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);
}
}
对于系统范围的密码过期策略,只要您的 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);
}
如果最终可以为单个用户指定策略,那么您可以简单地将策略对象存储在 User
。
您也可以在您当前的设计中使用 ISP,并在您的 UserPasswordService
服务上实现一个 PasswordExpirationPolicy
接口。这将使您以后可以灵活地重构为真实的策略对象,而无需更改 User
与策略的交互方式。
如果你有一个 Password
值对象,你也可以通过类似的东西(密码创建日期将嵌入密码 VO):
//class User
public boolean passwordMustBeChanged(PasswordExpirationPolicy policy) {
return this.password.hasExpired(policy);
}
只是抛出另一种可能的解决方案是实施一个长 运行 过程,该过程可以进行过期检查并向 PasswordExpiredHandler 发送一个命令,该命令可以将用户标记为密码过期。
我偶然发现了一份文档,它提供了我的问题的答案:
A common problem in applying DDD is when an entity requires access to data in a repository or other gateway in order to carry out a business operation. One solution is to inject repository dependencies directly into the entity, however this is often frowned upon. One reason for this is because it requires the plain-old-(C#, Java, etc…) objects implementing entities to be part of an application dependency graph. Another reason is that is makes reasoning about the behavior of entities more difficult since the Single-Responsibility Principle is violated. A better solution is to have an application service retrieve the information required by an entity, effectively setting up the execution environment, and provide it to the entity.
http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/