领域服务上抛异常还是返回错误码

简介: 最近收到这样的问题:领域服务做业务逻辑校验时应该返回错误码还是抛出业务异常?这其实不算是领域服务的问题,而是Java异常处理[1]问题。之前总结过一次如何处理异常[2]上面的文章基本上就解决异常相关问题了。这儿再回顾总结一下:

最近收到这样的问题:

领域服务做业务逻辑校验时应该返回错误码还是抛出业务异常?

这其实不算是领域服务的问题,而是Java异常处理[1]问题。

之前总结过一次如何处理异常[2]

上面的文章基本上就解决异常相关问题了。

这儿再回顾总结一下:

返回错误码

在异常没有出现时,像C语言是如何处理问题的?

在 C 语言中,错误码的返回方式有两种:一种是直接占用函数的返回值,函数正常执行的返回值放到出参中;另一种是将错误码定义为全局变量,在函数执行出错时,函数调用者通过这个全局变量来获取错误码

// 错误码的返回方式一:pathname/flags/mode为入参;fd为出参,存储打开的文件句柄。
int open(const char *pathname, int flags, mode_t mode, int* fd) {
  if (/*文件不存在*/) {
    return EEXIST;
  }
  if (/*没有访问权限*/) {
    return EACCESS;
  }
  if (/*打开文件成功*/) {
    return SUCCESS; // C语言中的宏定义:#define SUCCESS 0
  }
  // ...
}
//使用举例
int fd;
int result = open(“c:\test.txt”, O_RDWR, S_IRWXU|S_IRWXG|S_IRWXO, &fd);
if (result == SUCCESS) {
  // 取出fd使用
} else if (result == EEXIST) {
  //...
} else if (result == EACESS) {
  //...
}
// 错误码的返回方式二:函数返回打开的文件句柄,错误码放到errno中。
int errno; // 线程安全的全局变量
int open(const char *pathname, int flags, mode_t mode){
  if (/*文件不存在*/) {
    errno = EEXIST;
    return -1;
  }
  if (/*没有访问权限*/) {
    errno = EACCESS;
    return -1;
  }
  // ...
}
// 使用举例
int hFile = open(“c:\test.txt”, O_RDWR, S_IRWXU|S_IRWXG|S_IRWXO);
if (-1 == hFile) {
  printf("Failed to open file, error no: %d.\n", errno);
  if (errno == EEXIST ) {
    // ...        
  } else if(errno == EACCESS) {
    // ...    
  }
  // ...
}

错误码没有性能问题,但带来了维护性,尤其没有了异常堆栈信息,失去了快速定位问题的能力。

抛异常

在OO世界中,更推荐使用异常方式,显得更OO些

Checked Exception

Spring创始人Rod Johnson列举了检查异常几个问题:

1、太多的代码

开发人员不得不捕捉他们无法处理的检查异常

2、难以读懂的代码

捕捉不能处理的异常并重新抛出,没有执行一点有用的功能,反而会使查找实际做某件事的代码变得更困难

3、异常无休止封装

4、易毁坏的方法签名

一旦这么多调用者使用一个方法,添加一个额外的检查异常到该接口上将需要这么多代码被修改。也些违背OCP原则[3]

5、检查异常对接口不一定管用

接口有很多种实现,有些实现会出现异常,但有些是不会出现异常的,比如存储数据,放在文件会抛IO相关异常,但数据是数据库,刚不是此异常。

Runtime Exception

运行期异常被很多大牛接受推荐,但也有弊病,就是调用方需要知道内部实现细节,了解抛出了些什么异常,需在javadoc中给出明确说明。

当然我们现在可以使用AOP技术,对异常进行兜底处理,以防泄漏到用户层。

性能

当使用异常时,性能得确会下降,尤其调用链路很深时,更加明显。但异常总归是少数情况,不影响正常情况的性能。

但有些系统对性能要求比较高,怎么办?如正常情况是百万QPS,就算出现少许异常情况,对系统可用性也带来不小的影响。

怎么办?退回错误码时代

但从设计角度可改良一下,可以不再简单返回错误码,如可以使用vavr的Either



Either<ExceptionMessage,Result> do();

让调用方式来最终确定,当either.isLeft()时,是向上抛异常,还是额外处理。

良好实践

使用检查异常还是运行时异常是个见解问题,不管如何选择,只要团队达成共识,统一规范就可以。

良好的异常,不管是对开发人员,还是运维,用户都应该有全面友好的提示信息

对开发人员,在异常中包含相关信息,使用getMessage()打印日志,方便定位问题

对于用户,可以使用错误代码,字符串比数值语义更明确些。在spring初期代码中,Rod Johnson设计了一个接口ErrorCoded


public interface ErrorCoded {
    /** Constant to indicate that this failure isn't coded */
    public static final String UNCODED = "uncoded";
    /** Return the error code associated with this failure. 
     * The GUI can render this anyway it pleases, allowing for Int8ln etc.
     * @return a String error code associated with this failure
     */
    String getErrorCode();
}

