基于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
相关产品推荐
相关产品推荐

