如何對DevOps數據庫進行源代碼控制

提綱:

  • 包括索引在內的數據庫模式需要進行源代碼控制
  • 諸如查詢表這類用於控制業務邏輯的數據需要進行源代碼控制
  • 開發人員需要一種能夠便捷地創建本地數據庫的方法
  • 共享數據庫的更新只能通過構建服務器完成

健壯的DevOps環境需要對系統的每個組件進行持續集成。但是,數據庫常常被排除在這之外,這會導致從脆弱的產品發佈和低效的開發實踐到讓新入職的程序員工作更困難等一系列問題。

在本文中,我們將討論在成功的持續集成環境中,關係型數據庫和NoSQL數據庫的獨特的一面。

模式的源代碼控制

首先需要解決的就是源代碼控制和模式問題。讓開發人員以一種特別的方式進行數據庫變更是不合適的。這相當於在生產服務器上通過直接編輯JavaScript文件對其進行更改。

在爲數據庫規劃源代碼控制時,需要確保囊括了所有內容。這包括但不限於:

  • 表或者集合
  • 約束
  • 索引
  • 視圖
  • 存儲過程、函數以及觸發器
  • 數據庫配置

您可能會想,“我使用的是無模式數據庫,所以我不需要源代碼控制”。即便如此,您仍需要考慮索引和數據庫的整體配置。如果在QA和生產數據庫中索引計劃不同,那麼執行性能測試將毫無意義。

數據庫的源代碼控制有兩種基本類型,我們將其稱爲“全模式”和“變更腳本”。

全模式源碼控制

“全模式”源碼控制指的是源代碼控制與你所希望的數據庫的外觀看起來十分相似。在使用此模式時,可以看到所有的表和視圖都按照預期的樣子排列,這讓你無需部署數據庫就能夠更容易理解它。

SQL Server的SQL Server Data Tools(SSDT)就是全模式源碼控制的一個例子。這個工具可以通過CREATE腳本的形式表示所有數據庫對象。當想要用SQL創建一個新的對象,只需將最終腳本直接粘貼到處於源代碼控制下的數據庫項目中即可,這就很方便了。

全模式源碼控制的另一個例子是實體框架遷移(Entity Framework Migrations)。在這個案例中,數據庫主要通過C#/VB類的方式而非SQL腳本的方式表現。但同樣,可以通過瀏覽源代碼獲得對數據庫的整體認識。

在使用全模式源碼控制時,通常不需要直接編寫遷移腳本。部署工具通過將數據庫的當前狀態與處於源代碼控制中的理想版本進行比較來確定需要進行哪些更改。這可以讓你快速完成數據庫變更並看到結果。在使用這種類型的工具時,我很少直接更改數據庫,而是使用工具完成大部分工作。

有些情況下,只有工具是不夠的,即使包含部署前和部署後腳本也是如此。在這種情況下,生成的遷移腳本必須由數據庫開發人員或DBA手工修改,這可能會破壞持續部署計劃。這種情況通常發生在對錶結構進行重大變更時,因爲在這些情況下生成的遷移腳本效率可能較低。

全模式源碼控制的另一個優點是它支持代碼分析。例如,如果列的名稱被更改,但在視圖中未做出相應更新,SSDT將返回一個編譯錯誤。就像應用程序設計語言中的靜態類型一樣,可以捕獲大量錯誤,並且能夠對部署有明顯錯誤的腳本提前防範。

變更腳本源碼控制方式

另一種源代碼控制方式就是變更腳本源碼控制。這種方式不存儲數據庫對象本身,而是存儲創建數據庫對象所需的步驟列表。我曾經成功使用的是Liquibase數據庫,總的來說這類工具的工作機制都差不多。

變更腳本工具的主要優點是可以完全控制最終遷移腳本的樣貌。這使得複雜變更的執行更加容易,例如表的拆分或合併。

不幸的是,這種類型的源代碼控制也存在一些缺陷。首先是需要編寫變更腳本。雖然它的控制力度更強,但同樣也更費時。相比於手寫ALTER TABLE腳本,爲C#類添加一個屬性要容易得多。

