1. 我为什么开始琢磨“平台定制交互三级架构”这件事
做平台产品的人应该都有过这种经历:售前拍着胸脯跟客户保证“我们平台完全支持定制”,然后需求文档传到研发手里,开发一看,差点当场离职。客户要的不是换个Logo换套皮肤,而是改业务流程、改数据字段、改审批链路,甚至要对接他们自己的一套老旧系统。平台如果不做定制,在市场竞争里基本没有活路;但定制如果真的放开了做,平台就会变成一团乱麻——每个客户的版本各改各的,升级一次炸一次,测试团队天天加班。
我这两年一直在做一个多租户的行业平台,客户来自不同的细分领域,有的做供应链管理,有的做设备监控,有的把平台当作内部办公入口。需求差异大得离谱,但底层能力又是同一套。为了不让项目走向“一个客户一个分支”的深渊,我们逐步沉淀出了一套结构,后来内部管它叫“平台定制交互三级架构”。这篇文章就把这套架构的来龙去脉、每一层的设计思路、以及落地过程中踩过的坑,原原本本分享出来。
所谓的“三级”,指的是交互表现层、业务定制层、数据与接入层。三级之间不通过改源码耦合,而是通过标准配置和标准接口衔接。一句话概括这套架构的核心主张:把“怎么展示”“怎么变”“怎么连”三个问题拆开解决,让定制能力有边界、可管控、不失控。
适合看这篇文章的人:正在做SaaS平台、企业级管理平台、物联网对接平台,或者任何需要承接大量定制需求的系统架构师、技术负责人。哪怕你暂时不做平台,只是给一个单体系统做模块化拆分,里面关于配置驱动和分层边界的思路也可以直接迁移。
2. 第一级:交互表现层——定制的最前沿,也是最容易失守的地方
2.1 交互层的定制应该管什么、不该管什么
交互层是所有用户直接接触的部分,也是定制需求最先冒出来的地方。客户一看平台界面,第一句话往往是“这个颜色能不能改成我们公司的蓝色”“首页能不能不要显示这个模块”“登录页能不能换个布局”。这些需求看起来简单,好像改改前端就行,但如果交互层的定制没有边界,后面一定会出事。
我一开始犯过的错误,就是把交互层当成“万能层”——客户要什么就改什么。改到后来,每个客户的界面结构都不一样,操作路径完全不同,前端组件被各种if else塞得乱七八糟。更可怕的是,有些客户连业务流程都想在前端改,导致页面代码里长出了业务逻辑。等到平台要做一次UI整体升级,发现根本没法动——一动就影响了几十个客户的“定制成果”。
后来我给自己定了一个规矩:交互层的定制只做三件事——视觉样式、组件组合、交互反馈。
视觉样式不用多解释,主题色、字体、间距、圆角、Logo、登录页背景,这些是纯粹的表现层。组件组合指的是同一个页面允许不同客户配置不同的字段展示、不同的列表排序、不同的图表维度,比如客户A的订单列表要显示“金额”列,客户B要显示“采购员”列,这是布局级的差异,不改变业务规则。交互反馈则是按钮点击、提交成功提示、错误信息展示、通知渠道选择这类“用户怎么感知平台”的东西。
这三类定制的共同特点:它们都在回答“界面长什么样”,不回答“业务怎么跑”。一旦某个需求开始触碰业务流程、状态流转、数据权限,就不该在交互层解决。
2.2 用配置驱动渲染取代硬编码界面
要支撑上面说的三类定制,前端不能继续用写死的页面结构。我们的做法是配置驱动渲染:页面布局不再由一份固定的代码决定,而是由一份Json Config描述——页面上有哪些模块,模块里套哪些组件,组件的数据源指向哪个接口,组件对哪些角色可见。
运营人员在可视化的页面配置器里拖拽调整布局,保存后生成一份schema,前端渲染引擎读取schema,动态渲染页面。给新客户开门户的时候,运营花半天就能搭出一个满足要求的首页,开发完全不用介入。这个效率比早期“等研发排期改页面”不知道高了多少倍。
但这里我要特别提醒一个容易被忽略的点:页面配置不能只有“形态层”,必须有“状态层”。什么意思?就是一个模块不能只能配“在不在页面上”,还得能配“在什么条件下出现”。比如“当用户是管理员时显示数据看板,普通员工不显示”,这种条件判断一定要纳入配置体系。如果页面配置只支持静态的“有没有”,那定制的深度远远不够,最后还是得靠开发改代码来补。
至于技术实现,我们用的是Schema加Render Engine的方案,组件库保持统一,组件可以在多模块间复用。页面的数据来源通过配置的API标识符动态绑定,前端不做数据加工,只做展示。
2.3 交互层定制的性能红线
配置驱动渲染不是没有代价。每一次渲染都要解析配置、算条件、加载对应组件,如果这些操作每次都从接口拉配置数据,页面性能会直线下降。最开始我们上线时,某个客户首页因为加载了十几份配置文件和组件脚本,首屏时间飙到3秒以上,被客户的运营负责人当面吐槽“你们的平台是不是卡死了”。
后来我们做了两级缓存:一级是CDN静态缓存,针对公共组件和页面框架;二级是服务端动态配置的缓存,用带版本号的缓存键,配置发布后自动失效。实测下来,首屏时间稳定压在了800ms以内。
我还想补充一个经验:配置驱动渲染对前端基建要求不低,组件必须模块化、协议统一,否则配出来的页面会风格混乱。如果团队前端能力还比较初级,先把组件体系梳理清楚,再上配置驱动,否则配置平台只会输出一堆科技感很差的页面。
3. 第二级:业务定制层——真正的复杂度都集中在这一层
3.1 把业务逻辑从代码里抽出来,放进可配置的容器
业务定制层是三级架构里最核心、也最容易失控的一层。交互层解决的只是“好看”,业务定制层解决的是“好用”——流程怎么走、规则怎么判、权限怎么分、数据范围怎么划。
早期服务客户的时候,我们习惯于把每个客户的需求直接写成代码逻辑。客户说“金额大于1万的订单需要部门经理审批”,就在审批服务里写了个if判断。第二个客户来说“超过5万的还要财务复核”,再堆一个if。半年之后,审批服务里塞了几十个if else,每个客户的分支相互缠绕,没人敢动,也没人能说清楚某条规则到底服务了谁。
后来我们开始把规则抽离成可配置项。还是审批的例子:把审批动作抽象成节点类型——审批节点、条件节点、子流程节点、外部接口节点,然后用流程编排的方式把节点连起来。管理员在配置界面上拖出一条“金额大于10000 -> 部门经理审批 -> 金额大于50000 -> 追加财务复核”,保存即生效。客户改规则,不再需要走发版流程。
3.2 规则引擎的选型建议:别一上来就上重型武器
提到业务规则配置,很多人第一反应是上规则引擎。Drools、Easy Rules、Camunda,开源产品不少。但我的经验是不要一上来就引重型引擎,先把业务动作抽象成有限的节点类型,用流程编排解决八成以上的定制需求。
重型规则引擎适合复杂计算和推理场景,但对大多数业务定制而言,“审批流加条件分支”用工作流引擎或者自己写一个轻量的节点编排器就足够了。我们现在的方案是自己维护的流程编排引擎,节点类型大概有十几类,覆盖审批、条件判断、子流程、外部接口调用、消息通知、延时等待等。每个节点有独立的配置界面,非技术人员经过简单培训也能完成大部分流程搭建。
只有当我们遇到一些需要复杂策略计算的需求(比如多因素交叉判断、动态定价),才会考虑引入独立的规则引擎模块。而且这个模块也是独立部署的,通过业务定制层的接口调用,不跟主流程耦合。
3.3 配置的三个作用域:平台默认、租户级、用户级
业务定制层落地时,有一个绕不开的细节:同一项配置,可能在平台层、租户层、用户层三个级别都有定义,它们之间的优先级怎么处理?
我们在实际项目中采用了“平台默认值-租户级覆盖-用户级覆盖”的三层作用域机制:
- 平台默认值:平台方预置的初始配置,没有其他覆盖时生效。
- 租户级覆盖:某个租户在整体环境内覆盖平台默认值,该租户下所有用户默认看到这套配置。
- 用户级覆盖:某个具体用户针对自己账号做的个性化调整,优先级最高。
实际开发中,配置查询接口会一次性查出三层数据,在内存里做合并运算,而不是逐层查数据库。每一条配置记录都带上“作用域类型+作用域ID”,合并逻辑只判断作用域粒度,粒度越细优先级越高。
这里有个真实事故让我印象深刻:某次发版后,一部分用户发现自己之前调整过的界面配置全部失效了。排查了很久,发现是配置合并逻辑写反了——租户级配置把用户级配置覆盖掉。这种问题在测试环境很难发现,因为测试账号往往就是租户管理员,没人单独配过用户级数据。从那以后,我们把作用域合并逻辑写成了单元测试固定用例,每次发版都要跑一遍,再也没出过类似问题。
3.4 配置单元的模块化设计
业务定制层最忌讳的是做一个“大而全”的配置中心,把所有配置项丢在一个页面上让客户从零开始填。客户根本不知道哪些该改,哪些不该动,配置成本极高,还容易配错。
更好的做法是模块化配置单元:把一项业务能力拆成一组可独立启停的配置单元。比如“订单模块”同时有“订单状态流”“超时自动处理”“金额区间审批”“库存联动规则”四个配置单元,客户需要什么就打开什么,不需要的一律保持默认。这样平台的预置配置就是一套最佳实践,定制客户只是在预置基础上做增量调整,而不是从零打造一套自己的规则。
配置单元的粒度也要拿捏。拆得太细,配置项数量爆炸,维护成本高;拆得太粗,灵活性就没了。我们现在的标准是:一个配置单元对应一个“用户可感知的业务能力”,比如“报价审批”“发货拦截”“回款提醒”,而不是细到“字段A输入校验”这种级别。
4. 第三级:数据与接入层——让定制不触碰平台底线的最后防线
4.1 元数据模型:应对客户的自定义字段需求
平台做久了肯定会遇到这样的情况:客户说“你们的订单表没有我要的‘批次号’字段”。如果你说“加”,就要改表结构,影响全平台的数据模型;如果你说“不加”,客户定制就做不下去了。
早期我们采用预留扩展字段的方式,在核心表里预留几十个字符串字段,不够再加。但用着用着就发现问题:这些扩展字段没有业务语义,开发人员根本不知道第23个扩展字段存的是什么内容,查询统计的时候全靠猜。后来我们才把方案升级为元数据模型。
核心表只存标准字段,另建一张“扩展属性表”,结构大致是:实体ID、属性名、属性值、数据类型。客户要加“批次号”,就往扩展属性表写一条属性名为batchNo、值为具体内容的记录。查询时按需做属性拼接,完全不用改表结构。
这里必须说清楚这个方案的代价:扩展属性的查询性能比标准字段差。我们现在的做法是来一个分流——高频核心查询走标准字段,个性化属性查询走扩展表,如果某个扩展属性因为业务发展变成了高频查询字段,就通过数据加工任务把它物化回标准表。两套机制并行,既保住了灵活性,也没牺牲性能。
4.2 标准API与适配器机制:对接外部系统的正确姿势
接入层是平台和外部世界的边界。客户说“我们有自己的WMS系统,能不能对接”,或者“我们总公司用的是用友U8+,数据得跟它同步”,这些都是接入层要解决的问题。
这一层要做两件事:一是提供稳定统一的标准API,二是提供可插拔的适配器机制。标准API意味着对外暴露的接口格式、参数规范、返回结构、鉴权方式统一。客户只要对接一次,就能完成绝大多数数据交互。而面对不同外部系统的差异,平台侧不能把每家系统的写死逻辑塞进核心代码里,而是为各家系统写一个适配器,注册到适配器注册中心。适配器把外部系统格式翻译成平台内部的标准格式,平台核心逻辑只消费标准数据模型。后续某个适配器出了问题,也只会影响一条接入链路,不会拖垮平台主流程。
比如对接用友U8+的采购订单数据:适配器负责把U8+导出单据的字段映射、格式转换、状态同步逻辑处理好,平台侧不需要知道U8+内部怎么组织那些单据数据。以后客户要换成其他ERP,再写一个适配器就行,平台核心代码不用动。
4.3 接入层的数据安全与权限控制
系统集成的安全性是接入层必须认真对待的一点。不同客户对数据权限的要求不一样:有的客户要求普通员工只能看到自己的订单,有的客户要求管理人员能看到团队全部数据。如果这一层的数据权限没做好,就算界面上限制了用户可见范围,对方直接调API也可能绕过检查,把不该看的数据全捞走。
我们的方案是:在API网关层解析用户身份与角色,将数据范围条件统一拼装进查询上下文,由数据层统一执行过滤。业务层和交互层不需要重复处理权限逻辑。这样不管定制方从哪个入口调用API,数据权限都是一致且强制的,不会出现“页面上看不到某些数据、但通过API能查到”的漏洞。
5. 多级配置叠加后的故障排查:问题藏得比你想象得深
三级架构落地之后,排障工作的重心从“代码bug”变成了“配置问题”。配置分散在交互层、业务定制层、数据接入层,一旦表现不符合预期,很难一眼判断是哪个环节出了问题。这章分享几个高频问题的排查链路。
5.1 配置改了,页面却没变
用户改了一个按钮的文案,刷新页面还是旧文案。遇到这种问题,第一反应通常是缓存。但缓存的具体位置分好几层,得按顺序排查。
我的排查链路是固定的:先查服务端配置缓存有没有刷新,再查前端构建产物是否生效,最后查CDN缓存是否过期。按我的经验,大多数问题的根源在服务端配置缓存——配置虽然写进了数据库,但缓存键对应的版本号没有更新,节点还在用旧数据。
这里分享一个经验:配置后台保存配置后,一定要强制走一遍“配置发布”流程,生成新的版本号并广播失效事件,而不是直接写库就结束。直接写库会导致各节点缓存各自为政,出现“部分节点新配置、部分节点旧配置”的诡异现象。
5.2 作用域覆盖引发的“配置项不见了”
还有一种高频问题:平台默认把所有配置项展示在配置界面,但客户那侧的配置项少了一半。最开始我们以为是代码bug,查了半天发现问题是配置展示逻辑——系统只展示“当前层级有效配置”,没有区分“继承默认值的配置”和“本层级覆盖的配置”。
后来我们把配置界面改成:展示全部配置项,但用不同的状态标签区分来源(默认值/租户级/用户级)。这样操作人员能一眼看到当前生效值来自哪个层级,配置继承关系一目了然。这个改动不大,排障效率提升极明显。
5.3 定制需求开始穿透三层,意味着架构出了问题
最后提一个偏架构层面的判断经验:如果你发现一个定制需求必须同时改动交互层、业务定制层和数据接入层,那说明现有的三级架构可能没覆盖住这个需求。正常情况,一个定制需求应该在某一层内闭环解决,最多涉及相邻层的配置联动。一旦穿透三层,定制就失控了。
正确的做法不是硬把需求塞进某一层,而是停下来思考:是不是存在一个应该独立的第四层被我们忽略了?我实际遇到过一个车载系统定制项目,客户需要对接车辆总线数据,这个数据格式既不属于业务定制层,也不属于标准接入层。最后我们单独建立了“设备接入层”,才把定制边界理清楚。架构分层的本质不是定死三层层级,而是不断根据真实需求校准边界。
6. 三级架构在不同平台类型上的落地对照
说完通用架构,我拿几个具体的平台场景对照一下,方便大家找到自己项目的对应的位置。
6.1 企业ERP平台的定制落地
以用友U8+这类企业ERP平台为例。交互层定制是界面布局和字段显示,业务定制层是单据模板、审批流、角色权限,数据接入层是外部财务系统和供应链系统的对接。
ERP项目最容易踩的坑是拿“改源码”当定制方案。真实企业环境里一套ERP要跑好几年,每次定制都改源码,后面做版本升级的时候基本等于重做。用配置驱动的方式承接定制,前期投入确实比改源码多(要整理配置项、做配置界面、写配置文档),但后期维护成本低得多。我见过太多“改源码一时爽,升级火葬场”的案例了。
6.2 仿真平台和物联网云平台的定制模式
仿真类平台(像Wokwi这类电路仿真工具)和物联网云平台(像OneNET)的三级架构表达形式不太一样,但逻辑完全相通:交互层负责仿真界面和看板展示,业务定制层负责联动规则和消息流转配置,数据接入层负责设备数据与外部API对接。
物联网平台有个特有痛点:设备接入协议五花八门。Modbus、MQTT、TCP自定义协议,如果每种协议都写死在平台代码里,平台早晚被协议拖垮。正确做法就是我前面讲的适配器机制——每种协议封装为一个适配器,接入层注册管理,平台核心逻辑只消费标准设备数据模型。
我在一个项目里接入Modbus协议适配器,全程没动过一行平台核心代码,只是在适配器注册中心加了一条记录。这就是把定制能力收敛在可控空间里最直观的体现。
6.3 跨平台系统的定制取舍
如果开发的是跨平台系统(比如同时覆盖Windows和Linux的桌面应用,或者跨iOS和Android的移动应用),三级架构的复杂程度会再高一个数量级——每一级都要兼顾不同平台的表现差异。
我的建议是把平台差异当作“配置维度”。交互层配置里加一个platform字段,不同平台可以加载不同的组件实现和样式方案;业务定制层保持完全一致,因为业务规则不因终端变化而变化;数据接入层则按照各平台能力差异分别实现适配器。这样既保留业务逻辑复用,也不牺牲多平台的原生体验。
7. 最后分享几条三级架构落地后的实操心得
关于这套架构,我再补充几条在实战中积累的心得。
第一,配置项的文档投入产出比极高。很多平台不是架构设计有问题,而是业务人员完全搞不懂配置项的含义。我见过后台操作的人面对“XX-APL-032”这种配置名一脸茫然——平台再灵活也没人敢用。给每一个配置项准备清晰的含义说明、推荐值、影响范围,投入一周的时间,能省后续几个月的答疑。
第二,定制需求的审批门槛要前置。不是所有定制需求都该接,也不是所有需求都能通过配置解决。项目启动前,把需求的边界分成“平台标准版已支持”“可通过配置实现”“需要开发定制”三档,能避免大量无效沟通,也让客户对定制成本有理性预期。
第三,每一层必须日志留痕。交互层的页面配置变更、业务层的规则调整、接入层的API调用记录,全部要有审计。排查问题时,日志审计的价值远高于代码注释。我曾经靠一条配置变更日志,半小时定位了一个牵涉八个人、拖了两天的疑难问题。
这套三级架构不是银弹,它没法覆盖每一个平台的全部定制需求,也不会让你的后台一夜之间变得完美。但它提供了一个好用的思考框架:当定制需求涌过来的时候,先把变化对应到“表现变化、规则变化、数据变化”三个分类,再判断应该在哪一层解决。分得清,平台才活得久。