作者:XIAOJUSURVEY
导读
XIAOJUSURVEY是滴滴开源的一套调研管理系统,提供面向个人和企业的一站式产品级解决方案,用于构建各类问卷、考试、测评和复杂表单。在产品形态上,XIAOJUSURVEY包含了B端和C端的完整能力,在技术上,XIAOJUSURVEY是一个全栈、跨端的项目。
在线平台:xiaojuwenjuan.com
LogicEngine是XIAOJUSURVEY用于实现业务多种逻辑编排能力的逻辑引擎,本期我们将围绕LogicEngine的架构原理进行分享。【文章最后有一波福利】,祝各位大佬2025快乐。
背景
问卷逻辑编排用于实现面向不同受访者的定制问卷,以满足不同场景的采样方案。
基于此功能,可以根据不同条件设定题目/选项间的逻辑关系,例如选择某个题目的选项后,显示或跳过某些问题等,以下是简单的演示。
LogicEngine
在实现上,需要考虑用户不同的操作、题目怎么配置相应目标题显隐逻辑等。基于传统的业务含义设定的配置驱动开发虽然也能满足需求,但在应对需求变化时往往需要大量的手动配置和代码编写,无法一套架构满足多样化业务逻辑的同时,支持系统扩展。
结合内部场景经验,我们基于规则引擎的设计思路构建了垂域的逻辑引擎 — LogicEngine。
B端(问卷管理)和C端(问卷投放)对于问卷逻辑的关注点不同,B端主要是问卷逻辑的可视化编排,C端主要是规则的应用,基于此LogicEngine主要实现逻辑编排和逻辑解析的管理。
过程中可以看到,LogicEngine不仅满足了复杂的业务场景拓展,还能够将视图部分进行解耦,下面会进行具体讲解。
原理
1、领域设计
问卷逻辑编排实际上是问卷题目/选项之间关系的表达,即一系列规则条件和多目标的关系构建和应用。领域模型上分为RuleNode和ConditionNode:
- ConditionNode:维护规则条件。
- RuleNode :维护规则条件和目标。
2、架构设计
2.1、RuleBuild
逻辑配置管理
RuleBuild主要用于将问卷逻辑进行规则化描述,即构建RuleNode和ConditionNode:
逻辑关系管理
除了考虑逻辑配置的增删查改,RuleBuild还需要维护题目之间的逻辑关系。
比如配置题目2选中了选项1则显示题目3,那么题目2或题目3的删除会使规则组中存在一条失效的规则配置。
RuleBuild提供了用于题目关系管理的方法:
- findTargetsByFields: 根据前置题查找目标题
- findTargetByScope:根据目标作用范围查询目标题
- findConditionByTarget:根据目标题查找关联条件中的前置题
2.3、RuleMatch
RuleMatch的核心是动态计算结果,即根据用户的答题内容,进行题目显隐渲染。
实现上其实就是对规则组进行遍历匹配,考虑到规则配置存在组合嵌套的情况,我们采用了hash表的方式,将RuleNode和ConditionNode构造成一个题规则Map:
[target][scope]: [{
[field][operator][condition.value]: match(formvalues) // formvalues:整卷的输入内容
}]
举例,用户选中了题目1 (id: "data472")的选项2 (id: "115020"),需要显示题目5 (data515):
...
"data472question": [{
"data515in115020": true,
...
}],
...
关于结构字段可查看:《问卷业务协议规范》、《问卷业务协议规范和原理》
场景案例
显示逻辑配置(B端)
题目schema:
// 为演示更清晰,转换成了文字描述
题目1: "data515"
选项1: "115019"
选项2: "115020"
题目2: "data234"
选项1: "392497"
选项2: "097235"
题目4: "data700"
选项1: "328312"
选项2: "079366"
题目5: "data472"
显示逻辑配置:
通过RuleBuild得到如下规则配置:
显示逻辑渲染(C端)
题规则Map:
借助computed有计算缓存,实现渲染:
// QuestionWrapper.vue
<QuestionRuleContainer ...></QuestionRuleContainer>
...
// 显示逻辑:题目是否需要显示。当match有变化的时候触发重新计算
const logicShow = computed(() => {
const result = showLogicEngine.value.match(fieldId, 'question', formValues)
return result // true || false
})
扩展跳转逻辑
下面分析如何基于以上架构快速实现跳转逻辑。
异同点分析
在 显示逻辑 中表述为:
题目1 符合条件 且 题目2 符合条件,则显示题目4
题目1 符合条件,则显示题目5
在 跳转逻辑 中表述为:
题目1 符合条件 或 题目2 符合条件,则跳转到题目4(隐藏题目3)
题目1 符合条件,则跳转到题目5(隐藏题目3、4)
在跳转逻辑里,条件需要控制多个题目,最终作用到的是条件题到目标题之间的那批题目。
如上:题目1符合条件时,需要根据题目5查找题目1到5之间的题目进行隐藏,即题目3和4。
扩展实现
RuleBuild无需调整,视图上区别于显示逻辑的表单配置,我们采用了流程图的形式:
扩展RulesMatch满足跳转逻辑:
// RulesMatch.ts
match() {
...
Array.from(this.conditions.entries()).some(([, value]) => {
return !!value.match(fact)
})
...
}
检索要跳过的题目:
// 获取条件题关联的多个目标题匹配情况
getResultsByField(field: string, fact: Fact) {
...
return rules.map(([, rule]) => {
return {
target: rule.target,
result: this.match(rule.target, 'question', fact, 'or')
}
})
}
总结
项目基于LogicEngine已经实现了基础的显示及跳转逻辑,支持的功能如下,欢迎对这个方案比较感兴趣的同学一起交流:
写在最后
一波福利,参与年底活动,获官方精美周边。
参与方式:转载文章、原创文章、项目案例分享
活动截止时间:2025年02月28
资料推荐:
《解构领域驱动设计》张逸
《大道至简:软件工程实践者的思想》周爱民
《架构整洁之道》罗布特C.马丁