本文基于面向基本公共卫生的业务系统设计经验,抽象出一套适合大型ERP系统的表单业务数据模型,目标是最大限度保留系统弹性的同时,尽可能降低系统复杂度和开发成本。enjoy~
背景
填写表单应该是所有业务线条中最避免不了的环节,例如我所经历的医疗项目:
以上面两个例图作为示例,可以看到姓名、性别、出生日期、血型等字段是完全重复的,由于业务场景的差异,表单被定义了不同的样式和字段结构,此时将遭遇以下几种问题:
- 同一用户经历了两个不同场景时,不得不重复填写相同的字段;
- 如果相同的字段在两个表格中的值不同,基本无法判断哪个为正确值,例如同一个人在
居民健康档案
中血型填写为A型,而在居民健康档案信息卡
中填写为B型; - 某些字段会重复出现在不同的表单中,随着业务需要,将其串连起来查看其趋势,如身高、体重、血压、心率等等,以帮助医生确诊疾病,然而这些字段保存在各自的表单中,由于开发人员的变更、文档的遗漏和产品的迭代,无法穷举出所有的这些字段数据来源,即便能够回溯所有的来源,本身也是一件十分消耗精力的事情;
- 因为政策或业务需要,要在原有的表单上做调整,新的标准导致表单字段产生变化,此时原有系统为保证其运行的稳定,难以从数据表和底层代码中迭代,只能新增数据表做开发,当表单需求频繁变化时,加剧数据碎片化的问题;
- 新增业务表单时,开发需要订排期,用户需要等待发版后才能使用,新增大量表单时影响原有开发计划的同时,业务部门也难以快速开展系统业务。
- 做数据统计和分析时,由迭代造成的数据字段遗失或变更,无法统计出完整而准确的数据,做出的报告难以反映出真实的情况
....
传统的区域化基本公共卫生系统正在经历这样的剧痛,当然其他行业比如金融的部分业务同样面临相同问题(本人只经历过这两个行业,见谅),如何在纷繁复杂的业务环节中抽离出四两拨千斤的数据模型,除了满足日益频繁的业务调整外,还能将数据完整的、标准的存储并利用起来,是后端产品经理的安身立命之本。作为一个不务正业的产品经理,这次就从数据库表结构设计上,介绍一套解决方案:结构化动态表单。
场景和需求:
1.可覆盖绝大部分表单业务场景。
2.表单样式和字段可灵活调整,不影响历史积累数据,不会造成数据库和代码层面的频繁变更;
3.数据统计时能够快捷、准确、全面地获取到想要的字段数据,不过度依赖文档和程序员老员工;
模块介绍
属性库
所有表单中所有的字段都在属性库中定义,相当于表单字段的字典。定义的核心包含属性的唯一标识、属性名、属性值取值规则和约束等信息。
因为我认为所有的字段都是围绕某个业务进行的,把这个业务抽象成对象,那么这些表单的字段就是这个对象的属性,所以命名为属性库
如果用关系型数据库表达属性库,根据以往的经验可以总结出如下两个基础表:
属性分类,主要根据使用者需求对属性进行分类,方便查找和后期的批量数据统计,比如健康管理把心率、瞳孔大小、脉搏等属性规划到生命体征类,把身高、体重等属性规划到基本体征类等等,因此仅需要定义唯一识别码、名称和分类说明即可。
属性,这个表非常重要,是数据标准化的基础表。唯一标识、名称、说明,这是一个属性最基础的说明,不用解释。
- 分类ID字段可支持多个ID,表示一个属性可划分到多个分类下,这个可根据实际需求定义,我所经历的场景是有这种情况的,比如心率,既可以是生命体征类属性,也可以是临床诊断类属性,很难绝对界定。当然属性和属性分类也可以通过单独建关系表来定义对应关系,方法有很多,各有优劣,看技术leader自行选型吧。
- 属性类型,根据个人的经验,总结出图中的几种类型,相信大家都认识,不用展开,其中单选框、多选框两种类型因为还依赖对应的取值字典,因此还需要到属性值字典中定义取值选项。另外
值单位
这个字段,方便做数据转换和终端数据展示用,比如时长的值60,单位是分钟,通过算法即可转换该单位的值为1小时。
属性值字典,主要用于配合属性类型为单选框或多选框的取值,也是数据标准化的一部分。
例如定义一个属性叫性别的属性规划到基础信息分类中,此时会属性库的三个表中分别插入以下数据:
属性分类表:ID=‘1’,分类名称=‘基础信息’,分类说明=‘用户基本信息’
属性表:ID=2,分类ID=‘’,属性名称=‘性别’,属性说明=‘用户的性别’,属性类型=‘单选下拉框’,值单位=空
属性值字典:[ID=3,属性ID=‘2’,字典值=‘男’],[ID=4,属性ID=‘2’,字典值=‘女’],[ID=5,属性ID=‘2’,字典值=‘未知’]
模版库
所有的动态表单都是以模版的方式保存在数据库中,表单模版中定义表单中填写的字段、字段的默认值和表单样式。
由于表单样式的不可预见性,因此可以准备一套符合自家产品风格的视觉设计语言,限定表单视觉样式的框架,包含前面提到的属性类型呈现样式,和细化到UI在手写、PC端、移动端的字体大小、线条风格、交互方式、间距、缩进、比例、布局方式等参数,当然本篇由于篇幅限制不展开和视觉风格相关的讨论,读者可自行脑补。
既然是模版,肯定少不了控件,模版由控件组成,在这里把控件分为两类:属性组件和容器组件。
表单模版,是表单的字典表,用于定义表单的基础信息如名称、用途说明等,如果与业务衔接,还可以添加关联的业务、填写对象、触发填写的时间等,这部分信息由具体的业务场景决定,可根据实际需求设置字段。
容器组件,负责定义外观样式的组件,决定了属性组件在表单中的呈现样式,可根据不同布局需求细分更多容器组件,这里不展开细讲。
- 顺序号,在同级下的显示排序,从左至右,从上至下的原则进行排列。
- 容器名称,即表单中某方框的名称,可不填
- 在终端显示表单时,需要充分考虑各个组件在页面上的默认布局参数和可变参数。通常前面提到的设计语言中会定义标准的内边距外边距、线粗和线色等视觉样式,这些就是默认布局参数,但组件在表单中的显示顺序、嵌套关系和组件内的组件排列方式等参数多数时候是需要配置的,依据实际需求添加参数即可。
- 容器组件可嵌套,当遭遇多级层级关系时,用容器组件实现嵌套关系再好不过了,不建议属性组件也支持嵌套,因为会提高属性值的取值复杂度,除了开发和数据存储逻辑复杂度高外,后期数据分析时也会进入逻辑黑洞,应尽量避免
- 是否支持累加数据,此字段用于控制组件内的元素,是否可以按照定义的字段多组生成,例如如:
在容器组件主要用药情况
中,属性组件药物名称
、用法
、用量
、用药时间
、服药依从性
的值可以添加多次
- 还可以添加跟多字段或子表,描述容器更多的视觉布局样式,比如支持PC端、移动端、打印手写的样式定义。
属性组件,来自于属性库中的属性,决定了表单中填写的字段信息。
- 容器ID,当前属性组件放置在某个容器组件内,若值为null,表示直接放置在表单中
- 属性别名,为适应部分个性化的需求,可以为属性定义别名,比如身高,对婴儿通常叫身长,对青少年或成年人叫身高。别名定义到模版中而不是在属性库的意义在于,用户的个性化称呼通常只会在自己所处的场景内使用,对于其他场景下的其他用户并不一定通用。
- 属性默认值,很好理解,没用把这个字段放到属性库的理由和别名一样,场景不同,默认值不一定通用。
- 是否必填,表单提交前判断必填项的依据。
- 页面区域,用于判断当前组件出现在其父组件的位置,枚举类型。
属性组件还可以有更多可扩展特性,后面会提到一些。
业务数据库
有了前面的属性库和表单模版库的配置,即可配置出各式各样的表单,而实际使用这些表单保存下来的数据格式是怎样的呢?
表单主表,作为表单的索引表,主要是提供表单的填写来源、时间戳和与业务相关的标记。
- 通常实际业务有很多附加的信息,图中给出的是本人面临的业务场景常见的字段。
容器明细表,这里保存了表单内负责样式的容器数据,因为表单模版可能会变更,因此需要将其视觉样式数据保存下来,以记录当时呈现的样式,避免因为模版变更而造成的布局样式丢失。
属性明细表,保存了所有表单的所有字段的值。
- 为了配合容器组件记录其布局样式,还添加了容器ID、顺序号、组号、页面区域,用于记录保存表单时属性在表单中的位置。
- 组号,当遇到属性组件放置在允许累加数据的容器组件中时,标记出属性值所在的组。
- 别名,若属性在模版中配置了别名,则保存在这里,如果值为空,则显示原属性名。
- 修改时间,表单可能会遇到修改部分数据,因此标记字段的最后修改时间。如果有属性值的操作日志,可以不要。
样例
弊端
- 动态表单的存在,在一定程度上可以缓解产品迭代因业务变更带来的压力,但其开发复杂度较高,尤其表单模版,解析模版数据呈现到终端时,依赖遍历算法,对程序员的要求不低,若整套产品的应用规模不大,不建议使用动态表单,或者根据需求开发简配版。
- 由于表单依赖属性库和表单模版的配置,属性和表单模版的维护质量决定了表单的数据质量,因此需要有高度责任心和专业能力的人员来进行属性库的维护,提高了使用门槛,但反过来讲,罗马不是一天建成的,如果有野心建立行业标准,本身也需要大力投入数据质控。
设计思想和基本原则
- 产品开发和业务运行尽可能的解耦。业务人员不必完全依赖产品业务功能的情况下才能运行相关业务(这个问题单独靠动态表单无法完全解决,还需要依赖工作流,不过没有动态表单时也可以勉强适应部分场景做业务试运行);
- 产品永远做功能迭代,尽量避免数据迭代。常见的C端产品往往会有很多的营销推广广告页,这些广告页常常会频繁变化,而且为了抓热点往往需要即时响应跟进,如果按照每周发版1-2次的节奏,等发出来商业机会已经凉了,因此往往会做一个后台配置广告页的功能,使运营人员可以自行配置广告页面,包含页面元素、入口布局、外链引导、渠道埋点配置等等,这就是功能迭代。如果运营改一次广告,产品即发一次版,这就是数据层迭代。每一次变更都将累积相应的数据,产品是生成这些数据的工具,产生数据是业务人员做的事情,产品和业务是冰淇淋机和卖冰淇淋的小姐姐的关系。
- 用户永远只能看到功能,只关心产品是否满足其需求,而产品经理永远要从高低远近多维视角看待和解构需求,不断的整合、重组、拆分、归纳,穷举各种场景下的业务形态,在业务耦合和模块化处理上达到平衡,本质上是优化效率,创造利润,如果达不到这两个目的,这个产品不能叫产品,只叫艺术品。
- 提前预判功能模块的发展趋势,在设计初期预留充分的扩展性和迭代方向,避免高频率的推倒重来,当然如果是敏捷开发,请无视这条。
意义
- 属性库即数据规范,如果数据量在行业中足够大,适用面足够广的情况下,具备发展为行业规范的潜力。
- 表单模版即数据接口标准,当多个系统需要进行数据对接时,最头疼的往往是梳理数据对接标准,将需要对接的数据模版通过接口规范的方式导出给对接方,数据字段和取值规范一目了然,新老系统导数据也会用到。
- 数据利用更加便捷方便,需要查询某项具体的属性数据时,只需到属性明细表中即可找到,无需遍历其他业务表单
- 用户可通过配置表单明确新需求,表单模版一定程度上提高了用户需求和功能落地的沟通效率,一定程度上提高了产品的业务可扩展性
扩展性
以下是随意举例的可能的功能扩展方向,仅作为扩展阅读
属性组件功能扩展
多属性之间的逻辑约束和默认值
- 性别为男时,诊断中不可出现妇科疾病
- 年龄在18-21岁之间,职业默认值为大学生
- 填写了身份证号码即可解析出籍贯、性别和出生日期
单属性复用
- 姓名性别等字段用户一旦填写后,以后再次填写有这些字段的表单即可自动填写
属性值字典
诊断、症状字典数据可能依赖外部接口调取,而非本地属性值字典库
容器组件扩展
- 可配置容器背景图,视觉优化
- 内部元素排列方式支持左对齐、右对齐、垂直排列、水平排列等
- 可套用整体视觉设计的皮肤
模版库扩展
- 模版插入公用参数字段,如表单中的制表机构、填表人、所在科室等等
- 表单中的字段可作为工作流业务环节控制字段,比如当支付方式为现金时,无需弹出支付二维码
操作日志
- 记录所有用户在业务表单上的操作记录,包含操作人、时间戳、修改前属性值
- 记录所有用户在属性库和表单模版库上的操作记录