百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

业务代码与技术代码(业务代码和技术代码的对比)

cac55 2024-09-20 12:56 31 浏览 0 评论

当程序员大多都有一个共同的经历:当你在改一段复杂的代码时,你一边吐槽是哪个小可爱写的这段像一坨*一样的代码时,一边打开了提交记录,赫然发现竟然是自己3个月前写的!

明明看起来很简单的业务,但写出来的软件代码为什么会这么复杂呢?这是所有程序员都可能会思考的问题。

“领域驱动设计”号称是一种能够应对软件复杂性的解决方案,它的核心思路是从业务视角出发,去设计软件,并试图把技术复杂性和业务复杂性分离开来。但领域驱动设计是20年前就提出来的,那时候的软件面临的技术挑战和技术复杂性和现在不可同日而语,有些规则可能已经不那么适用。

那软件为什么复杂呢?前段时间看张逸前辈的《解构领域驱动设计》,里面对这块有非常详尽的解释,我觉得他总结得挺好的,这里精炼一下分享给大家:

简单来说,软件复杂是分为两个维度的。从理解维度上来说,软件的规模越大,结构越混乱,软件也就越复杂。从预测能力维度来说,软件可能会因为不能很好地适应未来的变化而导致改动成本太大而复杂。

我们可以用一些开发上的概念对应这几个维度,比如规模对应了代码行数和微服务数量;结构对应的是代码的分层设计、服务的调用关系;过度设计指的是为了把软件设计得过分通用,而导致代码设计复杂、可读性降低;设计不足就是很多写死的代码,导致业务变化时会有“改不动”的现象。

那么领域驱动设计的解决方案是什么呢?——通过分解领域,分而治之来「控制规模」;通过合理的架构来「清晰代码结构」;通过抽象合适的领域模型,高内聚低耦合来「应对变化」

其中,在清晰结构这一部分,我们应该尽量把“业务复杂度”和“技术复杂度”区分开来。比如一个订单业务,业务上关注的是订单的校验,计算订单金额,提交订单和整个订单后续状态的流转。而技术上关注的是保证订单能够正确的完成,保障数据的正确性,一致性,稳定性,更具体一点,比如如何防止重复提交,如何防止超卖,如何应对高并发,下游服务或者数据库挂了怎么办等等一系列技术问题。

那么我们以“提交订单”这个业务为例,来看看整个过程中会有哪些代码,哪些算是业务代码,哪些算是技术代码,它们应该如何被组织。

我们先来看看一个简化版的提交订单用例图,真实情况可能会比这个更复杂得多。

这个用例图看起来并不算很复杂,一共只有6步操作步骤,我们来一步一步解析。

分解步骤

1 提交订单

在提交订单这个环节,业务上并不会考虑太多。但从技术上,处于安全性考虑,我们可能需要校验一些参数的合法性,比如提交的商品id不能为空列表,提交的商品数量必须大于0等。

这些代码通常写在我们应用的最外层,是一进来就需要校验的。一些语言(比如Java)可能会有更优雅的解决方案,比如使用Validation注解。

所以「这里的参数校验是纯技术代码」

2 校验并占用库存

其实从业务上来讲,这一步应该只是叫“校验库存”,也就是校验这个商品是否还有库存,如果库存不足的话,应该及时中断流程,提示用户库存不足。

但处于技术上考虑,这里可能并不只是单纯的校验就可以的。我们还需要“占用”库存,防止超卖现象。比如在秒杀场景,某个商品有100个库存,但同时有1万人抢,大家几乎在同一时间抢购,如果在校验库存这个地方做限制,那很有可能有远超100人都校验通过,流程往后走,一直到用户付款后才发现库存不足了,给用户带来不好的体验,这在业务上是不允许的。

我把这一步合在了一起,叫“校验并占用库存”,但实际设计的时候可能是两个接口,也可能是一个接口。

所以「这里的校验是业务代码,但占用是技术代码」。但占用也是为了业务上的体验更好,业务上数据正确,所以有些团队会把占用也认为是业务代码。

3 查询商品价格

从业务视角看,其实只关心的是这个订单的总价。计算逻辑也很简单,就是订单上每种商品的价格 * 数量之和。

但技术上要考虑安全性,所以这个金额不可能是从前端页面传过来的,尽管此时前端页面已经拿到了每种商品的金额数据展示给用户。

出于保险起见,订单系统会去商品系统查询数据。

那么问题来了,假如查不到某个商品(下游挂了或者数据不正确),或者这个商品已下架/已违规怎么办?

此时如果是因为下游挂了或者数据不正确,而查不到商品,这里算是一种技术问题,应该终止流程并提示用户。而如果商品下架/违规,这里算是一个业务问题。当然也应该终止流程,并提示用户相应的文案。

但无论是技术问题还是业务问题,如果这里不能正常查询商品的价格,出于数据一致性的考虑,「都应该回滚在第二步占用的库存」

