24

业务逻辑开发套路的三板斧

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIwMDY0Nzk2Mw%3D%3D&%3Bmid=2650321010&%3Bidx=1&%3Bsn=ecf80e0bd157fa5cd1b7b7ae47cce450
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

转自: 代码这件大事

背景:

作为一个研发,我们工作中都会处理面临下面这些困惑:

  1. 又加需求,一个方法本来就处理了 300 行,现在又加 50 行。

  2. 状态逻辑太多了,产品第 2 期又加了一个逻辑,代码结构要调整,很头疼。

  3. 每个人都在吐槽,业务研发在工作中处理最多的就是 if else,好不容易写个 switch 都能给同事吹一周。以上三个场景应该是日常需求迭代优化中面临最多的场景了,作为一个自称编码水平较高的人,总结了以下三个真实的场景,给出一些可选的方案。

第一板斧:抽象事件,驱动业务

核心

梳理产品逻辑中的主流程节点,整理节点所需要的依赖数据已经节点触发后对应的业务逻辑。类比消息队列,也是不同的业务方订阅自己的事件源,进行不同的处理。不同点在于一个是分布式,一个是本文描述单机业务处理场景。

实际例子

举一个用户注册之后的场景,需要:

  1. 发短信

  2. 发优惠券 如果用户注册成功之后,直接发了mq消息,那么用户系统和券系统分别订阅这个消息进行处理。不过这里讨论的是在 一个项目模块中处理完所有相关的逻辑

代码将在 UserRegistered() 中一步一步去处理逻辑,之后需求又加入了 初始化A数据初始化B数据 两个需求,实现也会落到这个方法之后,最后整个代码会越来越臃肿。

2EJjuqy.jpg!web

接下来用事件订阅模型去化解这个点,非常实用,一点都不华丽,代码也很好读懂。

QrYFjyR.jpg!web

最后对应到程序代码可能是这样的:

# 事件注册
Event::register(UserRegistered::class, [
    SendSms::class,
    SendCoupon::class,
    InitSystemA::class,
    InitSystemB::class,
]);

# 业务调用
handleRegistered($user) {
    $event = new UserRegistered($user);
    $event->fire();
}

后面的迭代维护中,只要主流程不发生变化,那么相应的逻辑只需要去增加订阅者去实现。

第二板斧:有限状态机,定义流程

在业务逻辑数据处理这一层,很多的业务场景都与数据扭转状态有关,并且最后会有相应的数据实体相映射。比如我们常见的:

  1. 各种商品订单(天猫,淘宝,外卖)

  2. 工作流(审批,工单处理)

这类需求的特点是,读写场景QPS不高,对数据的准确一致性要求非常高。我们底层一般直接存储到数据库,之上加一层简单的数据缓存就能处理。

面临的主要问题是,状态太多难以维护,应该还会出现状态的调整比如特殊场景下的状态A到状态Z的扭转。 

不过业内早已给出了比较通用的解决方案,有限状态机。下面我们列举一个简单的订单状态扭转逻辑:

FfMvyui.jpg!web

fsm := fsm.NewFSM(
    "created",
    fsm.Events{
        {Name: "pay", Src: []string{"created"}, Dst: "paid"},  //支付
        {Name: "cancel", Src: []string{"created"}, Dst: "closed"}, //关闭
    },
    fsm.Callbacks{
        "after_pay": func(e *fsm.Event) { /* 支付成功调用 */ },
    },
)

如果设计到状态相关的调整,在状态机定义的地方去修改就可以解决问题。和事件订阅非常相识,也是集中维护,统一管理。

第三板斧:n元组配置,组合输出

软件工程没有银弹   --布鲁克斯

软件开发中我们遇到的一个一个需求都是不可预测的,我们确实很难找到一种终极的解决方案。不过本文中的讨论局限在业务逻辑开发的开发套路。那么,n元组这个简单的概念可能算得上一颗银弹。不管业务逻辑有多复杂,在理论上我们都能抽象出n个字段来表达我们的数据模型。

拿一个订单举例子,我们有如上订单特征。不可避免的,每一个业务场景,每一个逻辑,产品逻辑都可能有自己的配置和相应的处理流程,且这些逻辑都是业务迭代优化的重灾区,比如:

  1. 江浙沪地区包邮

  2. 某一批固定的城市需要打8.8折

  3. 雨天调价格

  4. 法定节假日打烊不服务

  5. vip身份的用户展示文案特殊处理

每一个开发同学都曾被这些逻辑折磨的异常痛苦,这里给出一个抽象的方案,最终每一个订单特征都会落到具体的业务处理类,所有的类都实现该业务场景的 interface,主流程只需要构造 n元组然后获取到相应的 interface 之后进行调用。

jERRfeZ.jpg!web

n元组的概念其实早已经渗透到了开发中的每一个角落,我们需要做的事情就是,在业务开发的时候真正的去思考这一层数据模型,然后加以运用,最后的代码一定不那么 if else。

总结

以上三板斧在业务中使用可以很轻,也可以很重。这就意味着我们能自己写一个简单够用的(对于你完全了解成长有限的业务场景),或者找一个star多且在维护的开源方案(对于有潜力,未来大有可为的业务)来代替。同时,这些 编码套路 在各种场景下都能非常灵活的组合,比如:

  1. 订单状态更新后触发事件(状态机+事件订阅)

  2. 不同业务线,状态机配置,初始化放松不同(n元组+状态机)

不得不提到的一点,在漫长的业务迭代中,产品文档会越来越缺失,最终只有通过代码才能了解线上的真正逻辑(有时间代码过于复杂,可能就没有人知道线上的具体情况了), 集中配置,统一维护的意义之一就在于此 。相信做过复 杂历史系统 的交接或重构的同学对这一点都深有体会。

参考

[有限状态机](https://zh.wikipedia.org/zh-hans/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA)

[go fsm](https://github.com/looplab/fsm)

热文推荐

Spring Boot 与微服务从0到1的实践

我在外包公司做增删改查有前途么?

那天晚上和@FeignClient注解的深度交流

jMjIJ3v.jpg!webvAjYjmU.jpg!web

UjmyInZ.jpg!web

如有收获,点个在看,诚挚感谢


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK