Chris Travers 最近发表了一系列题为“建设符合 SOLID 原则的数据库”的文章。他解释了如何运用一些常见的 OOP 原则(如单一责任原则、接口隔离、依赖反转)去改进数据模型和数据库代码的一些想法。尽管其中的一些想法可以在某种程度上应用到任何一种关系型数据库上,但文章列举的场景涉及到了对象关系特性(如表继承),只有类似 PostgreSQL 的数据库才有这些特性。
在单一职责和标准化方面,Chris 解释了数据模型和类模型之间的相同点以及细微的差异。在纯粹的关系型数据库中,标准化通常可以完全满足单一职责原则(SRP),然而,表继承可进一步用来管理通常一起出现的字段,这些字段依赖数据库中的其他字段。他举了一个例子:
对于管理备注来说,其构成方式【译者注,即备注相关表及其表结构】也会有很大差异。人们可能想在数据库里的各种数据上添加备注,因此,任何人都不能说备注的内容或主题是具有依赖性的。
有两种典型的处理纯关系型的方法,一是可以持有很多独立管理的备注表,另外也可以创建一张单独的全局备注表,用这张表保存所有备注,然通过多表关联来建立依赖关系。
对象关系型处理方法可以持有多张备注表,并让它们继承公共备注表的表结构。
开放 / 封闭原则的目标是保持系统的可扩展性,当基线版本变更时会无故破坏系统的可扩展性。表继承则以灵活的方式为数据模型提供可扩展点。——举例来说,pg_message_queue 0.2 能够处理各种数据类型,它有一张单独的且可支持每种数据类型的表,可以从这张公共表去做所有的继承。Chris 另外还列举了一个简单的例子,一个安全的 API 既要支持安全控制上的可扩展,还要禁止被更改。
对于纯粹的关系型数据库,里氏替换原则通常不是问题,但当你使用表继承时,就会暴露出问题了。这里有一个例子,my_square 表继承了 my_rectangle 表。
CREATE TABLE my_rectangle ( id serial primary key, height numeric, width numeric ); CREATE TABLE my_square ( check (height = width) ) INHERITS (my_rectangle); and run an update on my_rectangle - UPDATE my_rectangle SET height = height * 2
这将导致在 square 表中产生引用问题,最终导致运行失败。处理此问题有两个方法,避免整体更新(保持行不可变),或者在更新时,首先使用触发器将 my_square 表中的行删除,然后再插入到 my_rectangle 表中。
在数据库中应用接口隔离时,将涉及主要的用户自定义函数或存储过程。Chris 考虑把它们作为底层数据的接口,并建议理想情况下,在最小的逻辑范围内函数或存储过程应该只有一条大的查询——如果查询语句超过5 条或者具有大量的备选参数,就表明应该简化复杂度,把它们分解为多个独立的函数或存储过程,每一个针对一个特定的目标。这与单一职责原则也是相辅助相成的。
在依赖反转和健壮的数据库接口方面,Chris 解释以什么方式封闭应用逻辑与存储过程的绑定会导致抽象泄露,并提出了几个建议的解决方案。其中某些使用类似于服务探测器模式的东西,或使用视图和函数,或使用自定义数据类型、触发器和通知。在此强烈建议,充分考虑各种选择,像应用一样设计数据库,使其暴露适当的API。
查看英文原文: SOLID Database Design With PostgreSQL
感谢贾国清对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论