其实也不并是说所有场景都去使用异常,如openapi,最好使用errorCode。

异常与契约

乔新亮指出异常是那些让产品无法履行当初承诺用户的契约的问题。

对于异常设计有5点认知:

1、异常一定要消灭;有异常基本就意味着系统存在风险,一定要消灭异常

2、异常一定要管理:消灭异常是个长期工程,短期要通过管理行为来进行控制

3、对异常的处理水平,会极大影响产品的用户体验:用户规模越大,异常的影响往往越大

4、每个异常都要有具体负责人

5、与终端用户相关的异常,要以最高优先级处理

异常设计包含:异常注册、异常事件触发、异常协作流程以及异常统计。

要关注异常数据、异常发生频次、异常数据的增速和降速。

总结

回到起始问题,对于领域服务,自然OO更好些,抛出特定业务异常,业务语义更加清晰。

性能问题,一是避免发生异常情况,二是通过横向扩展。毕竟业务可运维性更重要。

References

[1] Java异常处理: https://www.zhuxingsheng.com/blog/java-exception-practice.html

[2] 如何处理异常: https://www.zhuxingsheng.com/blog/java-exception-handling.html

[3] OCP原则: https://note.youdao.com/


目录
相关文章
|
存储 Java API
阿里高级技术专家谈开源DDD框架:COLA4.1,分离架构和组件(下)
阿里高级技术专家谈开源DDD框架:COLA4.1,分离架构和组件(下)
11118 8
阿里高级技术专家谈开源DDD框架:COLA4.1,分离架构和组件(下)
|
微服务 测试技术 Java
阿里技术专家详解 DDD 系列- Domain Primitive
关于DDD的一系列文章,希望能继续在总结前人的基础上发扬光大DDD的思想,但是通过一套我认为合理的代码结构、框架和约束,来降低DDD的实践门槛,提升代码质量、可测试性、安全性、健壮性。
61958 17
阿里技术专家详解 DDD 系列- Domain Primitive
|
SQL 缓存 Java
殷浩详解DDD系列 第三讲 - Repository模式
# 第三讲 - Repository模式 **写在前面** 这篇文章和上一篇隔了比较久,一方面是工作比较忙,另一方面是在讲Repository之前其实应该先讲Entity(实体)、Aggregate Root(聚合根)、Bounded Context(限界上下文)等概念。但在实际写的过程中,发现单纯讲Entity相关的东西会比较抽象,很难落地。所以本文被推倒重来,从Repository
37746 8
|
1月前
|
人工智能 JSON 前端开发
Agentic AI崛起:九大核心技术定义未来人机交互模式​
本文系统梳理AI智能体架构设计的九大核心技术,涵盖智能体基础、多智能体协作、知识增强、模型优化、工具调用、协议标准化及人机交互等关键领域,助力构建高效、智能、协同的AI应用体系。建议点赞收藏,持续关注AI架构前沿技术。
486 1
|
SQL Java 数据库连接
对 MyBatis Plus SaveBatch 调优提升25倍性能!!!
最近在压测一批接口,发现接口处理速度慢的有点超出预期,感觉很奇怪,后面定位发现是数据库批量保存这块很慢。这个项目用的是,批量保存直接用的是提供的 saveBatch。于是开始排查之路。所以如果有使用 jdbc 的 Batch 性能方面的需求,要将rewriteBatchedStatements 设置为 true,这样能提高很多性能。然后如果喜欢手动拼接 sql 要注意一次拼接的数量,分批处理。
859 1
|
缓存 小程序 前端开发
【微信小程序】wx.login实现用户登录
【微信小程序】wx.login实现用户登录
|
存储 缓存 负载均衡
图解一致性哈希算法,看这一篇就够了!
近段时间一直在总结分布式系统架构常见的算法。前面我们介绍过布隆过滤器算法。接下来介绍一个非常重要、也非常实用的算法:一致性哈希算法。通过介绍一致性哈希算法的原理并给出了一种实现和实际运用的案例,带大家真正理解一致性哈希算法。
25035 64
图解一致性哈希算法,看这一篇就够了!
|
SQL 关系型数据库 MySQL
mysql数据转为pgsql
mysql数据转为pgsql
364 0
|
数据库 测试技术 Java
阿里技术专家详解DDD系列 第二弹 - 应用架构
应用架构,指软件系统中固定不变的代码结构、设计模式、规范和组件间的通信方式。在应用开发中架构之所以是最重要的第一步,因为一个好的架构能让系统安全、稳定、快速迭代。但是今天我们在做业务研发时,更多会关注一些宏观的架构,而忽略了应用内部的架构设计,希望能通过案例分析和重构,推演出一套高质量的DDD架构。
57694 24
阿里技术专家详解DDD系列 第二弹 - 应用架构
|
存储 Linux 开发工具
「译文」使用 submodule 和 subtree 管理 Git 项目
「译文」使用 submodule 和 subtree 管理 Git 项目
下一篇
oss教程