服务契约中定义可存储完整JSON模型的字符串属性有哪些弊端
这种在订单表直接存无约束JSON透传产品模型的方案,属于典型的「省一周事,擦一年屁股」的短平快烂方案,除了你提到的把契约强约束下沉到运行时、缺陷只能在线上暴露的问题外,还有几个绕不开的致命弊端:
核心风险点
1. 数据一致性完全无兜底
- 订单的生命周期远长于产品模型的迭代周期:现在存的JSON是当前版本的产品结构,等过几个月产品侧删字段、改枚举、调整字段类型的时候,历史订单里的存量JSON不会做任何同步更新。轻则反序列化直接报错导致老订单打不开,重则反序列化不报错但值逻辑错误——比如原来产品价格分
originalPrice和discountPrice,后来迭代改成直接存actualPaid,老订单读的时候错把原价当实付,直接触发资损。 - 数据库层完全没有校验能力:JSON字段里存什么、格式对不对,数据库不会做任何拦截,只要某段代码漏了校验写进脏数据,后续排查根本没法快速定位问题来源,也没法像结构化字段那样通过非空约束、枚举约束、外键约束提前把错误挡在写入前。
2. 查询、运维成本会指数级上涨
- 所有涉及产品属性的订单查询、统计都会变得极难实现:比如要统计「近30天某品类商品的订单总销售额」,结构化字段直接关联查询就能出结果,用JSON的话要写数据库JSON解析函数扫全表,数据量上来之后查询性能直接崩盘。你也不可能给JSON里每一个可能变动的字段都建索引,毕竟产品模型本身就在频繁迭代。
- 数据修复成本极高:如果发现某一批订单的产品配置存错了,结构化字段写一条批量UPDATE语句就能修复,JSON字段你得写逐行解析的脚本,还要兼容所有历史版本的JSON结构,修一次数据的工作量够你做半轮服务重构。
3. 彻底打破DDD的边界上下文隔离
- 你们拆订单、产品两个独立服务,核心目的就是让两个边界上下文解耦,现在把产品侧的完整模型直接以JSON形式塞到订单库,本质是让订单上下文直接耦合了产品上下文的内部实现,耦合度比直接同步调用产品接口还高——接口好歹还有版本兼容规则,JSON是连产品侧改个字段名、调整下字段层级,你这边所有解析逻辑都要跟着改,之前服务拆分的收益直接清零。
- 订单域模型会快速腐化:OrderLine作为订单域的核心实体,现在塞了一个没有任何领域行为、完全是外部模型透传的黑盒字段,后续所有和产品相关的逻辑都会慢慢堆到JSON解析的分支判断里,最后变成没人敢碰的大泥球,和你们想解决的「产品迭代快、订单要频繁适配」的初衷完全相反,只会越迭代越乱。
4. 测试、排障成本陡增
- 测试根本覆盖不全所有场景:因为JSON没有强schema约束,你永远预判不到线上会出现什么结构的JSON数据,每次产品模型迭代都要补一堆兼容老版本数据的分支逻辑,漏一个分支就是线上故障。
- 问题排查效率极低:之前查订单问题看结构化字段一眼就能定位值对不对,现在要把JSON扒出来,对应不同历史版本的产品模型逐字段核对,排查一个数据问题的时间至少是结构化存储的3-5倍,大促等高压场景出故障根本来不及恢复。
更合理的落地方向
不要为了赶进度拿长期可维护性换短期上线速度,现在省的一周工作量,后面至少要花10倍的成本填坑。
- 正确的快照实现思路是:在订单域内定义完全归属于订单上下文的产品快照模型,只存订单计价、履约、统计必须的稳定字段(比如产品ID、下单时的商品名、实付单价、品类、核心履约属性),这些字段一旦订单生成就不会随产品服务的迭代变更。从产品服务同步数据时直接做字段映射转换,把强契约约束留在订单域自己的模型层,既不用跟着产品服务的每次变更改逻辑,也能保证数据一致性。
- 如果确实有不确定的动态扩展属性,可以单独建一张订单扩展属性表,用结构化的键值对存储,同时对属性key做统一的枚举约束,不要直接存无schema的大JSON。
内容的提问来源于stack exchange,提问作者doubleotwo
相关产品推荐
相关产品推荐

