这样做的好处很明显:
- 首先,维护一张数据库表肯定比两张的成本要小。
- 其次,其数据的扩展性更好。比如,新需求来了,需要增加一个建议价格(suggest price)区间,如果是两张表的话,我需要在 price_range 中加两个新字段,而如果是 JSON 存储的话,数据模型可以保持不变。
可是,在业务代码里面,如果是基于 JSON 在做事情可不那么美好。我们需要把 JSON 的数据对象,转换成有业务语义的领域对象,这样,我们既可以享受数据模型扩展性带来的便捷性,又不失领域模型对业务语义显性化带来的代码可读性。
错把数据模型当领域模型的确,数据模型最好尽量可扩展,毕竟,改动数据库可是个大工程,不管是加字段、减字段,还是加表、删表,都涉及到不少的工作量。
说到数据模型的扩展设计经典之作,非阿里的业务中台莫属,核心的商品、订单、支付、物流4张表,得益于良好的扩展性设计,就支撑了阿里几十个业务的成千上万的业务场景。
拿商品中台来说,它用一张 auction_extend 垂直表,就解决了所有业务商品数据存储扩展性的需求。理论上来说,这种数据模型可以满足无限的业务扩展。
JSON 字段也好,垂直表也好,虽然可以很好的解决数据存储扩展的问题,但是,我们最好不要把这些扩展(features)当成领域对象来处理,否则,你的代码根本就不是在面向对象编程,而是在面向扩展字段(features)编程,从而犯了把数据模型当领域模型的错误。更好的做法,应该是把数据对象(Data Object)转换成领域对象来处理。
如下所示,这种代码里面到处是 getFeature、addFeature 的写法,是一种典型的把数据模型当领域模型的错误示范。
领域模型和数据模型各司其职上面展示了因为混淆领域模型和数据模型,带来的问题。正确的做法应该是把领域模型、数据模型区别开来,让他们各司其职,从而更合理的架构我们的应用系统。
其中,领域模型是面向领域对象的,要尽量具体,尽量语义明确,显性化的表达业务语义是其首要任务,扩展性是其次。而数据模型是面向数据存储的,要尽量可扩展。
在具体落地的时候,我们可以采用 COLA [1] 的架构思想,使用 gateway 作为数据对象(Data Object)和领域对象(Entity)之间的转义网关,其中,gateway 除了转义的作用,还起到了防腐解耦的作用,解除了业务代码对底层数据(DO、DTO 等)的直接依赖,从而提升系统的可维护性。
此外,教科书上教导我们在做关系数据库设计的时候,要满足 3NF(三范式),然而,在实际工作中,我们经常会因为性能、扩展性的原因故意打破这个原则,比如我们会通过数据冗余提升访问性能,我们会通过元数据、垂直表、扩展字段提升表的扩展性。
业务场景不一样,对数据扩展的诉求也不一样,像 price_rule 这种简单的配置数据扩展,JSON 就能胜任。复杂一点的,像 auction_extend 这种垂直表也是不错的选择。
wait,有同学说,你这样做,数据是可扩展了,可数据查询怎么解决呢?总不能用 join 表,或者用 like 吧。实际上,对一些配置类的数据,或者数据量不大的数据,完全可以 like。然而,对于像阿里商品、交易这样的海量数据,当然不能 like,不过这个问题,很容易通过读写分离,构建 search 的办法解决。