那么问题来了,回滚失败怎么办?

这里就涉及到一个问题了,「我们值不值得为了这种“极小概率场景”去做一套方案」?比如占用超时释放?

出于成本考虑,我想大多数团队不会做得很严谨,事实上也不可能完美解决这个问题,毕竟可能根本没有完美的方案。真要是发生了这种情况,可能多数人的选择还是打个ERROR日志,然后触发告警,手动看一看。

4 计算订单总价

好了,终于来到纯业务的领域了。计算订单总价,看起来好像很简单,乘起来再加起来就行了。

然后业务同学告诉你,这块现在是直接计算没问题,但我们后面可能要搞满减活动,它是分档次的。满100减10,满200减25,满500减80……

而且会员有折上折,vip 1 打9.8折,vip 2打9.7折……

在大促期间,我们可能还会有优惠券活动,用户可以使用各种各样的优惠券……

最恐怖的是,可能产品同学提需求的时候并不会跟你说这些,而是后面再提n个需求让你改。

好吧,这就是业务代码。它可能是随时会发生变化的。

在计算完后,应该把这个订单的信息保存到DB,生成一个单据。但业务不关心这个,这是技术代码。

5 确认扣减库存

其实提交订单在业务上看就是一瞬间的事情,提交订单,然后扣减库存。但技术上因为会分为上面的步骤,所以要有占用,回滚,确认扣减这几个步骤。也是处于数据一致性考虑。

所以个人觉得“确认扣减库存”,更像是一个技术代码。

6 生成支付单据

这个我觉得是业务代码了。但同样会有上面的一系列技术问题,比如生成失败怎么办,如何设计幂等,防止重复提交等等。

领域代码

假设我们是按领域来划分的系统。那我们就有订单领域,库存领域,商品领域,支付领域。对于电商场景来说,它们可能都是我们系统中的核心领域(支付领域也许不是,这个看公司策略)。

在用户提交订单这个业务,订单领域要做的是生成一个“订单”模型,完成订单的计算,生成订单单据。其中完成订单的计算这一步骤是纯业务逻辑,也是最复杂的,它应该放在领域模型内部。但其实也不尽然,当规则复杂和易变到一定程度,我们可能会使用「规则引擎」,那订单模型的职责就变成了“应用从规则引擎读取到的规则来计算金额”了。

在库存领域,核心的领域代码应该是库存被占用。

在商品领域,这个步骤不涉及领域状态的变更,更多的只是商品信息的查询。在领域驱动设计提倡的CQRS(读写分离)的架构下,商品领域只是提供一个查询接口,所以不会涉及领域模型的相关代码。

在支付领域,也是生成一个“支付单”,此处逻辑较简单。

技术代码

你可以很明显的能够感知到,如果光靠领域代码,基本上是不可能顺利地正确完成“订单提交”这个业务操作的。

比如库存占用,就需要上锁才能保证不超占。更别说还有回滚和确认。比如参数的校验、幂等的设计,也是不属于业务代码的。

我们再来看领域驱动设计倡导的两个领域之间“基于事件通信”,在这个业务里面也是不能完全使用事件来通信的。订单领域就是需要实时调用库存领域的接口来保证强一致性。

反思

所以我们再回过头来反思,领域驱动设计可以解决这个问题吗?业务代码和技术代码真的能分开吗?

很明显,在订单领域,单纯的一个订单模型已经不能够内聚所有的逻辑了。那么加一个订单领域服务呢?其实理论上是可以的。大概代码组织是这样:

public String submitOrder(SubmitOrderCommand command) {
    stockAdapter.checkAndOccupyStock(command.getCommodityInfos());
    try {
        Commodities = commodityAdapter.Getcommodities(command.getCommodityInfos());
        Order order = OrderFactory.generateOrder(command);
        order.computeTotalAmount();
        orderRepository.save(order);
     stockAdapter.confirmOccupyn(command.getCommodityInfos());
    } catch(Throwable t) {
        logger.Error("提交订单失败。", t)
        stockAdapter.rollbackOccupy(command.getCommodityInfos());
    }
    paymentAdapter.submitPayment(order.getPaymentInfo())
}

可以看到,上述代码涵盖了第2步到第6步的所有步骤(第1步没放进来是因为参数校验通常在更外层就做了)。但很明显我们仍然「不可避免地把技术代码和业务代码糅杂在了一起」。比如保存数据库、确认占用等。但我们也尽力把技术代码挪到了adapter和repository的实现里面,在领域层只是调用了一下接口。相较于传统的面条式各种service调用代码,使用领域服务和领域对象就清晰得多了。

再回过头来看过度设计和设计不足的问题。这其实考验的是一个程序员对业务的理解程度和思考程度。如果可以显然预料到未来会发生明显的变化(比如文中提到的计算规则的变化),那确实应该在设计之初更灵活地设计好。而如果对未来的变化把握并不清晰,或者确定,那满足当前业务需求就可以了,如果架构合理,代码清晰,改起来成本倒也没那么大。这里提倡的是开发者尽量多与领域专家(比如业务人员或者产品经理)沟通,这样才能更好地把握代码未来的走向。

