> 作为一个研发,我们工作中都会处理面临下面这些困惑:
> 1. 又加需求,方法本来处理了 300 行,现在又加 50 行。
> 2. 状态逻辑太多了,产品第 2 期又加了一个逻辑,代码结果要调整,很头疼。
> 3. 每个人都在吐槽,业务研发在工作中处理最多的是 `if else` ,好不容易写个 `switch` 都能给同事吹一周。
以上三个场景应该是日常需求迭代优化中面临最多的场景了,作为一个自称编码水平较高的人,总结了以下三个真实的场景,给出一些可选的方案。
## **第一板斧:抽象事件,驱动业务**
### **核心**
梳理产品逻辑中的**主流程节点**,整理节点所**需要的依赖数据**以及节点**触发后对应的业务逻辑**。
类比消息队列,也是不同的业务方订阅自己的事件源,进行不同的处理。不同点在于一个是分布式,一个是本文描述单机业务场景。
### **实际例子**
举一个用户注册之后的场景,需要:
1. 发短信
2. 发优惠券
如果用户注册成功之后,直接发 MQ 消息,那么用户系统和券系统分别订阅这个消息进行处理。不过这里讨论的是在**一个项目模块中处理完所有相关的逻辑**。
代码将在`UserRegistered()`中一步一步去处理逻辑,之后需求又加入了**初始化 A 数据**和**初始化 B 数据**两个需求,实现也会落到这个方法之后,最后整个代码会越来越臃肿。
:-: ![](https://img.kancloud.cn/68/17/68176f99c1ade9744904f005e2ba91b4_432x573.png)
接下来用时间订阅模型去化解这个点。
最后对应到程序代码可能是这样的:
~~~php
// 事件注册
Event::register(UserRegistered::class, [
SendSms::class, // 发短信
SendCoupon::class, // 发优惠券
InitSystemA::class, // 初始化A系统
InitSystemB::class, // 初始化B系统
])
---------------------------------------------------------------------------------------
// 业务调用
handleRegistered($user) {
$event = new UserRegistered($user);
$event->fire();
}
~~~
后面的迭代维护中,只要主流程不发生变化,那么相应的逻辑只需要去增加订阅者去实现。
:-: ![](https://img.kancloud.cn/99/bc/99bc44b0fa54d81861933f422ef450f9_854x416.png)
## **第二板斧:有限状态机,定义流程**
在业务逻辑数据处理这一层,很多的业务场景都与数据扭转状态有关,并且最后会有相应的数据实体映射。比如我们常见的:
1. 各种商品订单(天猫,淘宝,外卖)
2. 工作流(审批,工单处理)
这类需求的特点:读写场景 `QPS` 不高,多数据的准确一致性要求非常高。我们底层一般直接存储到数据库,之上加一层简单的数据缓存就能处理。
面临的主要问题是,状态太对难以维护,应该还会出现状态的调整比如特殊场景下的状态A到状态Z 的扭转。
不过业内早已给出了比较通用的解决方案,有限状态机。
:-: ![](https://img.kancloud.cn/37/2e/372ed7e2f23fab04a4d31876f4c16535_316x426.png)
~~~
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之后进行调用。
:-: ![](https://img.kancloud.cn/a3/af/a3afd810da97a9fbc39da7caaea66fff_920x279.png)
n 元祖的概念其实早已经渗透到了开发中的每一个角落,我们需要做的事情就是,在业务开发的时候真正的去思考这一层数据模型,然后加以运用,最后的代码一定不那么 `if else`。
## **总接**
以上三板斧在业务中使用可以很轻,也可以很重。这就意味着我们能自己写一个简单够用的(对于你完全了解成长有限的业务场景),或者找一个 star 多且在维护的开源方案(对于有潜力,未来大有可为的业务)来代替。同时,这些编码套路在各种场景下都能非常灵活的组合,比如:
1. 订单状态更新后触发事件(状态机 + 事件订阅)
2. 不同业务线,状态机配置,初始化放松不同(n 元组 + 状态机)
不得不提到的一点,在漫长的业务迭代中,产品文档会越来越缺失,最终只有通过代码才能了解线上的真正逻辑(有时代码过于复杂,可能就没有人知道线上的具体情况了),集中配置,统一维护的意义之一就在于此。相信做过复杂历史系统的交接或重构的同学对这一点都深有体会。
<br>
<br>
> 原文地址:https://learnku.com/articles/39302#replies
- PHP
- PHP 核心架构
- PHP 生命周期
- PHP-FPM 详解
- PHP-FPM 配置优化
- PHP 命名空间和自动加载
- PHP 运行模式
- PHP 的 Buffer(缓冲区)
- php.ini 配置文件参数优化
- 常见面试题
- 常用函数
- 几种排序算法
- PHP - 框架
- Laravel
- Laravel 生命周期
- ThinkPHP
- MySQL
- 常见问题
- MySQL 索引
- 事务
- 锁机制
- Explain 使用分析
- MySQL 高性能优化规范
- UNION 与 UNION ALL
- MySQL报错:sql_mode=only_full_group_by
- MySQL 默认的 sql_mode 详解
- 正则表达式
- Redis
- Redis 知识
- 持久化
- 主从复制、哨兵、集群
- Redis 缓存击穿、穿透、雪崩
- Redis 分布式锁
- RedisBloom
- 网络
- 计算机网络模型
- TCP
- UDP
- HTTP
- HTTPS
- WebSocket
- 常见几种网络攻击方式
- Nginx
- 状态码
- 配置文件
- Nginx 代理+负载均衡
- Nginx 缓存
- Nginx 优化
- Nginx 配置 SSL 证书
- Linux
- 常用命令
- Vim 常用操作命令
- Supervisor 进程管理
- CentOS与Ubuntu系统区别
- Java
- 消息队列
- 运维
- RAID 磁盘阵列
- 逻辑分区管理 LVM
- 业务
- 标准通信接口设计
- 业务逻辑开发套路的三板斧
- 微信小程序登录流程
- 7种Web实时消息推送方案
- 用户签到
- 用户注册-短信验证码
- SQLServer 删除同一天用户重复签到
- 软件研发完整流程
- 前端
- Redux
- 其他
- 百度云盘大文件下载
- 日常报错记录
- GIT
- SSL certificate problem: unable to get local issuer certificate
- NPM
- reason: connect ECONNREFUSED 127.0.0.1:31181
- SVN
- SVN客户端无法连接SVN服务器,主机积极拒绝
- Python
- 基础
- pyecharts图表
- 对象
- 数据库
- PySpark
- 多线程
- 正则
- Hadoop
- 概述
- HDFS