[譯] 在 Laravel 應用程式之間共享資料庫

elliott_zhao發表於2018-06-06

介紹

如果您碰巧在 Twitter 上關注了我,您可能已經看到我發表了一些我正在做的日常工作。我們有一個面向客戶的會員區和一個內部 CRM,它們工作在同一個主資料庫上。

CRM 是在我為現在的老闆打工之前建立起來的,而會員區是我在 2017 年初作為外包商建立的。會員區本身是一個新的 Laravel 應用程式,而 CRM 則是一個完全自定義編寫的軟體。

304/5000 作為外包商,我有一個資料庫的不完整副本,並且我設法從資料庫模式中反向工程 Eloquent 模型,建立工廠,以便能夠為會員應用編寫測試。

在 2017 年年底,我們開始將我們的 CRM 遷移到 Laravel,以便對程式碼庫進行一些現代化改造,為其提供一個標準結構,並且可以輕鬆地對其進行更改。現在我們有兩個 Laravel 應用程式,我們開始研究如何在它們之間共享資料。

Eloquent 模型

資料庫模型是最容易處理的部分。為此,我們使用 Composer 為每個共享資料庫表建立模型建立一個包,並將它們作為 vcs 儲存庫。這使我們能夠無需通過Packagist釋出就可以共享這些模型。

這個包中的模型每個都從它們自己的基礎模型擴充套件而來,它為每個資料庫設定連線,幷包含可以將它們連線在一起的最少量的邏輯。

我們試著讓包模型僅僅包含互相之間的關係,和一些通用的方法和行為。思路就是是,每個使用它們的應用程式都會根據需要擴充套件它們並實現它們自己的特定邏輯。

遷移

遷移是事情開始變得有點棘手的地方。雖然我們有一個技術上由 CRM 應用程式擁有的資料庫,但遷移應該可用於任何將訪問其中的資料的應用程式。那麼問題就變成了:“哪個程式負責管理資料庫模式?”

