数据库表的结构设计可谓是ofbiz除技术框架之外,另一个非常值得学习的方向。这篇文章我们来谈谈ofbiz对电子商务会员表的设计。 party ofbiz对人、团体进行了抽象,称之为party,翻译为中文称之为“会员”(但我觉得抛开领域,如果你也有相关的设计需求,在其
数据库表的结构设计可谓是ofbiz除技术框架之外,另一个非常值得学习的方向。这篇文章我们来谈谈ofbiz对电子商务会员表的设计。
partyofbiz对人、团体进行了抽象,称之为party,翻译为中文称之为“会员”(但我觉得抛开领域,如果你也有相关的设计需求,在其他领域可能称之为团体更合适)。会员在ofbiz被设计为一个抽象的概念(对应到面向对象设计中,你可以称其为一个基类),它有两个具体的延伸(继承者):分别是person以及party_group。数据库的e-r图:
这里person,party_group分别表示“个人会员”、“组织会员”。party只是一种抽象,它定义了可以被抽象为“会员”的对象所具有的基本特征。但 “个人”以及“组织”会员却具备比 “基本会员”更多的特征,所以此处从party延伸出两张表来存储这些额外的特征信息它们的主键都是party表的party_id。
喎?http://www.2cto.com/kf/ware/vc/ target=_blank class=keylink>vcd48ade+uefsvflfvflqrtwvade+pha+cgfydhluexbltqjs5chlcgfydhm1xmdg0m3uvmr4oanflvlnvmjnz8kjujxiciavjmd0ozwvcd48cd48aw1nihnyyz0=http://www.2cto.com/uploadfile/collfiles/20141208/2014120809092365.png alt=\ />
可以看到,party_type是拥有层级关系的(它的一个属性parent_type_id自关联了party_type的主键:party_type_id,下面如果看到e-r图上有自关联到本身的,都表示这种关系,不再敖述)。
ofbiz提供的初始数据中有如下几种party type:
构建成层级关系如下图所示:
上面展示的两张表:person、party_group也是其中的两个partytype,并且这些partytype都可以独立扩展的,person、party_group也是仅有的两个扩展。这也是上面表结构中这两个记录的has_table值为y的原因。
party_role就跟社会的“角色分工”一样,一个会员在系统中也必定会拥有属于自己的角色。而party_role表就是用于关联会员与角色类型的关系表,很明显会员与角色类型是多对多的关系(这里需要提及的是:ofbiz中只有角色类型,没有角色,或者更准确点说,角色类型包含了角色)。
party_relationship上面我们看到的会员是一类“抽象”的实体。不管它表示的是个人,还是组织,它总是会跟其他会员发生关系,就好像一个人不可能脱离社会而孤立得存在着,他必然有自己的社会角色,并跟社会的其他“团体”产生联系。这在ofbiz中被抽象为“partyrelationship”。我们来看它是如果表达“关系”这个语义的:
当你把这些所有的字段连起来,它几乎能涵盖所有的“会员关系”(要知道,有时会员关系会非常复杂,一个会员有时会存在于多个系统中)。
我们再回过头来,看party_relationship的表结构设计:
可以看到前五个键形成了联合主键,其中前四个都是形如xxx_from,xxx_to的id标识。表示从“from”方往“to”方建立关系。其中party_id_from与role_type_id_from是“源”方;party_id_to与role_type_id_to是“目标”方。
从上面图中也可以看到,每个关系都带有两个datetime字段,分别表示:开始日期,截止日期。这说明关系是有“时段”这个属性的。当然,如果没有截止日期,可以看做是“永久”的。因此为了防止关系过了生效时段无法再次建立关系(因为主键不允许重复),所以选择了联合“from_date”作为联合主键(后面如果再次建立相同的关系时,只要from_date不一样,就视为一条新记录)。
这里有必要说明一下,在ofbiz的数据库设计中,大量采用了“时段”这个属性来标识记录的有效性。这样的设计与逻辑删除相比的好处是:它除了减少了删除时因为外键约束等连带关系导致的错误,还可以直接充当“历史记录”的作用,省去了对历史表的维护,当然它的缺点就是:表中的记录会比其他的设计多得多。
当然,from跟to只是为了标识两者建立了关系,却并未说明它们到底存在怎样的关系,就好像——我跟你是朋友。这句话可以拆分为三部分:from方:我,to方:你,关系是:朋友。上表中用一个字段表示了关系:party_relationship_type_id(这只是一个外键,关联着表party_relationship_type)。
在界面上新建一个关系(此处是从外部到自己的一个关系):
party_relationship_type该表约束了关系的类型。比如:雇佣者、朋友、父、子、管理者,e-r图:
从图中可以看出,会员关系类型也拥有层次关系。表中还有两个特别的字段:
role_type_id_valid_fromrole_type_id_valid_to
它们用于约束这个关系的建立双方的角色。也就是说,不是任意的两个角色之间一定可以建立起某个特定的会员关系。当然这两个字段通常都为空,表示不对此加以限制。
对每个关系类型,都可以扩展以独立实现关系(被扩展后关系类型记录的字段has_table被标识为y,否则默认为n),在ofbiz的初始化数据中,唯一被扩展的关系类型是:employment。我们来看看employment关系表的实现:
可以看到,它跟之前的party_relationship的主键实现方式一样。因此可以把它看做是:party_relationship_type_id为employment的party_relationship的特殊实现。
在界面上建立一个关系类型:
party_classification_type为了便于管理,ofbiz对会员按各种维度进行分类,常见的分类的类型有:年收入、价值等级、产业、雇员数量等;
party_classification_group会员并不会直接跟分类的类型产生关系,而是跟一个或多个分类组产生关联关系。而分类组受分类类型约束。
新建一个分类组:
party_classification会员的分类相关表的关系图:
从表的关联关系可以看出,会员分类跟分类组是多对多的关系,并且分类具有时效性。因此联合from_date作外键。
将会员划归入一个会员分类:
content_mech从这张表开始,我们来看会员的联系方式相关的表结构设计,这也是一部分非常棒的设计。
这张表存储了联系方式基本信息。它引用了另一张表:content_mech_type作为外键,来表示该联系方式的类型(通常的联系方式类型有电话、邮箱、网址等)。
content_mech_type
可以看到联系方式类型,也是具有层级结构(父子关系)的。
当我们想新建一个联系方式时,首先必须先指定想创建的联系方式的类型:
party_contact_mech毫无疑问,地址信息只有跟会员联系起来,才能表示会员的地址。而会员跟地址是多对多的关系,理解这个关系时需要注意的是会员可以是任何团体、组织或者个人。那这里可能就会存在两个不同的会员拥有同一个联系方式的可能,比如:一个员工会员与一个该员工所属的公司会员,它们可以都存在同一个联系方式:公司的通讯地址。当然一个会员拥有多个联系方式,这是很容易理解的。所以会员标识跟联系方式标识之间是多对多的关系,并且跟前面的设计模式相似——联系方式也有时效性,比如换电话号码,换工作导致联系方式变化等,所以联合from_date作为联合主键:
当我们选择联系方式类型为电话号码时,会出现如下的表单填写:
如果你新建一个联系方式的类型为电话号码,那么电话号码存储在何处?此处又跟前面谈到的has_table字段有关(contact_mech_type中也存在这个字段)。正常情况下,联系方式关联着联系方式类型,普通的联系方式的具体信息存储在contact_mech的info_string属性中。但有些联系信息不是单纯的像邮箱这样只是一个字符串,比如像电话号码、邮政编码…它们都有具体的格式表示。所以这些特例用info_string这一个属性存储也不方便,因此可以独立扩展该contact_mech_type(将其has_table字段设置为y,这样查询该contact_mech信息的时候,就不采用info_string字段,而是采用扩展表中格式化的联系方式)。
contact_mech_purpose_type当我们点击上面界面的保存按钮之后,会更进一步得扩充联系信息:
在ofbiz中还存在一个称之为“联系目的”的东西,它是什么意思?
看到选项我们就会明白,说白了一个人的地址簿或者电话簿中的联系方式可能有很多。它们没有主次之分,只有目的不同。
party_contact_mech_purpose上面谈到了联系目的,那么很自然它需要跟会员具体的某条联系信息关联起来才能称之为:某个会员为了某种联系目的存储了一个“联系方式”记录。
这里需要注意的是,它并没有跟party_contact_mech产生直接关联(没有外键关系),而是把party_contact_mech的三个主键照搬过来,联合contact_mech_purpose_type_id形成四个组合主键,这是因为party_contact_mech的联合主键机制无法被其他表当做外键引用。因此,可以将party_contact_mech_purpose看作联系信息模块的聚合。这个怎么来理解?其实一个地址可以看成:某个会员(party_id),出于某种目的(contact_mech_purpose_type_id),在某段时间内(from_date),保存了某个联系方式(contact_mech_id)。这种联系方式的设计非常有弹性,因此在大部分情况下,这种抽象性能够涵盖大部分应用场景。
content_type会员内容的设计跟联系方式类似。会员可以有一个类似文件空间在服务器上,可以供其保存文档、图片之类的东西。content_type限定了会员可以存储的内容类型:
content该表是它的具体存储内容的地方,当然并不是唯一的,如果content_type有一条记录的has_table值为y,则那个记录对应的表也用于存储内容。内容表里的字段非常多,就不截图了。
跟之前的联系信息类似,会员可以有多个内容,一个内容也可能从属于多个会员。因为会员是个抽象的概念,对应到实体上可能会有重合,所以需要一个“目的”来修饰会员内容,它就是——party_content_type。
party_content_type用于修饰会员内容的用途,当然这里它的表名叫type,事实上从数据记录来看,来时充当了目的的作用。
内容还跟其他一些表有关联(主要是被引用关系,比如:content_role等),此处因为跟本文主题没太大关系,所以不再敖述。
总结更高的抽象级别ofbiz party模块的设计,正如它所应用的场景:非常适用于电子商务系统会员信息相关的设计。当然ofbiz中其他相关的多个系统也同样应用了这些表结构,这也意味着它有适用于一般行业、系统的通用性,这得益于这种设计的抽象级别比较高。它可以描述任何的组织、个体、他们的地址信息、他们之间的关系。特别是对会员“relationship”表的设计非常类似于《分析模式》中谈到的责任模式:
系统的用户,又或者一个非常大的跨国公司,拥有:总部、区域销售办公室、办事处、分公司等各种组织形式时,这种设计就会派上用场。
数据库表的继承关系从party、party_type、party_group、person这几张表我们可以学习到数据库表的“继承”设计。
时效性设计不是真删除、也不是逻辑删除、而是失效(from_date, thur_date)。这种方式可以代替“操作-操作历史”的多表设计,转而合并为独立的一张表。