本节书摘来自异步社区出版社《测试驱动数据库开发》一书中的第2章,第2.3节,作者:测试驱动数据库开发,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.3 数据库的类
测试驱动数据库开发
尽管事实上,大多数的时候,数据库就是上面保存那些不被使用的对象内容的“其他地方”,在数据库开发中运用上述模式一点也不切合实际。与上述描述最接近的做法,应该是当每次想更新对象的行为时,就从旧数据库中迁移数据到新创建的更新后的对象中。对于许多数据库来说,上述做法可能仍然比许多人现在做的方式要快许多,但是因为还有另一种支持比这还要快的开发过程的做法,因此就将上述做法作为一个可选项而不再继续讨论了。
2.3.1 两条途径:创建或改变
在许多系统中,创建某“类”数据库实例有两条途径。一条途径是针对从无到有地被创建的数据库,这种情况经常发生在测试和开发的环境中;另一条途径是针对反复更新的数据库,这些更新是由于随着时间的推移,数据库的设计以行为增量的形式不断地演进而产生的,这种情况往往发生在生产环境中。
应用开发团队的工作往往是上述情况的最好的说明。一个我曾参与工作的团队拥有一个代表“数据库”的脚本,该脚本通过一些如电子邮件或网络共享文件夹这样的机制被共享,当一个开发人员搞乱了他的数据库,他会把整个数据库删除,然后运行这段脚本。当人们要设计一些有意义的变化时,他们会把这些变化写入“脚本”中。有时候一个偷懒的开发人员可能通过一些GUI,用他操作的一个数据库实例为蓝本,重新生成了这个“脚本”。
上述工作方式在开发团队中是很正常的。然而,这些开发团队编写并据此进行测试的上述脚本,永远不会在生产环境下使用。取而代之的是,他们会把新的数据库设计提交给数据库专家,数据库专家再创建一个新的数据库实例,并运行一个diff工具,来搞清楚新的数据库设计与原来相比有什么变更。当然,上述工具的输出结果不会被全盘接受,而仅仅是用来指导数据库专家编写一个新的脚本,从而实现上面的数据库设计的变更。数据库专家会手工备份生产数据库实现变更,并验证一切能够正常工作。
在我与上面这个团队一同工作期间,使用上述工作方式进行了6次产品发布, 只有1次该方式实现了完美地工作。系统所有的用户回家度周末这个后备方案,能够保护我们免受真正的灾难,帮助我们抵御重大的系统服务中断。但是,在周五夜里熬到9点半或更晚才能发布产品真的让人抓狂。
问题的根源是,开发人员试图像对待典型的面向对象编程的类一样,即仅仅通过被创建和析构去对待数据库的类。他们不操心数据库被修改了的情况,因为那是其他人的工作。
然而,全世界每一个重要的数据库都是被创建一次,然后被修改多次的。
2.3.2 难点:统一两条途径
正是由于数据库世界中这种表里不一的现象,使得定义数据库的类变成了一项困难的工作。如何才能为某件事构建一个类,使得在一种情况下,该类被从头开始创建;而在另一种情况下,该类可能在大量迭代周期里被多次构建,且这些迭代周期之间又会间隔很长的时间?
上述“两种途径”的问题并不能真正得到解决。唯一合理的解决方案是完全消除这两条途径,这意味着程序员必须找到实例化数据库的类的单条途径,使得该途径既能产生新的数据库实例,又能更新数据库的类的现有实例。
2.3.3 真实的数据库的生长情况
找到上述途径对读者来说可能是具有挑战性的,不然立刻就能看到解决方案。当难以找到一种解决方案时,看看数据库世界真正发生了什么是会有所帮助的。在这种情况下,研究一下真正的生产数据库是如何构建和成长的对解决问题会很有帮助。
在所有诸如创建空的数据库实例这样乏味的事情结束后,第一件事情就是执行一系列DDL语句,将所有数据库初始化时应具备的行为注入到数据库中。通常情况下,上述事情可以通过执行与开发团队使用的完全相同的SQL脚本来完成。
最后,当作出要以某种方式更改数据库的设计的决定后,开发人员会完成一些工作来确认更改,构建相关的功能和基础架构,并确保一切都能够组合在一起工作,然后生产数据库会被实施一个变更,使数据库的设计发生了由旧到新的改变。
过了一段时间,人们会重复上述过程来实施另一种转变,使数据库演进到下一个版本,如此这般循环往复。
一个清晰的模式正在形成:生产数据库设计的变更通常从每一个发布版本过渡到下一个版本,然后再到下一个,以此类推。这时,一个明显的问题会映入脑海:我们有可能克服变更这个问题吗?
答案是“不能”的。几乎可以这样认为,对于一个长期运转的数据库,其设计必将定期地发生变更。
2.3.4 将每个数据库构建成生产数据库会怎么样
所以,如果不能改变构建生产数据库的方式,可以考虑下面的替代方案:用构建生产数据库的方式来构建每个数据库。这样做会有什么问题吗?肯定有人会得出自己的答案,但是对于我们大多数人,对上述问题的回答是:“用构建生产数据库完全相同的方式来构建每个数据库真是非常有意义。”
规则很简单,具体如下。
1.将一个空的初始化好的数据库实例当做版本零。
2.为了获得新的数据库,构建脚本,使得数据库的版本从零过渡到1。
3.为了升级数据库,构建脚本,使得数据库的版本从N过渡到N+1。
4.当构建数据库时,从数据库的当前版本直到期望的目标版本,依次运行所有相应的脚本。
因此,要构建一个新的版本为3的数据库,可以执行版本1的脚本,接着执行版本2的脚本,再接着执行版本3的脚本。为了将数据库从版本1升级到版本3,可以执行版本2的脚本,接着执行版本3的脚本。
2.3.5 所有数据库都遵循完全相同的途径
问题的关键是,只要依次执行两个版本之间的所有脚本,就能从任意版本过渡到下一版本。要想把数据库从版本5过渡到版本7,除了按上述方式执行脚本,就不需要做额外的工作了。因为你已经知道如何从版本5过渡到版本6,并且知道如何从版本6过渡到版本7,所以你就能知道如何从版本5过渡到版本7。
只要按上述方法来做,就向测试驱动数据库开发的正确方向跨出了一大步。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。