Laravel 在config/database.php檔案中附帶多個資料庫連線,向您顯示各種驅動的可用性。我們簡單的定義幾個都使用mysql` 驅動程式的連線。

我們對管理資料庫模式有一些要求:

  1. 使用資料庫的任何一個應用程式不應負責管理遷移
  2. 遷移應該能夠用於測試
  3. 如果可能,我們希望使用 Laravel 的遷移功能

前兩個要求相當簡單,假設我們可以通過某種方式解決第三個要求。

獨立的實用程式

我花了很長時間把類似 Artisan 的工具集中在一起,這個工具只關注遷移和資料庫填充功能 —— Nomad。為了管理許多應用程式的資料庫遷移,Nomad 可以被引入獨立的 Composer 專案 - 例如 Vagabond

Vagabond專案隨後被作為一個包,您可以將其作為 VCS 儲存庫使用,並使用服務提供者,指導 Laravel 載入遷移,以及使用它的應用程式中可能存在的所有遷移。

// 在你的 Vagabond 專案的服務提供者中
public function boot()
{
    $this->loadMigrationsFrom(dirname(__FILE__).'/../database/migrations');
}
複製程式碼

Nomad 實戰

我們在 Nomad 路徑中遇到的第一個問題是,如果您沒有在遷移檔案中指定遷移應該執行的連線,它們將全部在您的預設連線上執行。

// 在您的遷移檔案中
public function up()
{
    Schema::connection('the_connection')->create('table', function (Blueprint $table)
    {
        //
    }
}
複製程式碼

第二個問題是,雖然 Laravel 應用程式會在正確的連線上執行遷移,但它會跟蹤預設連線上的所有遷移,即如果您為三個不同的連線執行遷移,則遷移歷史記錄將全部在應用程式預設連線的 migrations 表。

為什麼這是個問題?如果您的資料庫使用者具有足夠的許可權,它將嘗試並在已存在這些表的資料庫上反覆執行相同的遷移。

如果您有許多不同的應用程式都使用集中式遷移檔案,並且每次都嘗試執行相同的遷移,則會出現此問題。

為了解決這個問題,我們在遷移專案的 database/migrations 資料夾中為每個連線的遷移建立了資料夾。

database/migrations/
                   /crm
                   /gis
                   /coverage
複製程式碼

這樣做,我們現在可以為各種遷移命令使用 pathdatabase 引數,使我們能夠顯式執行每個連線的遷移: php nomad migrate --database=gis --path=database/migrations/gis。這確保只執行 gis 遷移,並且在 gis 資料庫的 migrations 表中追蹤執行遷移的歷史記錄。

這現在解決了要求1和3; 我們現在在一個的獨立的資料庫上使用 Laravel 式遷移,並且我們還擁有能夠執行遷移的獨立應用程式。這意味著我們可以在程式碼的任何地方執行執行鍼對特定資料庫連線的遷移 a) 可以訪問到資料庫伺服器,並且 b) 擁有具有足夠許可權的使用者。

在測試中使用共享的遷移和模型

我們遇到的另一個問題是執行測試。

在我們的測試環境中,我們使用 Laravel 的 RefreshDatabase 特徵,它可以智慧地為每個測試建立並刪除整個資料庫。然而,在撰寫本文時,雖然它正確執行所有遷移,但它只會刪除預設資料庫連線上的表

這意味著如果我們對使用自己的資料庫以及共享資料庫的應用程式進行測試,則每次測試都將失敗,因為 Laravel 會嘗試執行未丟棄連線的遷移。對此,Sepehr Lajevardi 有一個解決方案Keith Damiani 為我指出明路。

Sepehr 的建議中提及的的特性使用一個從待刪表的連線陣列中查詢屬性的方法覆蓋 Laravel 的預設 refreshTestDatabase 方法。

資料庫配置

現在你已經將自己的模型和遷移都打包到了自己的倉庫中,這是最後一件你不想做的事情。從專案手動複製到另一個專案,就是配置本身。

Laravel 實際上很容易將第三方軟體包的配置合併到主配置中。在我們的生產應用程式中,我們的資料庫配置中沒有配置任何連線。

相反,這個功能位於每個資料庫連線的服務提供者內部。我們有一個頂級提供程式,每個提供程式都可以擴充套件,預設情況下,每個提供程式只需定義一個受保護的屬性:$connectionName

你可以在這裡看到這個功能的獨立樣例。

您需要在應用程式中執行的操作是將服務提供程式新增到您的 config/app.php 檔案的提供程式陣列中,併為每個連線定義必要的環境變數。

持續整合

這個拼圖給我們留下的最後一塊是讓測試在 CI 管道中執行。對我們來說,是 BitBucket

由於我們現有的資料庫包含很多 ENUM 欄位(我不建議使用它們,尤其是因為它們不受這個庫的支援 —— doctrine/dbal —— Laravel 用於遷移功能),我們必須在我們的測試環境中使用 MySQL。

在CI管道中使用容器可以輕鬆啟動 MySQL 服務,但是,如何配置多個資料庫卻並不是顯而易見的。由於我們使用的 MariaDB 映像不允許指定繫結的埠,因此多個資料庫服務都嘗試偵聽同一個埠(3306),隨後無法啟動,從而導致測試套件失敗。

解決方案非常簡單,只是我之前沒發現:在測試套件執行之前使用 MySQL 客戶端建立資料庫。

你的 bitbucket-pipelines.yml 檔案應該如下所示:

image: php:7.1.15

pipelines:
  default:
    - step:
        deployment: test
        caches:
          - composer
        script:
          - apt-get update && apt-get install -y unzip git mysql-client
          - docker-php-ext-install pdo_mysql
          - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
          - cp -n .env.example .env
          - export DB_USERNAME=root
          - export DB_DATABASE=first_db
          - export DB_PASSWORD=supersecret
          - export DB_SECOND_USERNAME=root
          - export DB_SECOND_DATABASE=second_db
          - export DB_SECOND_PASSWORD=supersecret
          - export DB_THIRD_USERNAME=root
          - export DB_THIRD_DATABASE=third_db
          - export DB_THIRD_PASSWORD=supersecret
          - composer install
          - php artisan key:generate
          - mysql -uroot -psupersecret -h127.0.0.1 -e 'create database second_db; create database third_db;'
          - vendor/bin/phpunit --colors=always -c phpunit.xml
        services:
          - mariadb
definitions:
  services:
    mariadb:
      image: mariadb:5.5
      environment:
        MYSQL_DATABASE: 'first_db'
        MYSQL_ROOT_PASSWORD: 'supersecret'
複製程式碼

export 這幾行為我們的應用程式作用的三個資料庫中的每個資料庫進行配置。我們讓 MariaDB 服務使用 MYSQL_DATABASE 環境變數配置第一個資料庫,然後使用 MySQL 客戶端建立 second_dbthird_db

MYSQL_ROOT_PASSWORD 變數被定義為一個靜態字串,因為我沒弄明白如何把隨機密碼注入部署步驟中,但是如果您知道如何做,請告訴我!

結論

如果您發現自己需要使用共享兩個或更多資料庫的應用程式,我希望您在本文中學到了有關管理和使用它們的知識。

涵蓋如下內容:

  • 打包模型和獨立遷移 Vagabond project
  • 利用 Nomad 把遷移作為獨立應用程式執行
  • 在測試中處理多個資料庫連線
  • 使用 BitBucket Pipelines 在多個資料庫中成功執行測試

由於應用程式與資料庫的分離,我們必須考慮的一個因素是遷移應該如何以及何時執行,因為我們現在需要將其作為單獨的操作來完成。它當然會根據具體情況而有所不同,我們需要確保針對每個應用程式進行測試,以確保不會對資料庫引入重大更改。

我花了幾個月的時間才把它變成了工作狀態,所以我希望在未來的某個時候,如果你遇到和我相似的情況,我能夠為你節省一些時間!

感謝 Keith DamianiSepehr Lajevardi 指出我拼圖中缺失的最後一塊。

Jake Bennett 和我在North Meets South網路播客的 episode 43 中討論了這種遷移行為。

如果您對本文中涵蓋的任何內容有任何疑問,或有任何改進建議,請隨時在 Twitter 上提出


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章