背景
| 随着货拉拉业务的不断发展,业务侧不断探索精细化活动运营策略,以拉新、促活、转化和留存等多个环节为切入点,不断丰富和完善营销活动玩法。随着营销活动玩法的不断迭代,原有的活动玩法架构已经不足以支撑活动玩法的开发迭代。原有的活动玩法代码在玩法间存在高度耦合,不利于后续需求的迭代,如活动玩法新增、玩法代码改动;改动到玩法的公共代码时,影响的活动玩法都需要回归测试,增加测试工作量;代码中还存在滥用ThreadLocal传递参数、再次查询缓存和db的情况,影响了服务性能。基于以上问题,我们设计了营销活动的“玩法引擎”,玩法引擎不仅有一套开发契约,活动玩法遵循该契约就能实现代码开发隔离、运行隔离;也有一套可以规避代码腐化的开发机制,活动玩法开发必须遵循该机制;还有一套用于业务流程编排的能力抽象设计,用于支持活动玩法的流程编排。 |
|---|
1. 活动玩法介绍
营销活动玩法支持多业务、多活动、多奖励的多端投放,简称活动玩法、玩法。 活动玩法按照类型分抽奖活动、领券活动、助力活动、邀请好友活动、签到活动、砍价活动、拼手气活动、投注活动等。 |
|---|
2. 玩法引擎介绍
2.1 为什么要设计玩法引擎?
2.1.1 活动玩法现状
1. 活动玩法流程不固定,流程节点调用顺序通过硬编码实现,不同玩法流程选择流程入口方法。 2. 流程节点基本固定,后续变动概率小,如活动准入、活动风控、完成任务、活动页渲染等。 3. 活动玩法代码耦合度高,每个流程节点或多或少存在各种活动类型维度的判断。 |
|---|
2.1.2 为什么要自建引擎?
为什么要自建引擎,而不用成熟的流程引擎?
- 引入流程引擎会增加活动玩法开发的复杂度,如流程引擎的学习成本与开发规范,安装流程绘图插件,不方便debug调试等。
- 流程引擎编排粒度过于灵活,编排时同样需要先进行流程节点抽象设计,并制定活动玩法编排契约;若不进行抽象设计,玩法业务流程生成的流程图会比较复杂,流程文件内容会很多。
- 成熟的流程引擎在启动时,加载的配置可能比较多,影响系统的启动速度。
基于以上几点考虑,决定自建玩法引擎。
玩法引擎的设计目标
1. 简单易用性
2. 玩法能力可扩展
3. 玩法隔离
4. 玩法编排
5. 方便调试
6. 代码防腐化机制
7. 与Spring无缝集成
2.2 玩法引擎设计
2.2.1 玩法引擎分层设计
玩法引擎在活动玩法上分三层,最上层是活动玩法层,中间层是能力编排层,最底层是流程节点层。 1. 流程节点层对应活动玩法的业务流程节点,玩法流程由流程节点组成,可视为基础能力。 2. 能力编排层是流程节点在活动玩法上的多态扩展,能力通过玩法引擎进行编排,生成玩法配置文件。 3. 活动玩法层对应我们各式各样的营销活动玩法,不同顺序的能力编排组合可以让活动玩法流程多样化,不同活动玩法对应不同的玩法配置文件。 |
|---|
2.2.2 玩法能力构建
1. 在玩法引擎中,玩法能力是由流程节点与活动玩法组合而成的,简称能力。 2. 流程节点是基于玩法业务流程梳理出来的,每个流程节点都有通用能力,即默认实现。 3. 能力采用二级存储结构,以流程节点标识作为一级存储结构,玩法类型作为二级存储结构。 4. 能力有唯一标识,标识命名使用一级存储结构加**#再加二级存储结构**的规范,通用能力标识则二级存储结构标识为空字符串;如load.model代表通用的加载模型数据能力,load.model#inviteFriends代表邀请好友活动的加载模型数据能力(inviteFriends为邀请好友玩法类型)。 5. 能力注册:系统启动时,Spring Bean初始化,玩法引擎根据能力注解扫描能力并完成注册。 |
|---|
能力构建演化
为什么能力存储使用二级存储结构?
1. 原生特性:能力本身就是流程节点与活动玩法的组合,有必要保留原生特性。 2. 可视化考量:后续可视化可以清楚的知道有多少流程节点,有多少活动玩法。 |
|---|
为什么流程节点标识作为一级存储结构,玩法类型作为二级存储结构?
1. 第一种方式:使用玩法类型作为一级存储结构,流程节点标识作为二级存储结构。
2. 第二种方式:使用流程节点标识作为一级存储结构,玩法类型作为二级存储结构
3. 从能力表示上看,考虑到流程节点与玩法类型是笛卡尔积的方式组合,第一种方式与第二种方式,能力表示上没有本质差异。
4. 从流程编排上看,第一种方式侧重点是活动玩法,必须先有活动玩法;而第二种方式侧重点是流程节点,活动玩法可以在流程节点上扩展,意味着可以在现有能力的基础上,直接编排新玩法。
| 基于以上几点考虑,最终选择了第二种方式;把流程节点前置,玩法后置,理念有点类似于倒排索引。这种设计思想,可以适用于各个业务,并支持在已有能力的基础上赋能新业务。 |
|---|
2.3 玩法引擎编排
2.3.1 引擎编排维度
1. 玩法引擎是基于玩法能力的编排,玩法能力需要实现玩法能力接口(MarketingAbility)。 2. 玩法能力接口(MarketingAbility)用于标识能力,用于描述流程节点的功能,简称玩法能力、能力。 |
|---|
2.3.2 玩法能力接口
1. 玩法能力接口(MarketingAbility)主要包含两个方法,一个是map2Request,即根据领域模型与请求上下文构建请求对象;另一个是handleRequest,即根据领域模型与请求对象执行动作处理,并返回结果对象(ResultAndView)。 2. 返回结果是ResultAndView对象,不仅包含结果值(数据域data),还包含结果视图(View),ResultAndView和Spring MVC的ModelAndView有异曲同工之效。 |
|---|
为什么玩法能力接口是两个方法,而不是一个方法?
1. 这样的设计严格遵循设计模式的原则,如开闭原则、接口隔离原则、单一职责原则等。 2. 当前接口设计使用了命令设计模式,将请求封装为一个对象,再根据请求对象进行处理。 3. 有助于降低玩法代码腐化的可能性,因为上述设计最容易腐化的对象就是上下文对象,而上下文对象只在构建请求对象时能用到,在请求处理时,只能用到请求对象与领域模型对象,通过命令设计模式降低代码腐化的可能性。 4. 这种设计利于能力的扩展,既可以在map2Request进行覆盖,也可以在handle2Request进行覆盖。 |
|---|
2.3.2能力编排设计
1. 能力编排通过视图(View)关联,用于表示流程图的连接线,视图通过名称标识,支持自定义。 2. 引擎侧包含玩法流程配置工厂、玩法能力接口、视图枚举等。 3. 业务侧包括玩法流程配置文件、基于能力接口实现的玩法能力等。 4. 我们用加载模型、活动准入、活动风控、页面渲染、错误页渲染等能力举例说明。 |
|---|
不同活动的页面渲染编排举例
2.4 引擎执行流程
2.4.1 玩法配置文件
1. 玩法配置文件是玩法引擎用于加载与执行的流程配置,默认使用json文件格式解析。 2. 每个活动玩法,可以自定义生成一个或多个流程配置文件,即某个活动玩法内部流程存在差异,支持拆分多个流程配置文件进行编排。 3. 配置文件开始节点视图(View)为默认为start,支持自定义配置。 4. Spring启动时,玩法引擎会根据配置的文件目录,扫描玩法配置文件,解析并加载到内存中。 |
|---|
2.4.2 玩法执行流程
3. 玩法配置举例说明
3.1 抽奖活动玩法样例
使用到的能力
抽奖活动页面渲染编排举例
流程编排配置文件
{
"start":"load.model",
"load.model": {
"成功":"risk.access#lottery"
},
"risk.access#lottery":{
"准入":"activity.access",
"不准入":"page.error"
},
"activity.access":{
"准入":"activity.page#lottery",
3.2 邀请好友活动玩法样例
使用到的能力
邀请好友活动页面渲染编排举例
流程编排配置文件
{
"start":"load.model#inviteFriends",
"load.model#inviteFriends": {
"成功":"activty.access#inviteFriends"
},
"activty.access#inviteFriends":{
"准入":"risk.access#inviteFriends",
"不准入":"page.error"
},
"risk.access#inviteFriends":{
"准入":"activity.page#inviteFriends",
"不准入":"page.error"
未来规划
1. 玩法引擎后续将提供丰富的可视化功能,如能力的可视化、玩法流程的可视化。 2. 玩法配置文件后续将支持动态创建、动态加载、动态生效功能,支持活动玩法的一键上线。 3. 玩法引擎的编排暂不支持单个能力多次调用的编排(如A->B->A,此时A会出现循环调用),后续玩法引擎扩展将支持更多的流程编排文件解析器。 |
|---|