Service层基础方法设计:为什么我的getUserByOpenId不该直接抛异常?
上周在开发用户模块时,我写了一个Service层方法用于根据 OpenID 获取用户信息。最初我的实现是这样的:
public User getUserByOpenId(String openId) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getOpenId, openId);
User user = this.getOne(wrapper, false);
if (ObjectUtils.isEmpty(user)) {
throw new CustomException(ExceptionMessageDefine.USER_NOT_EXIST);
}
return user;
}
我的初衷是好的:认为"用户不存在"是个严重错误,应该在底层拦截,避免上层调用者忘记判空导致NullPointerException
。然而在代码评审时,项目经理提出了调整建议,最终版本变成了:
public User getUserByOpenId(String openId) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getOpenId, openId);
return this.getOne(wrapper, false);
}
这个看似简单的改动背后,隐藏着重要的设计哲学。最初我不太理解,直到在实际业务场景中遇到了问题。
两种写法在实际场景中的对比
在新用户注册功能中,我需要检查OpenID是否已被注册,于是写了这样的逻辑:
public void registerUser(String openId) {
// 检查用户是否已存在
if (userService.getUserByOpenId(openId) != null) {
throw new CustomException("用户已注册");
}
// 注册新用户...
}
这时问题出现了:当新用户首次注册时,我的原版getUserByOpenId
方法会直接抛出"用户不存在"异常,导致注册流程中断!而在注册场景中,"用户不存在"恰恰是正常情况,不应该触发异常。这个设计缺陷在游客模式功能中更加明显,当我们需要为未注册用户创建临时账号时,原版方法强制要求用户必须存在,完全无法支持灵活的业务逻辑。
经过反思,我理解了问题所在:基础数据访问方法应该保持纯粹的数据查询语义。getUserByOpenId
的职责是查询用户数据,而不是执行业务规则校验。将业务异常处理嵌入基础方法会导致:
方法复用性降低:无法适应不同业务场景对"空结果"的不同需求
职责边界模糊:基础方法越权处理了应该由业务层决定的逻辑
代码灵活性受损:调用方失去对空结果的处理控制权
业务规则校验应该交给上层方法处理,于是我们重构为分层处理方案:
// 基础数据访问方法 (保持纯净)
@Override
public User getUserByOpenId(String openId) {
return lambdaQuery().eq(User::getOpenId, openId).one();
}
// 业务校验方法 (按需使用)
public User getRequiredUserByOpenId(String openId) {
User user = getUserByOpenId(openId);
if (user == null) {
throw new BusinessException(ExceptionMessageDefine.USER_NOT_FOUND);
}
return user;
}
这个调整带来了显著优势。在用户登录场景中,我们使用getRequiredUserByOpenId
确保用户必须存在;在注册场景中,则直接使用基础的getUserByOpenId
配合空值判断。在批量处理任务中,这种设计也展现出强大灵活性,我们可以轻松跳过无效用户:userList.stream().filter(Objects::nonNull).forEach(this::process)
,而原版方法任一用户缺失就会导致整个任务失败。
这次经历让我深刻认识到分层架构的核心原则:基础数据访问层应当保持"中性",只负责数据查询不介入业务规则。越是底层的方法,越要保持高复用性。业务异常本质上是业务流程的一部分,应该由具体业务场景触发,而非固化在基础方法中。就像项目经理强调的:"getUserByOpenId
的职责是获取用户,不是强制获取存在的用户"。这个设计原则虽然简单,却能显著提升代码的适应性和可维护性。当我们把业务决策权交还给调用方时,代码反而获得了更强的生命力和扩展性。