使用Either实现数据校验与账号创建,如何优化现有代码写法?
现有代码合理性评估
现有代码的核心思路是合理的,用Either承载错误/正常返回结果,避免了传统异常流跳转的不可控问题,同时把用户名、密码的校验逻辑内聚在值对象的create方法中,符合DDD的设计原则,只是在结构设计、资源利用、可读性上还有优化空间。
存在的可优化点包括:
- 错误对象提前实例化,且错误类型不匹配:用户名重复场景使用
ACCOUNT_PERSISTENCE_ERROR不合理,且错误对象只有校验不通过时才会用到,提前创建属于无效资源消耗 - 校验顺序不合理:密码合法性校验放在用户名唯一性校验之后,一旦密码不合法,之前的唯一性查库操作完全浪费
- 嵌套层级过深:多层flatMap嵌套再叠加三元表达式,后续如果加更多校验逻辑,可读性会快速下降
- 逻辑内聚性不足:Account的默认状态、创建时间、初始任务列表这些固定初始化逻辑放在服务层,后续修改Account默认规则需要同步改服务层代码
- 重复逻辑未抽离:实体转Dto的映射逻辑写死在方法中,其他需要转AccountDto的场景还要重复写映射代码
优化后实现方案
public Either<Error, AccountDto> create(AccountCreateDto accountCreateDto) { // 先做无IO的值对象校验,避免无效查库 return UserName.create(accountCreateDto.userName()) // 合并两个值对象的校验结果 .flatMap(userName -> Password.create(accountCreateDto.password()) .map(password -> new Tuple2<>(userName, password)) ) // 所有参数合法再执行需要查库的唯一性校验 .flatMap(tuple -> { UserName userName = tuple._1; Password password = tuple._2; if (!userNameUniquenessChecker.isUnique(userName.text())) { // 用到时再实例化错误,错误类型更精准 return Either.left(new Error(ErrorType.USER_NAME_DUPLICATED, "用户名不唯一: " + userName.text())); } // Account初始化逻辑内聚到自身静态工厂方法 Account newAccount = Account.create(userName, password); Account savedAccount = accountRepository.add(newAccount); // 转换逻辑抽成Dto静态方法复用 return Either.right(AccountDto.fromEntity(savedAccount)); }); }
补充说明:
上面代码用到的Tuple可以用自定义的二元组类,也可以根据你所用的Either工具库的配套容器替换;Account的静态create方法内封装默认状态赋值、创建时间生成、空任务列表初始化逻辑,AccountDto的fromEntity方法封装实体到Dto的字段映射逻辑即可。
额外优化建议
- 拆分错误枚举:为不同错误场景定义独立的枚举值,比如
USER_NAME_INVALID、PASSWORD_INVALID、USER_NAME_DUPLICATED、ACCOUNT_SAVE_ERROR,后续排查问题、给前端返回错误码都更清晰 - 可扩展校验逻辑:如果后续要加手机号校验、邮箱校验等逻辑,只需要在值对象校验阶段按同样逻辑追加即可,不会导致嵌套层级持续升高
- 存储层错误兼容:当前代码没有处理repository.add的异常,建议把存储层的操作也封装成Either返回,把存储层错误也纳入统一的错误处理链路中
内容的提问来源于stack exchange,提问作者Sampeteq
相关产品推荐
相关产品推荐