當使用SchemaBinding之類的功能時,會讓問題更加嚴重。對於不熟悉這個術語的人,可以將其理解爲,通過鎖定表的模式啓用SQL Server中的一些高級特性。如果要變更表或視圖的設計,必須首先將SchemaBinding從任何與該表或視圖有關的視圖中移除。如果這些視圖被其他視圖模式綁定(schema-bound),那麼這些視圖同樣也需要臨時解除綁定。雖然對於全模式源代碼控制工具來說,生成所有的樣板很容易,但是想要用手工的方式正確地生成它們還是一項相當困難的工作。

變更腳本工具的另一個缺點是它們往往難以理解。例如,如果你希望在不部署表的情況下了解某個表中列的信息,則需要查閱所有涉及該表的變更腳本。這就很容易錯過某些信息。

該如何選擇源代碼控制模型?

從頭開始一個新的數據庫項目時,我會選擇全模式源代碼控制。這種模式會讓開發人員工作更加高效並且只要有一名人員完成設置工作,之後只需要很少的知識就可以正常使用。

對於現存的數據庫,特別是那些已經存續多年的生產環境數據庫,通常選擇變更腳本源代碼控制模式更加合適。全模式工具會對數據庫設計做出某些特定的假設,而更改腳本工具則是通用的。此外,全模式工具構建難度更大,對於某些特殊的數據庫來說,可能根本不可用。

數據管理

根據表中所含數據的性質,表可以被廣泛地分類爲“管理表”、“用戶表”或“混合表”。根據表所屬的類別不同,處理這些表的方式也是不同的。

管理表

將數據庫置於源代碼控制之下的一個常見錯誤是遺忘數據。總有一些“查詢表”保存着用戶不打算修改的數據。例如,其中可能包含表驅動的業務規則邏輯、狀態機的各種狀態碼,或者僅僅是與應用程序代碼中的枚舉類相匹配的鍵-值對列表。

這類表中的數據應該被視爲源代碼一樣對待。對這類數據的變更需要經過與其他代碼變更相同的評審和QA過程。特別重要的是,爲確保這些變更不會被遺漏,這些變更的部署應該與其他應用程序和數據庫部署一併自動完成。

在SQL Server數據工具中,我使用部署後腳本處理此問題。在這個腳本中,我將期望數據填充到一張臨時表中,然後使用MERGE語句更新實際表。

如果源代碼控制工具無法很好地處理這個問題,還可以通過構建獨立工具來執行數據更新。重要的不是如何做,而是這個過程是否易用並且可靠。

用戶表

用戶表指的是用戶可以添加或修改數據的表。因此,這包括可以直接修改的表(例如名稱和地址)和通過操作間接修改的表(例如貨單收據、日誌)。

真實的用戶數據基本不會被直接加入源代碼控制中,不過,爲開發和測試提供逼真的樣本數據也是一種最佳實踐。這些數據可以直接存儲在數據庫項目中,處於源代碼控制之下的其他地方,如果特別大,也可以保存在獨立的共享文件中。

混合表

混合表指的是即存儲管理數據也存儲用戶數據的表。有兩種方法可以對其進行分區。

列分區是指用戶可以修改某些列,但不能修改其他列。在這種場景下,可以將該表視爲有額外限制的普通管理表,用戶控制的列永遠不會被更新。

行分區指的是某些記錄用戶無法修改的情況。我曾經遇到的常見的場景是需要在用戶表中對某些值進行硬編碼。在較大型的系統中,對於每個可以獨立於任何真實用戶進行更改的微服務,可能都有一個獨立的用戶ID。例如,可能是一個被稱爲“銀行數據導入器”的用戶。

在我看來,管理行分區混合表的最佳方法是通過保留鍵的方式。當定義identity/auto-number列時,將初始值設置爲1,000,通過源代碼控制對編號從1到999的用戶ID進行管理。這需要數據庫允許手動設置identity列中的值。在SQL Server中,是通過SET_IDENTITY_INSERT命令完成的。

處理此場景的另一個選擇是使用名爲“SystemControlled”的列或者能夠達到類似效果的方法。當設置爲1/true時,表示應用程序不可直接修改。如果設置爲0/false,則部署腳本會將其忽略。

個人開發數據庫

將模式和數據置於源代碼控制之下,就可以進行下一步並着手設置個人開發數據庫。正如每個開發人員都應該能夠運行自己的web服務器實例一樣,有可能對數據庫設計做出修改的每個開發人員都需要能夠運行自己的數據庫副本。

