六周重写一套在跑的 ERP:484 次提交、132 万行代码,只借鉴功能
我手上正在跑的一单:把一套用了几年的 ERP 重写。
旧系统我是从里面改起的——二十个独立仓库、约 1.64 万次提交,我个人的 383 次提交分布在 19 个仓里,重心全在平台层(鉴权、网关、通用日志记录、公共库)。所以我很清楚哪一部分值得留、哪一部分不值得留:平台层那套做法我带走,业务实现层一行不搬。
这次的做法是只借鉴功能:旧系统做什么,新系统就做什么;类、方法、包结构、实现全部重新写。
先划一条线:这一单不写客户名、行业、内部地址,规模数字取约数。
一、为什么一行代码都不搬
上一单那套六层里,"调用封装"这一层在多数仓里只有一行继承——它换来的是二十多个仓长得一模一样。这次团队协作规模变了,我把这类只服务一致性的目录收进了包结构。复制代码最省一时,代价是把旧系统的形状一起带进来——那形状里有当时来不及整理的分层、有为了赶上线留下的写法、有一批只有最初那批人写得动的类。
重写的判断标准很简单:这段代码里装的是业务规则,还是当时那个人的习惯? 前者照着做,后者一个字不搬。
六周的量放在这里,因为这件事没有数字就不成立:
| 量 | 数 |
|---|---|
| 提交 | 484 次(八月中旬开工,九月 27 天里交了 300 多次) |
| Java 文件 | 约 12220 个 |
| 代码行 | 约 132 万行 |
| 控制器 | 672 个 |
表映射(@TableName) |
540 张 |
| 生效模块 | 27 个(12 个域各一对 api / service) |
跨服务调用声明(@FeignClient) |
193 处,全部收在服务侧 |
| 架构红线 | 16 条,写进文档并按它评审 |
| 领域关系分析 | 7017 行(含实体关系、边界与服务间调用方向) |
二、跑得快的真正原因:平台层不在这里写
这个仓里几乎没有平台代码——鉴权、权限模型、缓存、消息、数据库访问、Excel、审计日志、国际化、幂等,全都住在我自己的基础工程 aryee-foundation 里,业务仓只是一个依赖坐标。
底座是什么形状,全部可以核对:
| 底座给什么 | 实测 |
|---|---|
| 模块面 | 24 个顶层模块,251 份构建文件,约 26 万行主代码 |
| 测试 | 110 个测试文件、约 35000 行测试代码、3438 个测试方法,集中在公共核心模块,加密那批工具(AES / RSA / HMAC / TOTP / 口令散列 / 转义)逐个带测试 |
| 门禁 | CI 跑 mvn verify -Pcoverage(JaCoCo 覆盖率门禁)+ 静态质量扫描,且等质量门禁结果才继续 |
| 分发 | 发布到 Maven Central,带 GPG 签名 |
"开箱即用"这四个字是可以验的,验法就是看下游到底引了多少。 这一单里:13 个域的 -api 模块直接依赖底座的安全契约,业务代码里出现 1690 处权限注解、分布在 371 个文件,另有 517 处从底座拿当前用户上下文;网关侧 13 个全局过滤器把限流、请求清洗、接口签名这些事在一个地方做完。
这意味着两件事,都是钱:
- 平台能力测一次,不是测十二次。 缓存切换、消息通道、鉴权链路这些行为在底座里已经有测试兜着,业务仓再各写一遍只会各写一遍不同的断言。给框架接线写重复测试,是这类项目里最典型的一种浪费。
- 业务开发者不需要懂权限模型就能写对。 他写的是
@RequiresAbacPermission(resource = ..., action = "access")一行注解,鉴权分支、上下文取法、审计打点都在底座里。八十人协作的系统里,"每个人都能写对"比"少数人写得漂亮"值钱得多。
三、模块切法:从六层收到两层
上一套系统每个服务固定六个模块(契约、调用封装、传输对象、实现、控制器、常量)。这次每个域只有两个:<域>-api 放契约与传输对象,<域>-service 放实现,内部再分控制器、服务、仓储、实体。
六降到二不是把分层扔了,是把只靠目录表达的分层改成靠包结构表达。 那两个只为让二十多个仓保持一致而存在的模块,多数仓里各只有一行代码——模块数不产出约束,它产出的只是文件数;真正管住边界的是每个模块里只允许放哪四类东西。
收敛之后能核对到三件实际效果:
- 新增一个业务域,只建两个模块,接线成本从"复制六个 pom 再改依赖"降到"复制一对";
- 跨服务调用的声明 193 处全部留在服务侧,契约模块里一处都没有——调用方向因此是单向的,不会出现两个域的契约模块互相引用;
- 旧目录结构残留清零,抽查每个域的旧包名路径都是 0 个文件。
边界本身写在 16 条红线里,逐条可评审。红线不是装饰:这次每个域的对外契约都从红线里的调用规则生成,谁越界在评审上一次就能定位。
四、「只借鉴功能」里最难的一条:外部契约算功能
有一批对外接口的路径,是上一家甲方按自己的名字定的。这次重写,这些路径、HTTP 方法、请求体字段名、响应结构、错误码原样保留。
为什么值得为此停下重写——因为前端和第三方系统里那些地址是接死的,动一个 URL 就是动别人的系统。所以这条重写的验收标准第一条就是:现有调用方不改一行代码,能跑通同样的业务流程。
这条判据对甲方的价值很直白:重写这件事对下游系统是不可见的。 没有联调窗口、没有第三方改造排期、不需要一次全员培训。
同样保留的还有领域词汇。这个仓里一百多个 Java 文件带着客户那边一直在用的业务词,一些枚举的类名里就是那个词。这不是没洗干净,是故意的:那个业务对象本身就是那个东西,换成中性词,读代码的人反而要多查一层映射。词汇是功能的一部分,客户每天早上在系统里看到的字段名,就是他们自己的语言。
所以我给"功能"这个词的定义是三格:外部契约、数据所有权、领域词汇。 三格都保留,只有实现全部重写。这么定义之后,重写的风险面一下就清楚了——凡是保留的都有一份回归判据,凡是重造的都有一次变更评审。
五、推进量怎么留证据
重写最怕的是"看起来在做,其实说不清做到哪儿"。这一单每个阶段都留了一个能对外核对的量:
- 端点全量对账:迁移前后 HTTP 端点数 178 → 178,丢失 0、新增 0。入口数量这条线管住了"没搬丢";行为层的等价性用黄金样本比对来管——同一批真实输入,新旧两份输出逐字段比,这一层排在下一批搬迁的最前面(做法在《没有测试的老系统,先让它自己交代行为》里)。
- 大文件在建仓第一天就有清单和顺序:48 个两千行以上的实现类全部登记在拆分计划里,试点挑最狠的那个(单文件 9424 行、控制器 97 个公开方法),拆完编译通过、端点对账不变,这份顺序就成了后面 47 项的排期表。重写项目里的大文件不该是写完之后再治的病。
- 一致性边界随业务长:跨服务写入口上的全局事务标注目前有 146 处,每一处都对应"这个动作的中间态不能被人读到"的一次判定。
- 文档先行:架构总说明、16 条红线、迁移计划、领域重构计划、领域关系分析、网关与鉴权与权限分析、开发规范共十二份,先有边界再落代码。同一套文档我给下一个项目的交付物清单也会照搬——文档是这个项目的产出之一,不是过程消耗。
一句实话放在这儿比藏在注释里好:这套做法的产出量不来自加班,来自该写的代码少了一半——平台层不用写,契约不用重新设计,词汇不用重新解释。
六、四条你可以直接抄走的
- 重写先分"规则"和"习惯"。 旧代码里装业务规则的照着做,装当时那个人写法的重写掉。不做这个区分,重写就是一次昂贵的复制。
- 平台层沉到独立底座,并且把它当成产品维护。 覆盖率门禁、静态质量扫描、发到公共仓库去——只有底座是被独立验证过的,业务仓才敢不重复写那些代码。
- 把"功能"定义成外部契约 + 数据所有权 + 领域词汇三格,开工第一天逐格签字。前两格一般会被想到,第三格一定被漏掉,然后以"零历史包袱"的名义悄悄背回来。
- 每个阶段留一个可对外核对的量。 端点对账、契约模块里的越界引用数、旧目录残留文件数——这三个数不需要测试框架就能算,但它们让"进度"这个词第一次有了可验证的形态。
收口
六周这个点上,这个仓有 27 个生效模块、约 132 万行新写代码、16 条红线、12 份设计文档、178 个端点对齐、1690 处权限注解,业务代码里零行鉴权实现——权限是注解,上下文是从底座取的。
这套做法我已经用在上一单的架构上(《新系统从 0 到 1 怎么搭:一套上线几年的微服务架构,逐层拆开》),现在正在把它用在重写这一单上。方法论是同一套,只是这次先量的是我自己。
如果你手上也有一套"还在赚钱但没人敢动"的系统,可以带着它的模块清单来聊一次:我给它一份保留 / 重写的分档判断,和一条按域的推进顺序。
本文作者:Aryee 发布时间:2026-09-27 11:35
本文出处:ARYEE.cn 固定链接:https://www.aryee.cn/archives/six-weeks-erp-rewrite-on-a-foundation.html
转载或引用请连这一行一起带走;文中的代码与结论按当时环境成立,组件升级后请以官方文档为准。