求个支持

我是Yasin,一个坚持技术原创的博主,我的微信公众号是:「编了个程」

都看到这儿了,如果觉得我的文章写得还行,不妨支持一下。

文章会首发到公众号,阅读体验最佳,欢迎大家关注。

你的每一个转发、关注、点赞、评论都是对我最大的支持!

还有学习资源、和一线互联网公司内推哦

相关推荐

Mac电脑强制删除任何软件方法-含自启动应用

对于打工者来说,进入企业上班使用的电脑大概率是会被监控起来,比如各种流行的数据防泄漏DLP,奇安信天擎,甚至360安全卫士,这些安全软件你想卸载是非常困难的,甚至卸载后它自己又安装回来了,并且还在你不...

Linux基础知识 | 文件与目录大全讲解

1.linux文件权限与目录配置1.文件属性Linux一般将文件可存取的身份分为三个类别,分别是owner/group/others,且三种身份各read/write/execute等权限文...

文件保护不妥协:2025 年 10 款顶级加密工具推荐

数据安全无小事,2025年这10款加密工具凭借独特功能脱颖而出,从个人到企业场景全覆盖,第一款为Ping32,其余为国外英文软件。1.Ping32企业级加密核心工具,支持200+文件格...

省心省力 一个软件搞定系统维护_省心安装在哪里能找到

◆系统类似于我们居住的房间,需要经常打理才能保持清洁、高效。虽然它本身也自带一些清理和优化的工具,但借助于好用的第三方工具来执行这方面的任务,会更让人省心省力。下面笔者就为大家介绍一款集多项功能于一身...

JAVA程序员常用的几个工具类_java程序员一般用什么软件写程序

好的工具做起事来常常事半功倍,下面介绍几个开发中常用到的工具类,收藏一下,也许后面真的会用到。字符串处理:org.apache.commons.lang.StringUtilsisBlank(Char...

手工解决Windows10的若干难题_windows10系统卡顿怎么解决

【电脑报在线】很多朋友已经开始使用Win10,估计还只是测试版本的原因,使用过程中难免会出现一些问题,这里介绍解决一些解决难题的技巧。技巧1:让ProjectSpartan“重归正途”从10074...

System32文件夹千万不能删除,看完这篇你就知道为什么了

C:\Windows\System32目录是Windows操作系统的关键部分,重要的系统文件存储在该目录中。网上的一些恶作剧者可能会告诉你删除它,但你不应该尝试去操作,如果你尝试的话,我们会告诉你会发...

Windows.old 文件夹:系统备份的解析与安全删除指南

Windows.old是Windows系统升级(如Win10升Win11)或重装时,系统自动在C盘创建的备份文件夹,其核心作用是保留旧系统的文件、程序与配置,为“回退旧系统”提供保...

遇到疑难杂症?Windows 10回收站问题巧解决

回收站是Windows10的一个重要组件。然而,我们在使用过程中,可能会遇到一些问题。例如,不论回收站里有没有文件,都显示同一个图标,让人无法判别回收站的空和满的真实情况;没有了像Windows7...

卸载软件怎么彻底删掉?简单几个步骤彻底卸载,电脑小白看过来

日常工作学习生活中,我们需要在安装一些软件程序,但随着软件的更新迭代速度,很多时候我们需要重新下载安装新的程序,这时就需要将旧的一些软件程序进行卸载。但是卸载软件虽然很简单,但是很多小伙伴们表示卸载不...

用不上就删!如何完全卸载OneDrive?

作为Windows10自带的云盘,OneDrive为资料的自动备份和同步提供了方便。然而,从隐私或其他方面考虑,有些人不愿意使用OneDrive。但Windows10本身不提供直接卸载OneDri...

【Linux知识】Linux下快速删除大量文件/文件夹方法

在Linux下,如果需要快速删除大量文件或文件夹,可以使用如下方法:使用rm命令删除文件:可以使用rm命令删除文件,例如:rm-rf/path/to/directory/*这个命令会递...

清理系统不用第三方工具_清理系统垃圾用什么软件

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

系统小技巧:软件卸载不了?这里办法多

在正常情况下,我们都是通过软件程序组中的卸载图标,或利用控制面板中的“程序和功能”模块来卸载软件的。但有时,我们也会发现利用卸载图标无法卸载软件或者卸载图标干脆丢失找不到了,甚至控制面板中卸载软件的功...

麒麟系统无法删除文件夹_麒麟系统删除文件权限不够

删除文件夹方法例:sudorm-rf文件夹名称。删除文件方法例:sudorm-r文件名包括扩展名。如果没有权限,给文件夹加一下权限再删。加最高权限chmod775文件名加可执行权限...

取消回复欢迎 发表评论: