问题域
企业软件的特征,与其说在于功能,不如说在于它所处的环境。一个新应用进入的系统环境里,已经有 ERP、CRM、人力资源系统、数据仓库、几个没人完全弄清楚的数据库,以及一批替这些系统补位的电子表格。它的价值取决于它与所有这些系统交换数据的质量,以及它能否清楚界定哪个系统拥有哪一项事实。
反复出现的问题是集成与一致性。同一张订单在 CRM、ERP 和仓储系统中有三种不同的状态。一条客户记录在两处被修改过。夜间批处理任务静默失败,财务团队到月末才发现。如果不解决数据归属和数据流向的问题,再增加一个应用只会让情况更糟,无论这个应用本身做得多好。
现代化改造让风险更高。遗留系统往往固化了几十年的业务规则,而这些规则在别处无处可寻;在整个行业里,试图在一个项目中整体替换它们的做法成功率并不高。我们倾向于增量式变更:在遗留系统前面放置一个稳定的接口,每次把一项能力迁移到接口之后,新旧并行运行直到结果一致,然后下线旧路径。
难在哪里
工程挑战
点对点集成蔓延
每个新系统都通过定制脚本、文件投递和数据库链接直接连到另外几个系统。几年之后,没有人说得清改动一个字段会导致什么出问题。这些集成成了整个系统环境中最脆弱、文档最少的部分。
共享数据没有唯一的所有者
当多个系统都能修改同一个客户、产品或价格时,数据就会出现分歧。报表因数据来源不同而互相矛盾,员工要花时间手工对账。根本原因在于,没有为每个实体明确指定权威的记录系统。
跨系统的更新
审批采购订单这样的业务操作,可能需要同时写入工作流系统、ERP 和通知服务,而它们之间并不存在分布式事务。如果第一次写入成功、第二次写入失败,组织就会处于一种任何系统都不认可的状态。
无法暂停的遗留系统
被替换的系统仍在支撑业务运转。它有未成文的行为、批处理窗口,还有直接读取其数据表的下游系统。假设可以干净切换的割接方案,往往要到生产环境才发现这些依赖。
超出角色模型的授权需求
访问规则一开始只是几个角色,后来变成由角色、部门、区域、审批限额和授权委托构成的矩阵。它们散落在应用代码中,根本无法评审。审计人员问起某一天谁可以审批某笔交易时,没有人能有把握地回答。
事后补上的审计轨迹
一个“修改人”字段加一个“更新时间”时间戳,并不能说明之前的值是什么、谁批准了这次修改,或者是哪个上游事件引发的。从应用日志中还原历史并不可靠。可审计性必须是数据模型自身的属性。
我们如何构建
架构
应用由一组与业务领域对齐的服务或模块构成,通过集成层与更大的系统环境相连;集成层把每个外部系统隔离在一份稳定的契约之后。
- 01
领域服务
采购、审批、案件管理等业务能力被实现为边界清晰、拥有各自数据的模块。它们是部署为一个模块化应用还是多个独立服务,取决于团队结构和扩展需求。边界比部署单元更重要,因为它决定了哪些部分可以独立变化。
- 02
集成层与防腐层适配器
每个外部系统都由一个适配器包裹,在该系统的模型与应用自身的模型之间做转换。ERP 的特殊行为、遗留字段格式和厂商 API 的变化,都被限制在这个适配器之内。同步调用经过带超时和熔断的 API 网关;数据传播使用事件或变更数据捕获,下游系统变慢也不会阻塞用户。
- 03
工作流引擎
带有审批、升级和定时器的长时间运行流程,在工作流引擎中显式建模,而不是分散在状态字段和定时任务里。引擎持久化每个实例的状态,流程因此能够经受重启,当前所处的步骤也始终可见。流程定义有版本管理,执行中的实例按其启动时的版本运行完成。
- 04
通过 Outbox 与 Saga 保证一致性
服务使用事务性发件箱(transactional outbox)模式,在同一个本地事务中写入状态变更和对应的事件,再由中继可靠地发布事件。跨多个系统的操作以 Saga 方式协调,每一步都定义了补偿动作。消费者是幂等的,重复投递因此是安全的。对账任务定期比对各系统,并报告出现的任何偏差。
- 05
身份与授权
用户通过企业身份提供方以单点登录方式认证。授权把角色与组织单元、审批限额等属性结合起来,在集中的策略组件中求值,规则以可评审的配置形式保存。职责分离约束也在这里执行,例如申请人不能审批自己的申请。
- 06
审计与历史存储
每一次状态变更都记录为一条只追加的条目,包含操作者、时间、变更前后的值,以及引发该变更的请求或事件的关联标识。这条记录与变更本身在同一个事务中写入,因此无法被跳过。它既支持审计查询,也使得还原某个实体在任意日期的状态成为可能。
- 07
用于现代化改造的绞杀者门面
路由层位于遗留系统之前,把每个功能导向旧实现或其替代实现。能力逐项迁移,新旧实现并行运行,比对输出之后再切换流量。随着功能逐步下线,遗留系统不断收缩,而且在每个阶段都有一条可用的回退路径。
客户端
- Web 应用
- 移动应用
- 合作方集成
API 网关 / BFF
- 路由
- 身份认证
- 限流
- 响应裁剪
领域服务
每个服务负责一个限界上下文
- 订单
- 支付
- 商品目录
- 账户
- 通知
异步消息
- Kafka 主题
- Outbox 发布器
- 事件消费者
- 死信队列
数据存储
每个服务独立数据库,不共享表
- 各服务独立 PostgreSQL
- Redis 缓存
- 搜索索引
- 对象存储
平台
- 链路追踪与指标
- 集中式日志
- CI/CD 流水线
- 服务配置
一次合作交付什么
- 领域模型,以及共享数据的记录系统归属图
- 带有文档化 API 与事件契约的应用服务
- 面向 ERP、CRM 与遗留系统的集成适配器
- 包含审批、升级与授权委托规则的工作流定义
- 基于角色与属性的访问模型,附审计历史
- 系统间的对账任务与偏差报告
- 包含并行运行与回滚步骤的增量迁移计划
开始之前
需要尽早做出的决策
- 配置套装产品,还是自建
- 当流程是标准化的,并且您愿意采用产品自带的工作方式时,使用套装产品;财务、薪资和核心人力资源是常见的例子。当流程是差异化的来源,或者定制套装产品意味着每次升级都要与它对抗时,选择自建。对套装系统进行重度定制,往往要付出自建的成本,却得不到自建的掌控力。
- 系统间使用同步 API,还是事件
- 当调用方需要得到答复才能继续时,使用同步调用,例如下单过程中的信用检查。当其他系统只需要知道某件事已经发生时,使用事件。事件降低耦合、吸收故障,代价是最终一致性,用户界面和报表在设计时必须能够容忍这一点。
- 替换遗留系统,还是包裹它
- 当系统稳定、逻辑可靠,主要问题只是难以访问其数据和功能时,包裹它。当它阻碍变更、运行在已失去支持的技术上,或者依赖正在从组织中流失的技能时,增量替换它。只有当系统小到可以穷尽验证时,一次性整体重写才说得过去。
- 一致性需要多严格
- 按操作逐一决定。财务过账和库存扣减一般需要在发生的那一刻就是正确的,应当放在同一个事务边界内。搜索索引、仪表盘和通知可以滞后几秒。把这些要求明确写出来,既能避免过度设计,也能避免悄无声息的不一致。
常见问题
常见问题
你们能与我们的 ERP 或 CRM 集成吗?
集成取决于该系统提供哪些接口:REST 或 SOAP API、消息队列、文件交换,或数据库层面的变更数据捕获。我们先评估这些接口,再围绕其中受支持且稳定的那一种构建适配器。提及厂商名称仅为描述之用;我们不声称与任何厂商存在合作关系或获得其认证。
现代化改造期间如何保证业务不中断?
每次把一项能力迁移到路由层之后,让新实现与旧实现并行运行,比对结果后再切换流量。每一步都有明确的回滚方案。凡是尚未迁移的部分,仍以遗留系统为准。
你们使用低代码平台吗?
在合适的场景下使用。低代码工具对集成需求不高的简单表单和部门级工作流很有效。当逻辑复杂、数据量大,或者变更需要规范的版本控制、测试和评审时,它们就会成为限制。我们依据流程本身给出建议,而不是出于对某一种方式的偏好。
系统上线后由谁运维?
这一点在项目开始时就约定。系统交付时附带基础设施即代码、监控、运维手册和文档,您的团队可以自行运维。Starone 也可以另行约定,提供持续的工程与运维支持。
