需求描述:现在有多条产品线,每条产品线都有几十种产品,且所有产品线中的不同产品之间都可能有公用字段,而且不同产品可能会公用一些计算公式,产品信息通过IP多播发送,给你一个IP多播的server:port,你来监听,然后每次能消费到一个字符串,这个字符串就代表一个产品(比如金融里面债券类似产品,数量非常庞大),请问你要怎么样来定义产品的schema?
1.需求分析
关键点:产品线非常多,且有大量共用字段
假设你用java语言,你可以每个产品都对应一个pojo,那么这样会有大量pojo产生,且你接收到消息之后要判断使用哪个pojo类来反序列化,这就要使用很大规模的switch-case语句,当然你也可以使用map{type:pojoClass}来优化swith-case语句,但是你不可避免的要设计大量的不同产品的pojo类,虽然你可以使用继承的方式把公用字段抽取出来放到父类中,但是继承的代价太大,且这个抽取让人十分头疼,而且如果这些产品的字段变化很频繁呢?那你就要频繁修改pojo类,这使得维护也十分难受和头疼
2.基于配置的schema设计
2.1设计思路
这种有大量schema的,且schema之间有公共字段或计算方法的,可以采取使用配置文件来定义schema,然后把所有要用的字段,在开发语言级别将所有产品的所有字段和所有计算方法都放到一个pojo中,这样使得(1)定义的pojo类最少,(2)维护更方便,可以尽可能只修改配置文件而不修改代码,比如删除某个产品的字段时,但是添加之前没有的新字段还是需要修改pojo类
考虑:当产品字段变更频繁时,可能需要频繁修改唯一的pojo,这可能风险比较高,因为可能影响到其他pojo,比如修改A产品的字段时不小心删了B产品的字段。
优化:可以在开发前按照产品的变化程度对产品分级,按照不同级别来将不同产品化为一类,比如绝对不会变的产品归为一类,这一类使用一个pojoA,变化程度中等的归为一类,这一类使用一个pojoB,变化程度高的使用POJOC这样的思路。
2.2 设计具体实践
- 以xml来设计schema配置文件,一个xml文件表示一个系列的产品,每个产品称为一个product,一个产品系列称为productSeries
- 一个product包含了大量field和一些method,为了提高抽象程度和减少配置代码量,将本系列产品公有的fileds抽取出来,并设计FieldGroup,一个FieldGroup可以包含多个fileds,设计MethodGroup,一个MethodGroup可以包含多个methods,根据产品公用字段分布情况来决定,将抽象出来的fileds分成不同的FieldGroup组,和将抽象出来的methods分成不同的methodGroup组。
- 因为会有多个公用的FieldGroup和多个公用的methodGroup,所以必须给每个FieldGroup、methodGroup给唯一标识,来使得message引用的时候能正确引用,所以设计id、referenceId给FieldGroup、methodGroup,id是FieldGroup、methodGroup声明自身时使用的,referenceId是message查找公用的FieldGroup、methodGrou使用的
- 考虑到需求里面说每一个产品是一个字符串,解析的时候要按照位置来解析,即不同位置对应不同的field,所以要嘛给一个filed标记一个绝对位置编号(在消息字符串中的位置),要嘛给一个相对位置编号(所有字段排序),但是我们这里采取了相对位置编号,因为产品可能会新增、删除字段,在新增、删除字段时需要对大量字段的绝对编号修改,这工作量太大。
- 采用相对编号的形式给字段排序,messag内首先定义不同FieldGroup的相对位置,每个FieldGroup内在定义组内fileds的相对位置。
- 而且还要定义每个字段的字符长度,比如字段A有2个字符长,用length标识一个字段的长度,这样才能精确知道某一个字段到底是截取消息字符中的哪一段消息。
- 考虑到虽然定义了相对位置,但是每个位置的字段长度可能不是固定的,必须长度可能是0、1、2、10这种,如果中间某个字段删除了,那么怎么让后续的字段正确映射到正确的字符串的正确位置呢?使用offset,当中间某个长2字符的字段被删除后,给后一个字段加上offset=2的标识,表明该字段要跳过2个字符继续读取。
- 字段的数据类型dataType
- 日期类型可能很多种,当dataType="datetime"时,用format来特别指定具体的日期类型,比如"yyyyMMddHHmmss",考虑到数据库可能需要的日期类型和从多播收到的类型不一样,可以用dbFormat特别定义数据库需要的日期类型,比如dBFormat="yyyy-MM-dd HH:mm:ss"
2.3 一个产品线的所有产品定义例子xml
<?xml version="1.0" encoding="UTF-8" ?>
<ProductSeries defaultClass="cbf.cdb.data.ProductSeriesA">
<FieldGroup id="MessageHeader">
<Field sequence="1" length="1" name="filedA" dataType="string"/>
<Field sequence="1" length="1" name="filedB" dataType="string"/>
<Field sequence="3" length="2" name="filedC" dataType="string"/>
<Field sequence="4" length="3" name="filedD" dataType="string" offset="1"/>
<Field sequence="7" length="8" name="filedE" dataType="datetime" format="yyyyMMddHHmmss" dBFormat="yyyy-MM-dd HH:mm:ss"/>
</FieldGroup>
<MethodGroup id="calculate1">
<Method sequence="0" name="doCal1_1"/>
</MethodGroup>
<MethodGroup id="calculate2">
<Method sequence="0" name="doCal2_1"/>
</MethodGroup>
<Product name="Security1" category="C" type="I">
<FieldGroup sequence="0" name="Header" referenceId="MessageHeader"/>
<MethodGroup sequence="0" name="" referenceId="calculate1"/>
</Product>
<Product name="Security2" category="T" type="M">
<FieldGroup sequence="0" name="Header" referenceId="MessageHeader"/>
<FieldGroup sequence="1" name="Label">
<Field sequence="1" length="14" name="S1" dataType="string"/>
<Field sequence="2" length="09" name="c1" dataType="string"/>
<Field sequence="3" length="12" name="b1" dataType="string"/>
<Field sequence="4" length="05" name="p1" dataType="string" comment="sUB-Product type"/>
</FieldGroup>
<FieldGroup sequence="2" name="Label1">
<Field sequence="1" length="08" name="o1" dataType="datetime" format="yyyyMMdd" dBFormat="MM-dd-yyyy"/>
</FieldGroup>
<FieldGroup sequence="3" name="Label2">
<Field sequence="1" length="01" name="Q1" dataType="string"/>
<Field sequence="2" length="14" name="Q2" dataType="double"/>
<Field sequence="3" length="11" name="Q3" dataType="double?"/>
<Field sequence="4" length="01" name="Q4" dataType="string"/>
<Field sequence="5" length="01" name="Q5" dataType="string"/>
<Field sequence="6" length="01" name="Q6" dataType="string"/>
<Field sequence="7" length="01" name="Q7" dataType="string"/>
<Field sequence="8" length="14" name="Q8" dataType="datetime" format="yyyyMMMddHHmmss" dBFormat="yyyy-MM-dd HH:mm:ss"/>
<Field sequence="9" length="02" name="Q9" dataType="string"/>
<Field sequence="10" length="01" name="Q10" dataType="string" offset="2"/>
</FieldGroup>
<FieldGroup sequence="4" name="Label3">
<Field sequence="1" length="1" name="ChangeIndicator" dataType="int?"/>
</FieldGroup>
<MethodGroup sequence="0" name="" referenceId="calculate1"/>
<MethodGroup sequence="1" name="" referenceId="calculate2"/>
</Product>
</ProductSeries>
解释:
- ProductSeries表示一个产品系列
- 所有的MethodGroup就是本系列公用的计算方法组
- 所有的FieldGroup就是本系列产品公用的字段组
- name为Security2的Product,他的category="T" type="M"表示产品系列编号为T,产品编号为M,其包含了5个FieldGroup,序号sequence分别是 0、1、2、3、4,这也说明了这几个FieldGroup的相对顺序,其实这里的sequence也可以写成0、2、7、8、9,因为只要相对顺序保持不变即可。
- 每个FieldGroup内部的的fileds也用sequence得到相对顺序,length表明了该字段需要的字符长度