er图的设计步骤,er图的设计原则

  

  依稀记得第一次设计系统的时候,画了一堆UML图,面对类图(其实就是域模型),纠结了很久。我不知道如何着陆。因为,如果按照这个类图去数据库,看起来很奇怪,有点繁琐。但是,如果你不按照这种画图,你就不知道这种画图有什么用。   

  

  现在回想起来,我当时的挣扎源于我最近对领域模型和数据模型这两个重要概念的不清楚.的感受,我发现混淆这两个概念并不是个案,而是非常普遍的现象。其结果就是,小到会影响一些模块设计的不合理性,大到会影响像业务中台这样重大技术决策,因为如果底层的逻辑、概念和理论基础都不清楚,建立在其上的体系也会有问题,非常严重的问题。   

  

  鉴于很少有人对这个话题进行过深入的研究和探讨,我觉得有必要花时间仔细理清这两个概念,以帮助大家在工作中做出更好的设计决策。   

  

  领域模型和数据模型的概念定义领域模型以领域知识为核心,是业务领域的核心实体,体现了问题领域中的关键概念以及概念之间的关系。领域模型建模的关键是看模型能否显性化、清晰的表达业务语义,扩展性是其次。   

  

  数据模型侧重于数据存储。所有的企业都离不开数据,他们也离不开数据,数据模型建模的决策因素主要是扩展性、性能等非功能属性,无需过分考虑业务语义的表征能力.   

  

  按照Robert在《整洁架构》中的观点,领域模型是核心,数据模型是技术细节。然而现实是两者都很重要。   

  

  这两种模型之所以容易混淆,是因为它们都强调实体和关系,而事实并非如此。我们传统的数据库数据模型建模是基于ER图的。   

  

  是的,他们确实有共同点。有时候,领域模型和数据模型看起来很像,甚至是趋同的,这很正常。但更多时候,两者是有区别的。正确的做法应该是有意识把这两个模型区别开来,分别设计,因为他们建模的目标会有所不同.如下图所示,数据模型负责的是数据存储,其要义是扩展性、灵活性、性能。而领域模型负责业务逻辑的实现,其要义是业务语义显性化的表达,以及充分利用OO的特性增加代码的业务表征能力.   

  

     

  

  然而现实情况是,我们的很多业务系统设计并没有区分它们之间的关系。总是有两个错误,一个是把领域模型当数据模型,另一个是把数据模型当领域模型.   

  

  把错误的领域模型当做数据模型。这几天在做一个报价优化的项目,涉及到报价规则的问题。这一块的业务逻辑是说,对于不同的商品(以品类、品牌、供应商类型等维度区分),我们会给出不同的价格区间,然后判断一个商家的报价应该是自动审批还是自动屏蔽。   

  

  对于这个规则,域模型非常简单,就是提供价格控制所需的配置数据,如下图所示:   

  

     

  

  如果我们根据这个域模型设计我们的存储,我们自然需要两个表:pricerule和pricerange,一个用于存储价格规则,另一个用于存储价格范围。   

  

     

  

  如果我们以这种方式设计数据模型,我们就犯了把领域模型当作数据模型的错误。在这里,比较合适的方式是一个表就够了,pricerange作为字段存储在pricerule带字段中,如下图所示。最好使用json字段来访问其中的多个价格范围信息。   

  

     

  

  这样做的好处是显而易见的:   

  

  首先,维护一个数据库表的成本肯定比两个少。其次,它的数据扩展性更好。例如,当一个新的需求到来时,就需要添加一个建议价格范围。如果是两个表,我需要在price_range中增加两个新字段,而如果是json存储,数据模型可以保持不变。但是,在业务代码中,基于json做事情就不那么好了。我们需要将json数据对象转换成具有业务语义的域对象,所以,我们既可以享受数据模型扩展性带来的便捷性,又不失领域模型对业务语义显性化带来的代码可读性.   

  

     

  

  将数据模型误认为领域模型   

确,数据模型最好尽量可扩展,毕竟,改动数据库可是个大工程,不管是加字段、减字段,还是加表、删表,都涉及到不少的工作量。

  

