如何利用 Serverless 的特性又不至于放飞自我。
缘由
简单心理的主系统基于 Ruby on Rails 已运行了 4 年多,随着业务发展,单体应用的局限性越来越明显,即使通过容器化大幅提升了其负载能力,简化了部署过程,但对于开发人员来说,随着时间的推移,系统内的耦合不可避免的越来越严重,开发体验越来越差。
在这个背景下,我们先尝试了微服务架构,微服务的高内聚特性,虽然可以较好的解决当前业务复杂度下系统耦合严重的问题,但若随着时间推移,部分复杂的微服务依然可能会发展成一个内部耦合严重的单体应用。
于是我们又把目光继续往前,开始尝试 Serverless。
Serverless 简单来说是一个事件驱动的全托管计算服务,你只需写好业务代码,剩下的事情托管给服务商即可。
听上去很简单,实际上手操作也的确非常容易。但问题就在于它过于简单,以至于对于复杂的业务需求,没有任何范式可参考。
单体应用有很多成熟的框架可以使用,并且这些框架大部分在微服务中也可以使用。但到了 Serverless,会突然陷入无框架可用的境地。虽然你可以把一个 MVC 框架上传上去当成一个可以处理多种请求的“超级函数”,但这会导致开发和维护的困难。
Serverless 提倡一个函数只解决一个问题,使得开发人员可以专注于具体的业务流程及环节,不必为了某个小的业务迭代而花费大量时间熟悉整个系统和整个业务。但随着函数数量的增多,如果没有一定的约束,开发人员肯定会放飞自我,造成大量碎片化的问题。我们的解决办法就是建立一套人为的约束机制,让所有函数都可以被方便的理解和开发维护。
这套机制主要目标是让开发人员可以快速理解和迭代业务环节,并可以方便的拓展新业务。(因为简单心理的日常任务主要是优化各项业务环节,以及尝试新业务)。
实践指南
本实践指南仅仅是简单心理内部尝试的实践方式,仅供参考,欢迎讨论完善。
首先,本实践指南基于腾讯云构建,不一定适合其它服务商,我们主要使用以下服务:
为了解决函数碎片化问题,实践的核心思想是:
将函数抽象为基于事件触发的业务环节。
同时,基于领域驱动的设计,我们把业务环节抽象为指令
,而所有函数被区分为指令处理函数
和事件响应函数
两类。
指令
被处理的流程如下:
事件响应函数
将事件转化为具体指令,存入相应的指令队列;- 调度函数(本质是
定时指令响应函数
)根据设定的规则,将指令从队列中取出; - 取出的指令被异步分发到对应的
指令处理函数
; 指令处理函数
完成指令。
最终,我们对函数的命名做如下规定:
- 命名格式:
<类型名>--<业务领域名>--<资源名>--<指令名>
,总共分四级; - 类型名全部大写,其余全部小写;
- 生产环境有以下类型:
- PE 指令处理函数
- PC 定时指令 响应函数
- PI 内网网关指令 响应函数
- PO 外网网关指令 响应函数
- PW 前台网关指令 响应函数
- PD 后台网关指令 响应函数
- 测试环境下,类型的第一个字母为
T
,如TE
、TC
等; - 开发环境下,类型的第一个字母为
D
,如DE
、DC
等; - 若名字由多个单词组成,互相之间用
-
连接,如PE--user--user-account--create
。
于是,实际开发人员的工作流程如下:
- 如需跟客户端配合,则先新建或修改开发环境下的API网关的模拟数据
- 创建或修改测试环境的函数,并完成测试验收
- 将测试环境的代码同步到生产环境,并完成生产环境的验收
这种开发模式下,一个函数同一时间段里最多只允许一个开发人员修改。开发人员可以根据需求的复杂情况决定改动一个还是多个函数。