我正在设计以下业务需求的模型:
我最终得到了以下模型:
然后是问题!
当用户想要更新他的电子邮件地址时,我希望这样做:
// In the application service
User u = userRepository.findByUid(uid);
u.updateEmailAddress(newEmailAddress, enteredCode);
userRepository.save(u);
// In the User class
public void updateEmailAddress(EmailAddress newEmailAddress, String code) {
// Here comes the direct repository access
VerificationCode v = verificationCodeRepository.findByVerifiable(newEmailAddress);
if (v != null && v.hasNotExpired() && v.equalsToCode(code)) {
this.emailAddress = newEmailAddress;
verificationCodeRepository.delete(v);
}
else {
throw new IncorrectVerificationCodeException();
}
}
但是为了防止我的实体访问存储库,我最终得到了以下代码:
// In the application service
User u = userRepository.findByUid(uid);
VerificationCode v = verificationCodeRepository.findByVerifiable(newEmailAddress);
if (v != null && v.hasNotExpired() && v.equalsToCode(code)) {
verificationCodeRepository.delete(v);
u.updateEmailAddress(newEmailAddress);
userRepository.save(u);
}
else {
throw new IncorrectVerificationCodeException();
}
// In the User class
public void updateEmailAddress(EmailAddress newEmailAddress) {
this.emailAddress = newEmailAddress;
}
但是它看起来像一个贫血的模型,业务逻辑现在在应用程序层中……
我真的很努力地正确设计模型,因为这是我的第一个DDD项目,欢迎任何建议,建模建议...
答案 0 :(得分:1)
在您的updateEmailAddress()方法中将存储库作为参数传递没有错。
但是还有一个更好的选择,域名服务:
您的域服务取决于存储库,并且封装了绑定到验证的逻辑。然后,您将此服务传递给负责调用正确方法的用户实体。
这是它的样子:
class EmailVerificationService {
VerificationCodeRepository repository;
boolean isCodeVerified(EmailAddress emailAddress, String code) {
// do your things with the repository
// return true or false
}
}
然后在用户类中:
class User {
// ...
public void updateEmailAddress(EmailVerificationService service, EmailAddress emailAddress, String code) {
if (service.isCodeVerified(emailAddress, code)) {
this.emailAddress = emailAddress;
} else {
// throw business Exception ?
}
}
}
在应用程序服务中,您注入域服务并连接所有内容,捕获最终的异常并向用户返回错误消息。
答案 1 :(得分:1)
如果要考虑建模,这是一个建议。希望它能对您有所帮助。我会这样建模:
用户(汇总根实体)
VerificationCode(聚合根实体)===>它与用户相关联
forSendingByEmail ==>创建一个实例,其smsOption为false
forSendingBySMS ===>使用smsOption true创建并实例化
域服务: sendVerificationCodeToUser(VerificationCodeId)===>检查smsOption以发送SMS或电子邮件(发送到相关userId的mobilePhoneNumber / emailAddress)
DomainEvent: VerificationCodeWasCreated ===>它具有已创建的验证码的ID
注册过程(应用服务方法):
(1)用户输入电子邮件地址并确认
public void registerUser(字符串电子邮件):
使用给定的电子邮件检查是否不存在任何已启用的用户
如果该电子邮件存在禁用用户,请将其删除
使用电子邮件创建并保留新的禁用用户
创建并保留与创建的用户相关联的新验证码,以通过电子邮件发送
(2)验证码已发送到提供的电子邮件地址 ===>它是由域事件监听器完成的
(3)用户必须输入正确的验证码才能继续 ===>在步骤(1)中发送电子邮件的用户必须再次输入电子邮件,并且他输入的密码收到)
public boolean isARandomCodeCorrectForUserEmail ( String randomCode, String email ) {
User user = userRepository.findByEmail(email);
if (user==null) {
return false;
}
VerificationCode vcode = verificationCodeRepository.findByRandomCodeAndUserId(randomCode,user.id());
if ( vcode==null) {
return false;
}
return vcode.hasNotExpired();
}
(4)对带有SMS验证码的电话号码重复步骤1-3(可选)
(4.1)步骤(3)的用户输入手机号码(我们知道用户ID):
public void generateCodeForSendingBySmsToUser(字符串mobilePhoneNumber,字符串userId):
(4.2)事件侦听器发送SMS
(4.3)在步骤(4.2)中发送SMS的用户必须再次输入步骤(1)的电子邮件,并且他通过SMS收到的代码 ===> isARandomCodeCorrectForUserEEmail( randomCode,电子邮件)
(5)然后,用户输入一些个人信息,并确认===>已创建帐户 ===>我正在做的事情就是启用该用户,因为已经创建了该用户,并且我们从步骤(3)或(4.3)知道了userId
公共无效的ConfirmRegistration(PersonalInfo personalInfo,字符串userId):
电子邮件/手机号码修改过程:
这与注册类似,但是开头输入的email / mobilePhoneNumber必须属于现有的已启用用户,并且最后将执行用户更新,而不是启用。
已启用/已禁用的用户:
具有启用和禁用的用户,使您可以在身份验证和授权方法中将其考虑在内。如果您不希望或不允许启用/禁用用户,则必须使用id,email和mobilePhoneNumber建模另一个聚合,它将是UserCandidate或类似的东西。然后,在过程结束时,使用这些值创建实际用户。