当前位置: 首页 > 科技观察

数据库变更的持续集成和交付

时间:2023-03-20 18:40:30 科技观察

【.com快言】近年来,一种被称为DevOps的软件工程文化在许多组织中悄然流行起来。它旨在统一软件开发(Dev)和IT运维(Ops),通过持续集成(CI)和持续交付(CD)两大概念提倡软件工程实践中的自动化。今天,许多应用程序开发团队能够从这样的敏捷开发实践中实现:频繁的软件交付、及早接收客户反馈、在组织内拥有跨职能团队、更快地将产品推向市场并让客户满意花费。然而,传统的数据库手动变更管理流程正逐渐成为持续交付的瓶颈。对此,本文将重点讨论如何将其简化为应用代码的统一交付??流水线。作为敏捷开发过程的核心原则之一,持续集成强调确保团队多个成员开发的代码能够顺利集成,从而避免孤岛的“集成地狱”。它主要涉及独立和自动化构建,以及自动化测试。可以说,持续集成推动了测试驱动开发和频繁“原子”提交到版本控制系统的基线、主分支、主干的实践。图1:典型的持续集成流程如上图所示,一旦开发人员将代码签入源代码控制系统,就会触发持续集成服务器中的配置构建作业。该作业将从版本控制系统检出代码、构建它、执行测试并将生成的工件(例如jar文件)部署到工件存储库。一部分定期触发的CI作业会将代码部署到开发环境,将详细信息推送到静态分析工具,并对部署的代码或团队认为可行的自动化流程进行系统测试,从而确保代码库运行状况良好。同时,敏捷团队有责任确保在上述自动化过程中出现任何故障时,可以暂停代码提交,直到自动化构建修复。持续交付除了要确保软件系统中的不同模块始终能够集成,持续交付还需要确保代码始终能够部署到生产环境中。这意味着除了自动构建和测试套件之外,系统还有一个自动交付过程。通常,只需单击一个按钮即可在几分钟内部署软件。同样作为DevOps的一个核心原则,持续交付的优势包括:可预测的部署、降低引入新特性的风险、缩短客户反馈的周期、提高软件的整体质量。图2:一个典型的持续交付流程“持续交付”流程往往基于“持续集成”流程。上图包含两个环境,UserAcceptanceTesting(UAT)和Production。然而,不同的组织在软件进入生产环境之前可能有多个阶段(模拟)环境,例如质量保证(QA)、负载测试、预生产等。当然,所有暂存和生产部署都是通过相同的自动化过程执行的,针对不同环境使用相同版本的代码库。我们可以使用多种工具来实现配置的自动化、控制、可重复性、可靠性、可审计性和可逆性(或回滚)。数据库变更管理的瓶颈是不可否认的。除了交付开发的应用程序代码外,几乎所有的项目还涉及与数据库相关的工作,例如模式(结构模式)的更改。目前,我们认为敏捷原则或持续集成尚未在数据库开发中采用。因此,与此类数据库相关的工作或多或少会减慢整个软件产品的交付过程。让我看一个真实的案例。开发团队遵循Scrum的敏捷方法进行了为期2周的冲刺(迭代)。一个当前故事(storyline)就是在文档中增加一个新的字段,可以与下游系统进行交互。开发团队估计,在代码开发方面,业务事件触发应用程序向下游系统发送文档,以及后来的检索系统,只涉及数据访问层的微小变化。因此,这个仅向现有数据表添加新列的故事,如果不涉及对数据库(在本例中为关系数据库管理系统)的更改,则可以在当前冲刺中轻松实现。然而,正是由于涉及到数据库修改,开发团队可能对此类迭代的可行性缺乏信心。为什么?原因是他们需要向数据库管理员(DBA)发送架构更改请求。DBA将花时间确定变更请求的优先级,并将其与从其他开发团队收到的变更请求进行比较。开发数据库完成变更后,DBA会通知开发人员,等待他们的反馈,以便将变更推进到QA或其他阶段的环境中。同时,开发人员将测试新架构中代码的更改。最后,通过开发团队和DBA的密切配合,将应用变更和数据库变更共同交付到生产环境。图3:交付数据库更改的手动与半自动过程值得注意的是,在上图中,该过程不是由开发人员签入代码触发的,而是需要两个团队之间的交互。也就是说,即使数据库端的部署过程是自动化的,也无法与应用代码的交付管道集成。虽然应用程序代码的变化在某种程度上直接依赖于数据库的变化,但这两种变化的生命周期是完全相互独立的。接下来,我们将讨论如何将与数据库更改(包括数据建模和模式更改等)相关的工作纳入CI/CD流程的范围内。DBA应该是跨职能敏捷团队的一部分许多组织通过协助建立应用程序开发数据库和维护生产数据库来区分DBA的角色。其中,服务于生产环境的DBA的主要职责是通过监控数据库、处理升级和补丁、分配存储空间、进行备份和恢复来保证生产环境中数据库的可用性。开发DBA需要与应用开发团队紧密合作,预估存储需求,并协助他们设计数据模型,将逻辑模型转化为数据库的物理模式。可见,要将数据库工作和应用开发工作整合到一个交付流水线中,就必须让开发DBA成为开发团队的一员,成为一名具备良好数据库知识的全栈开发人员。数据库即代码(DatabaseasCode)为了将数据库变更和应用程序代码集成到一个管道中,我们需要为数据库中的每个变更编写脚本,并对它们进行版本控制,然后按需自动创建脚本。一个新的数据库实例。如果我们必须将数据库的对象捕获为代码,那么就需要根据脚本(即代码)的类型对数据库进行评估和分类。具体区别标准如下:数据库结构:就是我们常说的schema(模式),它定义了数据库存储数据的结构,包括对表、视图、约束、索引、类型的定义。数据字典也可以被认为是数据库结构的一部分。存储代码:这些代码与应用程序代码非常相似,只是它们存储在数据库中并由数据库引擎执行。它们包括:存储过程、函数、包和触发器。参考数据:这些通常存储一组被其他业务数据表引用的允许值。在理想情况下,参考数据表中几乎没有数据记录。它们可能仅在某些业务流程发生变化时发生变化,但在正常业务过程中不会发生变化。应用程序数据或业务数据:它们是应用程序在正常业务过程中产生的数据记录,这是将任何数据库添加到应用程序系统中的主要目的。一般来说,在上述四种类型的数据库对象中,前三种可以而且应该被捕获为脚本,然后存储在版本控制系统中。表1:可编写脚本或不可编写脚本的数据库对象类型如上表所示,业务或应用程序数据是唯一不能编写脚本或存储为代码的类型。所有的回滚、修改、归档等都是由数据库自己完成的。唯一的例外是当模式更改导致数据迁移时(例如,填充新列,或将数据从基表移动到规范化表),迁移脚本被视为代码并且应遵循相同的生命周期。下面我们以一个简单的数据模型为例(大家可以想到数据建模的“HelloWorld”)来说明如何将脚本存储为代码。图4:此示例模型包含业务数据表和参考数据表。在上述模型中,客户可能与多个地址相关联,例如:帐单地址、送货地址等。AddressType表存储了不同类型的地址,例如:Billing、Shipping、Home、Work等。AddressType中存储的数据可以认为是参考数据,毕竟它们不会在日常业务操作中出现激增。随着客户数量的增加,其他包含业务数据的表将继续增长。下面是各种示例脚本:表:约束:参考数据:从上面的例子可以知道,除了业务数据,其他所有的数据库对象都可以捕获到SQL脚本中。版本控制的数据库工件与应用程序代码在同一个存储库中:在版本控制系统中将数据库工件与应用程序代码保存在同一个存储库中有几个好处。由于在大多数情况下,对数据库架构的更改通常会涉及对应用程序代码的更改,将它们标记为共同发布可以避免应用程序代码与数据库之间出现不同步的情况。同时,由于与项目相关的所有内容都集中在一个地方,新团队成员可以更轻松、更方便地学习和查阅,从而加快工作效率。图5:包含数据库代码的JavaMaven项目示例结构上面的目录结构显示了如何在JavaMaven项目中存储数据库脚本和应用程序代码。当然,这对于Ruby或.Net等应用程序也很常见。CI/CD自动化工具可以在一个地方找到它们,并对它们执行必要的操作,例如:从头开始构建架构、生成迁移和生成部署脚本。将数据库工件集成到构建脚本中:为了确保对数据库的更改与同一交付管道中的应用程序代码“齐头并进”,我们必须在构建过程中包含数据库脚本。通常,数据库工件是某种形式的SQL脚本,大多数主流构建工具都支持在本地或通过插件执行SQL脚本。这里先说在本地环境,也就是CI服务器上搭建,后面再说staging环境。包括的典型任务包括:删除模式。创建模式。创建数据库结构(或架构对象),包括表、约束、索引、序列和同义词。部署存储的代码,包括过程、函数和包。加载参考数据。加载测试数据。构建工具确保数据库在加载已知数据集时处于稳定状态,并通过充分的集成测试以避免与应用程序代码和数据模型不同步。这是在数据库变更管理流程中迈向持续交付模型的第一步。图6:此代码片段显示了用于运行数据库脚本的Maven构建上面的代码屏幕截图显示了如何使用Maven插件来运行SQL脚本。它可以删除和重建模式并运行所有DDL脚本来创建表、约束、索引、序列和同义词。接下来,它将所有存储的代码部署到数据库,最后加载所有参考和测试数据。避免共享数据库让多个应用程序共享一个数据库模式并不是一个好主意。将应用程序代码和数据库更改放在同一个交付管道下将不会按预期工作,除非数据库确实属于一个应用程序并且不与其他应用程序共享。同时,共享数据库会导致应用程序之间的紧密耦合以及许多其他问题。每个提交和CI服务器的专用Schema开发人员总是希望能够在自己的“沙箱”中工作,而不必担心开发数据库实例等常见环境。CI服务器就是这样一个沙箱,它遵循应用程序代码开发的模式。开发人员可以执行各种更改,在本地运行构建,并在构建成功并通过测试后提交更改。通常,这样的沙箱可以是安装在开发人员本地计算机上的独立数据库实例,也可以是共享数据库实例中的另一种架构。图7:开发人员在他们的本地环境中进行频繁的更改和提交如上图所示,每个开发人员都有自己的模式副本。执行构建后,除了构建应用程序之外,它还从头开始构建数据库模式。其中包括:删除和重建模式,执行DDL脚本以加载所有模式对象(例如表、视图、序列、约束和索引)。同时,它创建表示存储代码的对象,包括:函数、过程、包和触发器。最后,它将加载所有参考和测试数据。自动化测试可确保应用程序代码和数据库对象始终保持同步。值得注意的是,由于数据模型不像应用程序代码那样频繁变化,为了构建性能,我们应该让构建脚本有跳过数据库构建的选项。事实上,CI构建作业也应该设置有自己的数据库沙箱。毕竟,构建脚本完成了完整的构建,包括构建应用程序和从头开始构建数据库模式。此外,它还运行一整套自动化测试,以确保应用程序本身以及与之交互的数据库保持同步。图8:修改后的CI流程,集成数据库构建和应用程序代码构建上图中描述的流程与图1中的类似。CI服务器包含在提交到存储库时触发的构建作业。它执行的构建包括应用程序和数据库构建。此时,数据库脚本可以集成到应用程序代码中。处理迁移我们在上一篇文章中讨论了如何从头开始构建数据库模式对象、存储代码、参考数据和测试数据,以用于持续集成和本地环境。那么生产环境中的数据库,以及QA或UAT环境中的数据库如何处理呢?鉴于数据库的本质是支持业务数据,我们不可能对当前正在运行的业务事务数据库删除schema或者从脚本中重建等等。因此,我们需要编写增量脚本,即从已知状态改变数据库的结构(所谓的“软件定制”),或者将数据迁移到期望的状态。例如,为了标准化,我们可能需要通过脚本将数据从一个表迁移到一个或多个子表。通过在源代码存储库中编写脚本,可以使架构更改成为构建的一部分。这些脚本可以在积极的开发过程中手动编写,也可以通过一些自动化工具编写。一个这样的工具是Flyway,它生成迁移脚本,将数据结构从一种状态转换为另一种状态。可以在源代码存储库中编写和维护架构更改,以便它们成为构建的一部分。图9:模式迁移和回滚的自动化在上图中,左侧显示了与应用程序先前版本(1.0.1)同步的数据库状态。所需的下一个数据库版本状态显示在右侧。在版本控制系统中,我们不仅可以捕获并标记左侧的状态,还可以将捕获到的右侧状态作为基线、主分支或主干。两者之间的区别正是我们需要在暂存环境中将数据库保持在与生产环境中不同的状态。上图是Flyway工具通过创建迁移脚本将数据库从旧版本迁移到新版本;以及通过回滚脚本将数据库转换回以前版本的自动化过程。这些生成的脚本将被标记并与其他部署工件一起存储。因此,我们通过将此自动化流程与持续交付流程集成来确保可重复、可靠和可逆(通过回滚)的数据库更改。将数据库变更纳入持续交付现在,我们可以将以上几部分结合起来,即:通过现有的持续集成流程重建数据库和应用程序代码;生成迁移脚本。DevOps工具将使用这些已发布的工件来构建任何暂存或生产环境。部署工件也会包含回滚脚本,这样如果出现问题,我们可以通过运行数据库回滚脚本来重新部署以前版本的应用程序,将数据库的模式转换为与以前版本的应用程序代码状态同步.图10:持续交付与数据库变更上图描述了将数据库变更管理纳入持续交付的过程。这里假设持续集成过程已经存在。在启动UAT部署(或其他暂存环境,如测试、QA等)后,自动化过程将负责在源代码控制存储库中创建标签,并从标记的代码库构建可部署的应用程序工件,生成数据库迁移脚本,组装工件,并执行部署。整个部署过程包括:应用部署,以及应用迁移脚本到数据库。在批准过程之后,应用程序通过相同的工件部署到生产环境中。回滚到以前的版本需要重新部署应用程序并运行数据库的回滚脚本。市面上可用的工具前面我们主要介绍了如何在涉及数据库变更的项目中实现CI/CD流程。在实践中,我们经常会根据不同的需求使用不同的工具,例如:用于构建自动化的Maven或Gradle,用于持续集成的Jenkins或TravisCI,以及用于配置管理的本地解决方案,如Chef或Puppet。下面,我就为大家罗列一下数据库DevOps的自动化通用工具:DaticalRedgateLiquibaseFlyway总结持续集成和持续交付的过程确实给组织带来了如:缩短产品上市时间,可靠发布,改进软件整体质量和其他巨大的好处。针对手动数据库变更管理带来的交付瓶颈,本文与大家一起探讨如何将数据库变更与应用代码纳入同一个交付管道,以及市面上各种可以配合这种实践的实用工具。原标题:数据库变更的持续集成和持续交付,作者:AshokeBhowmick