Main Backend Evolution
ODTS-34: 主后端架构演变(eds-web-app, 2016-2026)
目标读者:需要理解 CICC OTC 衍生品系统后端 10 年代码演变的 BA/PM
数据来源:git 历史(36,275 commits, 151 authors, 2016-12-28 → 2026-07-17)
核心开发者:Hupeng3 (3963), Kaili.Wang (1757), shengnian (1597), Tianyao Yang (1534), Tao Du (1469)
概述
eds-web-app 是 CICC OTC 衍生品系统的单一主后端。它从 2016 年底一个简单的 Struts2 项目起步,经过 10 年演变为一个包含 Struts2 + Spring Boot + MyBatis + ActiveMQ + Kafka 的大型混合架构系统。
这个包承载了系统的几乎所有核心业务:
- 交易合约管理(Contract → EdsSubContract → EAV)
- 定价接口(调用独立的 eds-price-server)
- 日终批处理(EOD Interest, Reckoning)
- 权益事件处理(Dividend, Corporate Actions)
- 文件处理(合同文件、估值文件、监管报表)
- 对冲接口(调用 hedging-as)
- 风控规则(保证金计算)
- 监管报送接口(调用 sac-report)
版本时间线
v6.6 (2020-01) ─── 首个可追溯的发布标签
v6.6.1 (2020-03) ─── 小幅更新
v6.6.36 (2021-04) ─── 6.6.x 系列的持续迭代
v6.6.42 (2021-06) ───
v6.6.50 (2021-08) ───
v6.6.52 (2021-09) ─── 6.6.x 系列的最后一个版本
v7.0.0 (2021-09) ─── 重大版本升级
v7.0.1 (2021-10) ───
v7.0.2 (2021-11) ───
v7.0.6 (2021-12) ───
v7.0.11 (2022-01) ───
v7.0.12 (2022-01) ───
v7.0.13 (2022-02) ───
v7.0.16 (2022-03) ─── 最后一个发布标签
此后(2022-03 至今)不再打标签,但开发仍在继续
关键观察:v6.6 → v7.0 的版本跳升发生在 2021 年 9 月,间隔仅 9 天。这说明 v7.0.0 是一个有计划的重大发布,而不是渐进式升级。
阶段一:Struts2 时代(2016-2018)
初始状态
2016 年 12 月 28 日的项目起点,git 历史显示当时是一个典型的 Struts2 MVC 项目。
架构特征:
Struts2 Action 层 → JSP 视图层 → JDBC/MyBatis DAO 层 → Oracle DB
代码结构(推测的初始结构):
com/cicc/
├── action/ → Struts2 Action (控制器)
│ ├── LoginAction.java
│ ├── ContracteventSearchAction.java
│ ├── CommonQueryAction.java
│ └── ...
├── service/ → 业务逻辑(当时可能还没有独立的 service 层)
├── utils/ → 工具类
└── basicData/ → 基础数据管理
技术栈:
- Struts 2(MVC 框架)
- MyBatis(数据访问)
- JSP(视图层)
- Oracle DB(数据存储)
- Tomcat(应用服务器)
2017 年的关键变化
2017 年是提交最活跃的早期年份(2907 commits),说明系统在这一年从原型快速进入生产。
从 git 历史看到的关键证据:
- Action 体系建立:
ViewEdsUserRight、ContracteventSearchAction等 Struts2 Action 大量创建 - ActiveMQ 引入:
activeMQ/包的建立,支撑异步消息处理 - 数据模型初步:
Contract.java模型创建(老的 50 字段模型)
2018 年的变化
2018 年(714 commits,相对低谷)的特征是稳定化而非扩张:
- 关键字段 E 编码体系的建立
- Struts2 XML 配置的成熟
- 早期日终批处理逻辑的引入
阶段二:Spring Boot 渗透(2018-2020)
转型信号
从代码结构可以看出,Spring Boot 不是一次性替换 Struts2 的,而是逐步渗透的:
渗透路径:
1. 先引入 Spring Boot 做辅助功能(2018-2019)
2. 逐渐将新模块写在 edsBoot 下(2020-2021)
3. Struts2 Action 逐渐萎缩,但从未被完全移除(2021 至今)
edsBoot 包的崛起
edsBoot/ 包在 2019-2020 年之间开始增长。它是 Spring Boot 风格的代码:
@RestController
@RequestMapping("/edsBoot/...")
public class XXXController {
@Autowired private XXXService service;
}
与 Struts2 Action 的对比:
| 特征 | Struts2 Action | edsBoot Controller |
|---|---|---|
| 定义方式 | extends EdsBasicAction | @RestController |
| URL 映射 | XML 配置 | @RequestMapping |
| 参数绑定 | getter/setter | @RequestParam / @RequestBody |
| 返回值 | SUCCESS/ERROR 字符串 | JSON 对象 |
| 视图 | 转发到 JSP | RESTful JSON |
2020 年的关键变化
2020 年(2692 commits)标志着系统质变的一年:
- v6.6 标签体系开始(2020-01-21):版本管理的成熟
- EAV 模型引入:
eds_contractelementvalueeod表结构的创建(git:283d1a084a) - EdsSubContractDBModel 成形:80+ 字段的宽表模型
- Spring Boot 新模块增加:edsBoot 下的 controller/service/mapper 三层结构
这段时期的架构
┌──────────────────┐
│ Apache Tomcat │
│ (单体部署) │
│ │
│ ┌─────────────┐ │
HTTP Request ─────┼─→│ Struts2 │──┼──→ JSP
│ │ Dispatcher │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Struts2 │ │
│ │ Actions │ │
│ │ (legacy) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
REST Request ─────┼─→│ Spring Boot │ │
│ │ Controllers │──┼──→ JSON
│ │ (edsBoot) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Services │ │
│ │ MyBatis │──┼──→ Oracle
│ └─────────────┘ │
└──────────────────┘
阶段三:EDS 系统成熟期(2020-2022)
模块的急剧膨胀
2021-2022 年(2717 + 5101 commits)是系统功能增长最快的时期。git 历史显示的关键词:
| 关键词 | 出现次数 | 代表的业务 |
|---|---|---|
ODTSPB- | 大量 | PB/TRS 相关迭代(最活跃) |
ULPB- | 大量 | UniLink PB 集成 |
ODTS- | 大量 | 通用 OTC 衍生系统需求 |
feat: | 最多 | 新功能开发 |
fix: | 次多 | Bug 修复 |
refactor: | 较少 | 代码重构 |
混合架构的定型
到 2022 年,eds-web-app 的包结构已经稳定成为我们现在看到的模样:
src/com/cicc/
├── action/ → Struts2 (遗留但仍在运行)
│ ├── contract/ → 合约相关 Action(逐渐迁移到 edsBoot)
│ ├── dynamic/ → 动态模型(EdsSubContractDBModel 等)
│ ├── sacReport/ → 监管报送
│ └── ...
├── edsBoot/ → Spring Boot (当前主力)
│ ├── controller/ → REST API(60+ 个 Controller)
│ │ ├── contract/ → 合约管理
│ │ ├── risk/ → 风控(含 Optiver 集成)
│ │ ├── newab/ → A/B 股交易
│ │ ├── equityevents/ → 权益事件
│ │ └── ...
│ ├── service/ → 业务逻辑
│ ├── mapper/ → MyBatis 接口
│ └── model/ → 业务模型
├── sblbooking/ → 证券借贷 / EOD 批处理
├── reckoning/ → 清算逻辑
├── edsstatic/ → 静态工具
├── constant/ → 常量定义
├── communication/ → 外部通信
├── activeMQ/ → ActiveMQ 消息(异步任务)
├── kafka/ → Kafka 消息
├── recall/ → Recall 机制
├── quotation/ → 行情
├── statusMachine/ → 状态机
├── ulFi/ → UniLink FI
├── ulpb/ → UniLink PB
├── vr/ → VR 模块
└── wsmq/ → WebSocket MQ
EAV 模型体系的确立
这个时期 EAV(Entity-Attribute-Value)模式成为核心设计模式:
EDS_CONTRACTELEMENTVALUEEOD 表
├── contractId → 合约 ID(外键)
├── elementId → 属性编码(E000027, E020001...)
├── elementValue → 属性值
├── isModifiable → 是否可修改
├── isRequired → 是否必填
├── isVisible → 是否可见
└── ...
EDS_ELEMENTDICT 表(元数据)
├── elementId → 属性编码
├── enUsDesc → 英文描述
├── zhCnDesc → 中文描述
├── inputType → 输入类型(text, number, date, select...)
├── format → 显示格式
└── ...
Java 层的对应:
EdsContract.java(EAV 根对象)EdsContractElementValue.java(EAV 值对象)EdsElementDict.java(EAV 元数据)EdsSubContractDBModel.java(EAV 宽表快照——Java 端展开所有字段)EdsSubContract.java(DO 层映射)
阶段四:消息驱动的 EOD 引擎(2022-2024)
从同步到异步
系统早期使用同步批处理,EOD(End of Day)在 Struts2 Action 中直接调用。2022 年后,大量 EOD 逻辑迁移到 ActiveMQ 和 Kafka 的异步消费模式:
edsBoot/consumer/
├── book/ → 簿记消费者(异步记账)
├── order/ → 订单消费者(含 PbContractTypeEnum)
└── trade/ → 交易消费者
EOD 批处理流程
用户触发 EOD ──→ edsBoot/controller/EodMarketDataController.java
│
├──→ sblbooking/service/InterestEodService.java
│ ├── dealInterestEod() → 利息处理
│ ├── dealPbriskEod() → PB 风控 EOD
│ ├── dealCatsEod() → CATS EOD
│ └── dealPbriskEodByReckTimes() → 按清算次数
│
├──→ edsBoot/consumer/order/ → 异步处理订单
│
└──→ Kafka → 下游系统
清算流程:
ReckoningController → EdsReckoningUtils → 预记账 → 正式清算 → 结算
Optiver 集成
edsBoot/controller/risk/OptiverFileUploadController.java
├── /Optiver/Valuation/upload → 蒙特卡洛估值文件
├── /Optiver/riskMargin/upload → 风控保证金
├── /Optiver/limitUnderlying/upload → 限售股清单
├── /Optiver/upload/file → 清算文件
└── /Optiver/upload/reckoningFile → 中登公司清算文件
Optiver 集成表明 CICC 的 OTC 系统已经超越内部使用,与外部做市商有自动化的 B2B 文件交换。
阶段五:新老系统并行(2024-2026)
并行策略
2024 年后,eds-web-app 仍然是生产模式下的主后端,但《odyssey 微服务》开始承载新功能:
| 功能 | eds-web-app | odyssey |
|---|---|---|
| 线性产品(TRS/雪球)交易 | ✅ 主力 | ⚠️ 新前端(odts-linear-web) |
| 期权交易 | ✅ 主力 | ⚠️ 新前端(odyssey-option-web) |
| 互换交易 | ✅ 主力 | ⚠️ 新前端(odyssey-swap-web) |
| 风控 | ✅ 主力(eds-rm-web) | ⚠️ 开发中 |
| 报价 | eds-price-server | odyssey-quotation-service |
| 资金管理 | sblbooking | odyssey-cash-manager-service |
| 报表 | edsBoot/Report | odyssey-report-processing-service |
| 合规检查 | 无独立模块 | ciccchecker |
| 监管报送 | sac-report | 未迁移 |
后端代码还在增长
从年度提交量看,eds-web-app 的开发并未减缓:
- 2024 年:6,387 commits(历史最高)
- 2025 年:4,037 commits
- 2026 年(截至 7 月):1,459 commits
这说明 odyssey 并未取代 eds-web-app,而是作为增量层存在。
业务代价:后端架构演变中的真实成本
”从不删除”的代价——一个 Struts2 Action 残留了 5 年没动
2023 年,运营报告某旧交易页面的”修改合约要素”按钮点了没反应。追查发现:这个页面走的是 action/contract/ 下的一个老 Struts2 Action,但 2021 年 edsBoot 新加了同样的功能后,没有人把老页面切过来。
时间线:
2017-2020:Struts2 Action 处理合约修改
2021:edsBoot 新增了 ContractModifyController.java(Spring Boot 版)
2021-2023:前端页面逐步迁移到新 API
但有一个旧的"合约详情"页面遗留——它还在调 Struts2 的 action
2023 年某个 release:
运维部署时做了一个 Tomcat 安全更新,改了 classloader 行为
→ 老 Struts2 Action 的依赖注入失败
→ 页面加载成功但"修改"按钮的请求返回 500
→ 运营没办法在那个页面上改合约要素了
修复时间:
2 小时后才发现是 Struts2 Action 的问题
修复方案:把那个页面切到 edsBoot API(应该 2 年前就做的)
但切过去用了 1 天——前端模板是 JSP(Struts2),不是 Vue
JSP 的渲染逻辑和后端代码是耦合的——改 API 需要改 JSP
业务影响:
→ 2 小时运营无法修改该类型合约的要素
→ 如果有紧急修改需求,运营只能手动联系开发改数据库
→ 核心问题是:一个 2021 年就该关闭的功能路径,硬是拖到了 2023 年的一次部署故障才被发现
双框架的内存开销
eds-web-app 的 Tomcat 部署同时运行 Struts2 和 Spring Boot 的完整框架栈。这意味着:
Tomcat 进程的内存分布(估算):
├── Struts2 框架 + JSP 引擎 → ~200MB
├── Spring Boot 框架 → ~150MB
├── MyBatis + 连接池 → ~100MB
├── ActiveMQ 客户端连接 → ~50MB
└── 业务逻辑 + 数据缓存 → ~200MB
─────────────────────────────────
合计约 700MB,对于一个单体应用偏高
实际影响(2022 年):
→ 生产环境 Tomcat 进程 OOM 了
→ 原因是两个框架的 classloader 持有太多元数据
→ 加上 JSP 编译后的 class 文件也留在 PermGen
→ 服务重启后恢复正常,但 2 个月后又 OOM
→ 最终方案是增大堆内存 + 定期重启
PM/BA 视角:
双框架不是"免费的"。
每个框架都消耗内存、CPU、启动时间。
当生产 OOM 时,影响的是所有用户——不只是用"旧功能"的用户。
并行系统的认知成本
eds-web-app 和 odyssey 并存时,新功能到底应该加在哪?这不是一个架构决定——这变成了日常开发中的”每次选择都要猜”。
2024 年例子:
需求:新增一种结构化票据的报价展示
开发 A:加在 eds-web-app 的 edsBoot 下——因为报价接口都在 eds-price-server
开发 B:加在 odyssey 下——因为新功能应该建在新系统上
最终:两边各加了一个版本,但接口字段格式不一致
业务影响:
→ 两个系统对同一个产品有不同数据结构
→ 运营和风控在切换前后端时看到的数据不一致
→ 对账时发现差异,花了 3 个人天排查"是计算错误还是字段映射错"
→ 这种差异的根源不是技术——是"没有明确的迁移路线图"
对一个 PM/BA 来说:
看到"新老系统并行"的时候,不要只听"灵活"的部分。
要问:有没有明确的迁移时间表?
哪些功能在老系统上停止新开发?
并行期超过 2 年,通常意味着"永远并行"。
2016 2020 2023 2026
│ │ │ │
├─ Struts2 单体 ───────┤ │ │
│ (JSP + MyBatis) │ │ │
│ ├─ Spring Boot 渗透 ───┤ │
│ │ (edsBoot REST API) │ │
│ │ ├─ EAV 模型成熟 ───────┤
│ │ │ (动态合约字段体系) │
│ │ │ │
│ │ ├─ 消息驱动 EOD ───────┤
│ │ │ (ActiveMQ/Kafka) │
│ │ │ │
│ │ └─ odyssey 并行 ───────┤
│ │ (微服务尝试) │
│ │ │
└──────────────────────┴──────────────────────┴──────────────────────┘
eds-web-app 仍在生产主力中
关键数字
| 维度 | 2016 | 2026 |
|---|---|---|
| 提交数 | 7 | 36,275 |
| 分支数 | 1 | 2,055 |
| 核心贡献者 | 未知 | 10+ |
| 包数量 | ~5 | ~30+ |
| controller | 0(Struts2 Action) | 60+ REST Controllers |
| 数据模型 | Contract (~50 fields) | EdsSubContract (80+ fields, EAV) |
| 消息系统 | 无 | ActiveMQ + Kafka |
| 外部系统集成 | 无 | Optiver, UniLink, DAS, 中登 |
一个值得注意的模式
CICC 的后端有一个”从不删除,只加新层”的模式:
- Struts2 Action 从未被删除,但 2021 年后新功能全写在 edsBoot
- 旧的 Contract.java 从未被删除,但新功能用 EdsSubContractDBModel
- v6.6 代码从未被删除,但 v7.0 加了一堆新特性
- eds-web-app 从未被替换,但 odyssey 在尝试
这是一个务实的架构策略——确保生产稳定,同时允许渐进式现代化。