说到数据模型的扩展设计经典之作,非阿里的业务中台莫属,核心的商品、订单、支付、物流4张表,得益于良好的扩展性设计,就支撑了阿里几十个业务的成千上万的业务场景。

  

拿商品中台来说,它用一张auction_extend垂直表,就解决了所有业务商品数据存储扩展性的需求。理论上来说,这种数据模型可以满足无限的业务扩展。

  

JSON字段也好,垂直表也好,虽然可以很好的解决数据存储扩展的问题。但是,我们最好不要把这些扩展(features)当成领域对象来处理,否则,你的代码根本就不是在面向对象编程,而是在面向扩展字段(features)编程,从而犯了把数据模型当领域模型的错误。更好的做法,应该是把数据对象(Data Object)转换成领域对象来处理。

  

如下所示,这种代码里面到处是getFeature、addFeature的写法,是一种典型的把数据模型当领域模型的错误示范

  

  

上面展示的代码,是一个在某中台上写业务代码的同学,在离职那天发给我看的,他说他受够了这种乱七八糟的代码,但作为一个底层小P,又无能改变局面,无奈之下,只能选择离开。

  

领域模型和数据模型各司其职上面展示了因为混淆领域模型和数据模型,带来的问题。正确的做法应该是把领域模型、数据模型区别开来,让他们各司其职,从而更合理的架构我们的应用系统。

  

其中,领域模型是面向领域对象的,要尽量具体,尽量语明确,显性化的表达业务语义是其首要任务,扩展性是其次而数据模型是面向数据存储的,要尽量可扩展。

  

在具体落地的时候,我们可以采用COLA的架构思想,使用gateway作为数据对象(Data Object)和领域对象(Entity)之间的转义网关,其中,gateway除了转义的作用,还起到了防腐解耦的作用,解除了业务代码对底层数据(DO、DTO等)的直接依赖,从而提升系统的可维护性。

  

  

此外,教科书上教导我们在做关系数据库设计的时候,要满足3NF(三范式),然而,在实际工作中,我们经常会因为性能、扩展性的原因故意打破这个原则,比如我们会通过数据冗余提升访问性能,我们会通过元数据、垂直表、扩展字段提升表的扩展性。

  

业务场景不一样,对数据扩展的诉求也不一样,像pricerule这种简单的配置数据扩展,json就能胜任。复杂一点的,像auctionextend这种垂直表也是不错的选择。

  

wait,有同学说,你这样做,数据是可扩展了,可数据查询怎么解决呢?总不能用join表,或者用like吧,实际上,对一些配置类的数据,或者数据量不大的数据,完全可以like。然而,对于像阿里商品、交易这样的海量数据,当然不能like,不过这个问题,很容易通过读写分离,构建search的办法解决。

  

  

关于扩展的更多思考最后,再给一个思考题吧。

  

前面提到的数据扩展,还都是领域内的有限扩展。如果我连业务领域是什么还不知道,能不能做数据扩展呢? 可以的,salesforce的force.com就是这么做的,其底层数据存储完全是元数据驱动的(metadata-driven),他用一张有500个匿名字段的表,去支撑所有的SaaS业务,每个字段的实际表意是通过元数据去描述的。如下图所示,value0到value500都是预留的业务字段,具体代表什么意思,由metadata去定义。

  

  

说实话,这种实现方式的确是一个很有想法,很大胆的设计,也的确支撑了上面数以千计的SaaS应用和salesforce千亿美金的市值

  

只是,我不清楚从元数据到领域对象的映射,salesforce具体是怎么做的,是通过他们的语法糖apex?如果没有领域对象,他们的业务代码要怎么写呢?反正据在salesforce里面做vendor的同学说,他们所谓的low code,里面还是有很多用apex写的代码,而且可维护性一般。

  

anyway,我们绝大部分的应用都是面向确定问题域的,不需要像Salesforce那样提供“无边际”的扩展能力。在这种情况下,我认为,领域对象是最好的连接数据模型和业务逻辑的桥梁

相关文章