這一規則經常會被打破,這對開發團隊是相當不利的。在共享環境上做變更的開發人員一定會相互干擾。他們甚至可能會陷入“部署之爭”,每個開發人員都試圖將更改部署到數據庫。當使用全模式工具執行此操作時,開發人員將交替地恢復其他開發人員的變更,甚至都不會意識到這一點。在使用變更腳本工具的情況下,數據庫則可能處於不確定狀態,遷移腳本可能無法正常使用,需要從備份中重新恢復。

另一個問題是模式漂移。這時,開發數據庫與處於源代碼控制下的內容不再匹配。隨着時間的推移,開發數據庫會逐漸積累越來越多的非生產表、測試腳本、臨時視圖和其他垃圾信息需要清除。當每個開發人員都有自己的數據庫時,這樣做要容易得多,因爲他們可以隨時重置數據庫。
最後也是最重要的問題是,服務開發人員和UI開發人員需要穩定的平臺來編寫代碼。如果共享數據庫不斷變化,他們就無法有效地工作。在我曾經工作過的一家公司,很少看到開發人員大喊“服務又宕機了!”,然後玩一個小時視頻遊戲,等待共享數據庫重新組裝起來。

共享的開發和集成數據庫

對於共享的開發人員數據庫或集成數據庫來說,首要原則就是不能對數據庫直接進行修改。更新共享數據庫的唯一方法就是通過構建服務器以及持續集成/部署流程完成。這不僅可以防止模式漂移,還可以通過有計劃的更新減少中斷的次數。

根據經驗,我會在相關分支進行代碼檢入時同步更新共享開發人員數據庫。這可能會造成中斷,但通常是處於可控範圍的。在進行集成之前,您確實需要先驗證部署腳本的正確性。

對於集成數據庫而言,我更傾向於每日安排一次部署,與服務的部署次數相同。這樣能夠爲UI開發人員提供一個相對穩定的工作平臺。對於UI開發人員來說,沒有什麼比不知道突然開始失敗的代碼是他們的錯誤還是服務/數據庫中的問題更令人沮喪的了。

數據庫安全和源代碼控制

在數據庫管理中,安全性是一個經常被忽略的方面。具體來說,就是哪些用戶和角色可以訪問哪些表、視圖和存儲過程。在最糟糕的情況下,應用程序可以獲得對數據庫的完全訪問權限。它可以對每個表的每一列進行讀寫操作,甚至是那些與其沒有業務關聯的列。數據泄漏經常是由於實用小程序的訪問權限遠遠超過其所需的訪問權限而造成的。

鎖定數據庫的主要反對意見是,“我們無法預知什麼會崩潰”。因爲以前從未被鎖定過,所以開發人員根本不知道應用程序實際需要的權限是什麼。

解決這一問題的辦法是從第一天起就將權限控制放入源代碼控制中。這樣,當開發人員測試應用程序時,如果權限不正確,一開始就會失敗。這反過來又意味着,當到達QA階段時,所有權限問題都已解決,不存在權限缺失的猜測或風險。

容器化

根據項目的性質不同,數據庫的容器化是一個可選步驟。我將通過兩個案例說明箇中原因。

對於第一個案例,這個項目有一個非常簡單的分支結構:有一個“dev”分支,它會導入QA分支,QA分支又會導入階段化分支和最終的生產分支。這可以通過四個共享數據庫實現,管道中的每個階段,各使用一個數據庫。

在第二個案例中,我們有一組重要的特性分支。每個重要的特性分支被進一步細分爲開發和QA分支。每個特性必須通過QA檢驗,才能夠成爲合併到主分支的候選特性,因此每個重要特性都需要有各自的測試環境。

在第一個案例中,即使web服務確實需要容器,容器化也可能是浪費時間。對於第二個用例,容器化某種程度上則是至關重要的。如果沒有真正的容器(例如Docker),那麼在創建新的重要特性分支時,至少可以根據需要生成新環境的部署腳本(例如AWS或Azure)。

關於作者

Jonathan Allen在90年代後期開始爲一家醫療診所開發MIS項目,逐步將該項目從Access和Excel升級成企業級的解決方案。在花了五年時間爲金融業編寫自動化交易系統之後,他成爲了多個項目的顧問,其中包括自動化倉庫的UI、癌症研究軟件的中間層,以及一家大型房地產保險公司的大數據需求。在空閒時間,他喜歡研究和記錄源於16世紀的武術。

查看英文原文: How to Source Control Your Databases for DevOps

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章