DDD建模问题(实体访问存储库)

时间:2018-12-12 15:01:26

标签: domain-driven-design

我正在设计以下业务需求的模型:

  • 该应用程序必须能够注册用户
  • 用户注册的步骤为:
    1. 用户输入电子邮件地址并确认
    2. 验证码已发送到提供的电子邮件地址。
    3. 用户必须输入正确的验证码才能继续
    4. 对带有短信验证码的电话号码重复步骤1-3(可选)
    5. 然后用户输入一些个人信息并确认=>帐户已创建
  • 注册后,用户可以更新其电子邮件地址或手机号码,但必须经过相同的验证过程(发送的代码必须输入以确认修改)

我最终得到了以下模型:

  • 可验证的(界面)
  • 用户(实体)
  • EmailAddress(值类型,是可验证的)
  • MobilePhoneNumber(值类型,是可验证的)
  • RandomCode(值类型)
  • VerificationCode(包含Verifiable,RandomCode和generationDateTime的实体)
  • VerificationEmail(包含验证码,电子邮件地址和语言环境的集合)
  • VerificationSms(包含验证码,移动电话号码和语言环境的集合)

然后是问题!

  • 具有Verifiable接口以具有VerificationCode而不是EmailVerificationCode和SmsVerificationCode是否正确? (尽管它实际上并不是普遍使用的语言的一部分)
  • 由于我必须将元组emailAddress / mobilePhoneNumber + randomCode + generationDateTime保留在某个位置,以便能够检索它以进行验证,因此可以为此指定一个特定的实体吗?
  • 当用户想要更新他的电子邮件地址时,我希望这样做:

    // 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项目,欢迎任何建议,建模建议...

2 个答案:

答案 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)

如果要考虑建模,这是一个建议。希望它能对您有所帮助。我会这样建模:

用户(汇总根实体)

  • id
  • 电子邮件地址(非null和唯一的)
  • mobilePhoneNumber(可选)
  • personalInfo
  • 已启用(在注册过程开始时创建的用户被禁用,并且在过程成功结束时被启用)

VerificationCode(聚合根实体)===>它与用户相关联

  • id
  • randomCode
  • expirationDate
  • userId
  • smsOption(boolean)===>如果sms选项为true,则此验证码将通过SMS发送给用户(否则,将通过电子邮件发送给用户)
  • 静电工厂聚会:

forSendingByEmail ==>创建一个实例,其smsOption为false

forSendingBySMS ===>使用smsOption true创建并实例化

域服务: sendVerificationCodeToUser(VerificationCodeId)===>检查smsOption以发送SMS或电子邮件(发送到相关userId的mobilePhoneNumber / emailAddress)

DomainEvent: VerificationCodeWasCreated ===>它具有已创建的验证码的ID

  • 由VerificationCode构造函数引发
  • 侦听器将调用域服务:sendVerificationCodeToUser(verificationCodeWasCreated.verificationCodeId())

注册过程(应用服务方法):

(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):

  • 使用给定的mobilePhoneNumber更新userId的用户
  • 创建并保留与用户相关联的新验证码,以通过SMS发送

(4.2)事件侦听器发送SMS

(4.3)在步骤(4.2)中发送SMS的用户必须再次输入步骤(1)的电子邮件,并且他通过SMS收到的代码 ===> isARandomCodeCorrectForUserEEmail( randomCode,电子邮件)

(5)然后,用户输入一些个人信息,并确认===>已创建帐户 ===>我正在做的事情就是启用该用户,因为已经创建了该用户,并且我们从步骤(3)或(4.3)知道了userId

公共无效的ConfirmRegistration(PersonalInfo personalInfo,字符串userId):

  • 使用给定的personalInfo更新userId用户
  • 启用用户权限

电子邮件/手机号码修改过程:

这与注册类似,但是开头输入的email / mobilePhoneNumber必须属于现有的已启用用户,并且最后将执行用户更新,而不是启用。

已启用/已禁用的用户:

具有启用和禁用的用户,使您可以在身份验证和授权方法中将其考虑在内。如果您不希望或不允许启用/禁用用户,则必须使用id,email和mobilePhoneNumber建模另一个聚合,它将是UserCandidate或类似的东西。然后,在过程结束时,使用这些值创建实际用户。