You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于DDD的.NET Core晚餐托管系统分步用户注册实体建模问询

DDD视角下晚餐托管系统用户注册的实体设计与状态管理

聚合设计梳理

你当前把Host和Guest作为聚合根、User作为其组成部分的思路是合理的——因为Host/Guest的核心业务能力(创建晚餐、报名晚餐等)必须依附于用户身份。可以明确:

  • Host聚合:包含Host实体(托管晚餐的专属属性) + User实体(通用用户属性:手机号、姓名、邮箱等)
  • Guest聚合:包含Guest实体(参与晚餐的专属属性) + User实体(通用用户属性)
  • 核心规则:只有当关联的User属性完整且验证通过时,Host/Guest聚合才是有效的,无效聚合不允许执行任何业务操作(比如Host创建晚餐)。

解决分步注册的核心:临时会话实体

因为注册是分步收集信息,中途退出不能生成无效的Host/Guest聚合,必须引入RegistrationSession实体来承载临时状态——它的职责就是跟踪注册进度、存储未完成的用户数据,直到注册全流程完成才触发正式聚合的创建。

RegistrationSession的核心属性:

  • sessionId:唯一标识会话
  • mobile:第一步提交的手机号
  • otpVerified:OTP是否验证通过
  • tempUserInfo:临时存储姓名、邮箱、性别等后续步骤的信息
  • userType:用户选择的类型(Host/Guest)
  • expiresAt:会话过期时间(比如30分钟,避免无效会话长期占用资源)

分步注册的领域流程实现

步骤1:提交手机号

  • 调用RegistrationService的startRegistration方法,传入手机号和用户类型,生成RegistrationSession(标记otpVerified = false)并保存到仓库。
  • 基础设施层触发OTP发送(这是外部依赖,领域服务只负责调度)。

步骤2:验证OTP

  • 通过sessionId找到对应的RegistrationSession,验证OTP有效性。
  • 验证通过后,更新会话的otpVerified = true并保存。

步骤3:提交用户基本信息

  • 找到会话后,先校验otpVerified是否为true(业务规则:未验证OTP不能完成注册)。
  • 根据userType创建正式聚合:
    • 生成User实体:填充所有必填属性(手机号、姓名、邮箱、性别),标记Status = ACTIVE,Joining_Date = 当前时间,并通过isValid()方法校验完整性。
    • 创建Host/Guest聚合根:传入生成的User实体,构造函数中强制校验User的有效性,不满足则抛出领域异常。
  • 将Host/Guest聚合保存到对应仓库,同时删除/标记RegistrationSession为已完成。

聚合有效性的强制保障

  • 在Host/Guest的构造函数中添加校验逻辑:只有当关联的User满足所有必填条件(手机号已验证、姓名/邮箱/性别非空)时,才允许创建实例,否则抛出InvalidAggregateStateException。
  • 聚合仓库只接受通过有效性校验的聚合实例,拒绝保存无效状态的Host/Guest。

中途退出的处理

  • 用户在步骤1/2退出时,系统中只有RegistrationSession存在,不会生成任何正式的Host/Guest聚合,避免无效实体流入业务系统。
  • 定时清理过期的RegistrationSession(比如超过30分钟未完成的会话),释放存储资源。

伪代码示例

RegistrationSession 实体

public class RegistrationSession {
    private final String sessionId;
    private String mobile;
    private boolean otpVerified;
    private TempUserInfo tempUserInfo;
    private final UserType userType;
    private final LocalDateTime expiresAt;

    public RegistrationSession(String sessionId, String mobile, UserType userType) {
        this.sessionId = sessionId;
        this.mobile = mobile;
        this.userType = userType;
        this.expiresAt = LocalDateTime.now().plusMinutes(30);
        this.otpVerified = false;
    }

    public void verifyOtp() {
        // 先通过外部服务验证OTP,通过后更新状态
        this.otpVerified = true;
    }

    public void updateTempInfo(String firstName, String lastName, String email, String gender) {
        this.tempUserInfo = new TempUserInfo(firstName, lastName, email, gender);
    }

    // getter方法省略
}

Host 聚合根与User实体

public class Host {
    private final HostId id;
    private final User user;

    public Host(HostId id, User user) {
        if (!user.isValid()) {
            throw new InvalidAggregateStateException("无法创建无效状态的Host");
        }
        this.id = id;
        this.user = user;
    }
}

public class User {
    private final UserId id;
    private final String firstName;
    private final String lastName;
    private final String email;
    private final String mobile;
    private final String gender;
    private final LocalDateTime joiningDate;
    private final UserStatus status;

    public User(UserId id, String firstName, String lastName, String email, String mobile, String gender) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.mobile = mobile;
        this.gender = gender;
        this.joiningDate = LocalDateTime.now();
        this.status = UserStatus.ACTIVE;
    }

    public boolean isValid() {
        return !StringUtils.isAnyBlank(firstName, lastName, email, mobile) 
            && status == UserStatus.ACTIVE;
    }
}

注册领域服务

public class RegistrationService {
    private final RegistrationSessionRepository sessionRepo;
    private final HostRepository hostRepo;
    private final GuestRepository guestRepo;
    private final OtpService otpService;

    public String startRegistration(String mobile, UserType userType) {
        String sessionId = UUID.randomUUID().toString();
        RegistrationSession session = new RegistrationSession(sessionId, mobile, userType);
        sessionRepo.save(session);
        otpService.sendOtp(mobile);
        return sessionId;
    }

    public void verifyOtp(String sessionId, String otp) {
        RegistrationSession session = sessionRepo.findById(sessionId)
                .orElseThrow(() -> new SessionNotFoundException("会话不存在"));
        
        if (otpService.validate(session.getMobile(), otp)) {
            session.verifyOtp();
            sessionRepo.save(session);
        } else {
            throw new InvalidOtpException("验证码无效");
        }
    }

    public void completeRegistration(String sessionId, String firstName, String lastName, String email, String gender) {
        RegistrationSession session = sessionRepo.findById(sessionId)
                .orElseThrow(() -> new SessionNotFoundException("会话不存在"));
        
        if (!session.isOtpVerified()) {
            throw new OtpNotVerifiedException("请先验证手机号");
        }

        session.updateTempInfo(firstName, lastName, email, gender);
        UserId userId = UserId.generate();
        User user = new User(userId, firstName, lastName, email, session.getMobile(), gender);

        if (session.getUserType() == UserType.HOST) {
            HostId hostId = HostId.generate();
            Host host = new Host(hostId, user);
            hostRepo.save(host);
        } else {
            GuestId guestId = GuestId.generate();
            Guest guest = new Guest(guestId, user);
            guestRepo.save(guest);
        }

        sessionRepo.delete(session);
    }
}

内容的提问来源于stack exchange,提问作者Tisha Anand

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 11:27:16