货拉拉-营销活动-玩法引擎建设

1,139 阅读9分钟

背景

随着货拉拉业务的不断发展,业务侧不断探索精细化活动运营策略,以拉新、促活、转化和留存等多个环节为切入点,不断丰富和完善营销活动玩法。随着营销活动玩法的不断迭代,原有的活动玩法架构已经不足以支撑活动玩法的开发迭代。原有的活动玩法代码在玩法间存在高度耦合,不利于后续需求的迭代,如活动玩法新增、玩法代码改动;改动到玩法的公共代码时,影响的活动玩法都需要回归测试,增加测试工作量;代码中还存在滥用ThreadLocal传递参数、再次查询缓存和db的情况,影响了服务性能。基于以上问题,我们设计了营销活动的“玩法引擎”,玩法引擎不仅有一套开发契约,活动玩法遵循该契约就能实现代码开发隔离、运行隔离;也有一套可以规避代码腐化的开发机制,活动玩法开发必须遵循该机制;还有一套用于业务流程编排的能力抽象设计,用于支持活动玩法的流程编排

1. 活动玩法介绍

营销活动玩法支持多业务、多活动、多奖励的多端投放,简称活动玩法玩法

活动玩法按照类型分抽奖活动、领券活动、助力活动、邀请好友活动、签到活动、砍价活动、拼手气活动、投注活动等。

图1.png

2. 玩法引擎介绍

2.1 为什么要设计玩法引擎?

2.1.1 活动玩法现状

1. 活动玩法流程不固定,流程节点调用顺序通过硬编码实现,不同玩法流程选择流程入口方法。

2. 流程节点基本固定,后续变动概率小,如活动准入、活动风控、完成任务、活动页渲染等。

3. 活动玩法代码耦合度高,每个流程节点或多或少存在各种活动类型维度的判断。

图2.png

2.1.2 为什么要自建引擎?

为什么要自建引擎,而不用成熟的流程引擎?

  • 引入流程引擎会增加活动玩法开发的复杂度,如流程引擎的学习成本与开发规范,安装流程绘图插件,不方便debug调试等。
  • 流程引擎编排粒度过于灵活,编排时同样需要先进行流程节点抽象设计,并制定活动玩法编排契约;若不进行抽象设计,玩法业务流程生成的流程图会比较复杂,流程文件内容会很多。
  • 成熟的流程引擎在启动时,加载的配置可能比较多,影响系统的启动速度

基于以上几点考虑,决定自建玩法引擎

玩法引擎的设计目标

1. 简单易用性

2. 玩法能力可扩展

3. 玩法隔离

4. 玩法编排

5. 方便调试

6. 代码防腐化机制

7. 与Spring无缝集成

2.2 玩法引擎设计

2.2.1 玩法引擎分层设计

玩法引擎在活动玩法上分三层,最上层是活动玩法层,中间层是能力编排层,最底层是流程节点层

1. 流程节点层对应活动玩法的业务流程节点,玩法流程由流程节点组成,可视为基础能力

2. 能力编排层是流程节点在活动玩法上的多态扩展,能力通过玩法引擎进行编排,生成玩法配置文件。

3. 活动玩法层对应我们各式各样的营销活动玩法,不同顺序的能力编排组合可以让活动玩法流程多样化,不同活动玩法对应不同的玩法配置文件

图3.png

2.2.2 玩法能力构建

1. 在玩法引擎中,玩法能力是由流程节点活动玩法组合而成的,简称能力

2. 流程节点是基于玩法业务流程梳理出来的,每个流程节点都有通用能力,即默认实现。

3. 能力采用二级存储结构,以流程节点标识作为一级存储结构玩法类型作为二级存储结构。

4. 能力有唯一标识,标识命名使用一级存储结构加**#再加二级存储结构**的规范,通用能力标识则二级存储结构标识为空字符串;如load.model代表通用的加载模型数据能力,load.model#inviteFriends代表邀请好友活动的加载模型数据能力(inviteFriends为邀请好友玩法类型)。

5. 能力注册:系统启动时,Spring Bean初始化,玩法引擎根据能力注解扫描能力并完成注册。

能力构建演化

图4.jpg

为什么能力存储使用二级存储结构?

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有异曲同工之效。

图5.jpg

为什么玩法能力接口是两个方法,而不是一个方法?

1. 这样的设计严格遵循设计模式的原则,如开闭原则、接口隔离原则、单一职责原则等。

2. 当前接口设计使用了命令设计模式,将请求封装为一个对象,再根据请求对象进行处理。

3. 有助于降低玩法代码腐化的可能性,因为上述设计最容易腐化的对象就是上下文对象,而上下文对象只在构建请求对象时能用到,在请求处理时,只能用到请求对象与领域模型对象,通过命令设计模式降低代码腐化的可能性。

4. 这种设计利于能力的扩展,既可以在map2Request进行覆盖,也可以在handle2Request进行覆盖

2.3.2能力编排设计

1. 能力编排通过视图(View)关联,用于表示流程图的连接线,视图通过名称标识,支持自定义。

2. 引擎侧包含玩法流程配置工厂、玩法能力接口、视图枚举等。

3. 业务侧包括玩法流程配置文件、基于能力接口实现的玩法能力等。

4. 我们用加载模型活动准入活动风控页面渲染错误页渲染等能力举例说明。

不同活动的页面渲染编排举例

图6.jpg

2.4 引擎执行流程

2.4.1 玩法配置文件

1. 玩法配置文件是玩法引擎用于加载与执行的流程配置,默认使用json文件格式解析。

2. 每个活动玩法,可以自定义生成一个或多个流程配置文件,即某个活动玩法内部流程存在差异,支持拆分多个流程配置文件进行编排。

3. 配置文件开始节点视图(View)为默认为start,支持自定义配置。

4. Spring启动时,玩法引擎会根据配置的文件目录,扫描玩法配置文件,解析并加载到内存中。

2.4.2 玩法执行流程

图7.png

3. 玩法配置举例说明

3.1 抽奖活动玩法样例

使用到的能力

图8.png

抽奖活动页面渲染编排举例

图9.jpg

流程编排配置文件

{  
"start":"load.model",  
"load.model": {  
"成功":"risk.access#lottery"  
},  
"risk.access#lottery":{  
"准入":"activity.access",  
"不准入":"page.error"  
},  
"activity.access":{  
"准入":"activity.page#lottery",

3.2 邀请好友活动玩法样例

使用到的能力

图10.png

邀请好友活动页面渲染编排举例

图11.jpg

流程编排配置文件

{  
"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会出现循环调用),后续玩法引擎扩展将支持更多的流程编排文件解析器。