Maison > Article > base de données > 企业级开发中关系型数据库做接口传输的设计思考
企业级开发中关系型数据库做接口传输的设计思考 所谓思考,就是思考。 本文以oracle数据库为例,探讨一下企业级开发中,用数据库做中间表传输数据的一些设计思考。欢迎留言补充指正。 1、 接口双方,谁来主宰定义权 一般的原则是,接收方负责定义接口数据格
企业级开发中关系型数据库做接口传输的设计思考
所谓思考,就是思考。
本文以oracle数据库为例,探讨一下企业级开发中,用数据库做中间表传输数据的一些设计思考。欢迎留言补充指正。
1、 接口双方,谁来主宰定义权
一般的原则是,接收方负责定义接口数据格式和规则。因为接收方必须非常清楚自己要接收什么数据,如果这点都不知道,那可以不用做了。其次,还涉及到数据转换问题,由发送方将某些接收方无法识别的档案数据,转换成接收方能识别的档案数据,再发送给接收方,会比较合理。因为这样在接口交互中,交互的数据,就是一个完整的可接收数据,而不是一个半成品数据。
但也有2种特殊的情况,第1种特殊情况是遵循了兵法原则,先下手为强。尤其出现在后进场的项目组,虽然处于数据接收方的角色,但因为提前进场的项目组已经将发送接口定义好也开发完成了,所以为了避免重复劳动,接收方只能将就发送方的定义规则。
第2种特殊情况是,接收方是一个基础数据上比较完整的系统,而发送方是一个小系统,基础数据比较单一。这就是基础数据转换问题导致的控制权反转。
有些公司的基础数据比较混乱,举个极端例子:A系统定义一套人员编码,全部以字母A开头。B系统定义一套人员编码,全部以字母B开头…以此类推。每一个系统中的人员编码,实际上都是有1-1对应关系的,只是,这种对应关系,仅仅维护在主要系统A中。此时,A系统和B系统做人员接口,要求B系统传输人员数据。若此时由接收方负责定义接口,则A系统会希望B系统把人员编码转换成A系统的格式,再发送给A。但是B系统却无法做到档案的转换,所以只能按照B系统的原则来定义接口。
正如上例,有时会因为种种压力而要求B系统中维护一套对应规则,再由B系统负责接口编码转换。实质上,编码转换的工作量不大,但维护一套对应规则就比较麻烦而且是长期的工作量。如果这套规则对B系统从业务上毫无必要的话,那B系统要非常坚决的反对这种不正当要求。
2、 流水号和编码,谁才是真正的主键
企业级数据中,几乎所有档案都有编码,比如人员编码,公司编码,部门编码。。。这些编码都有一个特点,即不重复。应该不会有2个员工,共用一个人员编码的情况吧。如果现在有一个任务,是通过db来从对方系统传输人员数据,那是否可以用人员编码来作为中间表的主键呢?
当然是不行的。考虑一个最简单的情况,如果人员编码A,发送一次中间表;然后A离职,另一个人入职,启用了原有的编码A,则此时,需要将人员编码A(其实是另一个人)再次发送中间表,那会破坏主键唯一性原则。
现在来想想2个概念,我称它为系统级主键和业务级主键,所谓业务级主键,可以理解成人员编码这种,可以在软件前台界面中看到的主键,它的特点是用户可编辑并且唯一。其生命周期与业务相关,比如人员A离职后,业务级主键A消失;下一个人入职启用了编码A,则业务级主键A神奇复活。
系统级主键,可以理解成流水号,不可以在软件前台界面中看到,不可被用户编辑,由系统底层通过规则创建,此规则与业务逻辑毫无关系。生命周期与此条数据在数据库中的生命是一致的,简单讲,只有delete才会删除此主键,并且没有人工干预的话,是不会神奇复活的。
说到流水号,还有一个比较相近的是批次号。主要出现在db模式接口中,需要对多笔数据执行一个数据库事务操作时。批次号有系统级主键的部分特点,也有业务级主键的部分特点(比如有时是前台可见的)。
3、 越来越通用的不是功能,而是表?
经常有见到将多个种类的需要传输的数据,通过各种潜规则而保存到一个数据库中间表的情况。或者是把原本需要通过主子表、或者关系表的数据,整合到一个表或一个字段中,用各种潜规则来分开标记,赤裸裸的违背了“关系型”数据库原则。
做接口设计的时候,若是能把功能设计的更具通用,从而使接口表数量越少,则越是高端大气上档次。所以datatype字段被大量的使用,自定义项def被青睐。若是一个数据库中间表在定义的时候,不留出十几个自定义项,就感觉很快要出事似的。
笔者有2个观点,第1,给数据库增加字段,非常非常非常的简单。若是用自定义项,还要增加维护一个自定义项具体业务功能说明,反而麻烦。第2,把多种数据放到一个表中用datatype区分,非常非常非常的复杂。造成某些字段含义不单一,数据冗余度高,数据库索引无法彻底分析字段内容的规则,效率下降。
具体需要做几个接口,是业务需求决定的,不是建几张数据库表决定的。少建表,对开发代码工作量没有任何的节约,反而增加工作量。例如:字段A,在数据类型1中是必输,但数据类型2中是不输入。则将2种数据类型混合到一张表的话,接收数据时,就要程序校验字段的必输性。若是分开2张数据库表,则数据类型1的字段A,可以设置数据库约束必输,程序无需校验。而数据类型2的字段A,根本就不用定义此字段了。
4、 对丢失数据的恐惧!
接口系统上线后,若是丢了一条数据,容易造成发送方和接收方互相推卸责任,并且没有很好的方式找到证据。于是备份中间表的数据成为习惯,或者一不小心将备份表作为接口传输的一个流程环节,直接设计成了状态同步表。
先说备份表,千万不要把备份表做成同步表。说的直接一点,在写备份表的程序中,只允许执行insert,不允许任何的select update delete。这样才像备份表。想想日志文件,不都是write吗?不会有程序去read edit delete日志文件吧。
如果你在写备份表程序的时候,非常不小心的使用了select update delete,那么你就把备份表做成了状态同步表了,也就是说,此表中的数据的状态,与整个接口程序和最终数据的状态、与后续程序流程都有了耦合。千万不要这么做。
如何避免数据丢失纠纷,下面是笔者提供的一种方案:
首先是参与接口工程的角色,有发送方、接收方、客户方(第三方)。客户方提出需求,接收方定义中间表,发送方配合。例如中间表t
流水号字段 |
数据字段 |
状态字段 |
id |
Data |
status |
|
|
|
1、 将数据库中间表t,新建在客户方数据库中,既不是发送方数据库,也不是接收方数据库。
2、 在客户方数据库中,新建用户user_send,并将中间表t的查询和插入权限赋予此用户。Grant select,insert on t to user_send。将user_send用户提供给发送方,这样发送方只能查询和写入数据到中间表 ,无法删除数据或更改数据。
3、 在客户方数据库中,新建用户user_receive,并将中间表t的查询和状态字段的更新权限赋予此用户。Grant select,update(status) on t to user_receive。将user_ receive用户提供给接收方,这样接收方只能查询数据,并且只能修改status状态字段,不能修改其它字段,也不能插入和删除数据。
根据上述方案,通过数据库的权限配置,已经将发送方和接收方的责任权限彻底分开,也不必担心数据丢失或被篡改。
5、 对空值的偏爱
数据库的空值是非常特殊的一种类型,并且对于空值如何处理,关系型数据库没有做统一的规定,SQL语言也没有做统一的规定。这是一个非常危险的消息,因为空值可能一不小心造成bug。
例如oracle的varchar2类型字段,select *from table where column = ‘’这种查询是无效的,应该写成select*from table where column is null。换一个数据库,写法又不一样了。
所以在做数据库接口设计的时候,尽量保证字段是有值的。
我经常看到这样的设计,字段A,传值1表示***,传值2表示xxx,若传空值,表示$$$。这是糟糕的设计。
即使SQL和关系型数据库对空值做了统一规范,也不应该在中间表字段中定义空值,这样相当于把一个空值赋予业务含义。并且,可以有空值的字段,不能配置数据库非空约束。还可能由于发送方程序bug而导致没有给字段写值却成了合法的输入。
6、 ABCD还是1234
定义什么样的状态值往往困扰我们。我们已经受够了太多的状态字段和状态值。有的状态字段值分别是ABCD…有的值分别是1234…时间长了,我们已经无法清楚的记住哪个字段是哪个含义,哪个值是哪个含义
真的有必要那么多的状态吗?仔细想想应该没有,除了常见的“初始init”“成功success”“失败fail”“异常error”,我想不出哪个状态是没有办法用英文单词来明确区分的。所以为什么不查查字典,用简单明了的英文单词来描述状态值呢,这样一看就懂了。
7、 唯恐天下不乱的不是记者而是智者
不出问题永远不知道错在哪,如果你懂了,不要说出去,等出了问题,再说出来