在 Laravel 应用程序之间共享数据库
导言
如果你刚好有在 Twitter 上面关注我 ,你可能会看到我在 Twitter 上谈论一些我工作中一些日常的工作内容。我们有一个面向客户的会员信息管理和一个内部客户关系管理(CRM),并且他们都在同一个主数据库中同时运行。
客户关系管理(CRM)是在我来现有的雇主这里工作前就创建好了的,而会员信息管理是我在 2017 年早期作为外包开发商时构建的。会员信息管理本身就是一个全新的 Laravel 应用程序,而客户关系管理(CRM) 则是一个完全定制的软件。
作为外包商,我有一个数据库的不完整副本,并且我设法从数据库模式中反向工程 Eloquent 模型,创建 模型工厂 ,以便能够为会员应用编写测试。
在 2017 年年底,我们开始将我们的 CRM 迁移到 Laravel,以便对代码库进行一些现代化改造,为其提供一个标准结构,并且可以轻松地对其进行更改。现在我们有两个 Laravel 应用程序,我们开始研究如何在它们之间共享数据。
Eloquent 模型
数据库模型是最容易处理的部分。为此,我们使用 Composer 为每个共享数据库表创建模型创建一个包,并将它们作为 vcs 存储库 。这使我们能够无需通过 Packagist 发布就可以共享这些模型。
这个包中的模型每个都从它们自己的基础模型扩展而来,它为每个数据库设置连接,并包含可以将它们连接在一起的最少量的逻辑。
我们试着让包模型仅仅包含互相之间的关系,和一些通用的方法和行为。思路就是,每个使用它们的应用程序都会根据需要扩展它们并实现它们自己的特定逻辑。
迁移
最开始,迁移有点棘手。虽然我们有一个,更确切的说属于 CRM 的数据库,但这些迁移应该可用于所有需要访问该数据库的应用。那么问题来了:“哪个应用负责管理数据库表结构(Schema)呢?”
Laravel 在 config/database.php
文件中可以添加多个数据库连接,同时也支持多种数据库驱动。我们只使用 mysql
驱动来定义多个连接。
我对管理数据库模式有以下要求:
- 使用数据库的单个应用不应该负责管理迁移
- 迁移应该可以用于测试
- 如果可能,我们希望使用 Laravel 的迁移功能
如果我们能够通过某种方式解决第三个要求,那么前两个要求就相当简单了。
独立功能
我花了很长的时间把一个类似 Artisan 的 工具 Nomad 整合进来,它专注于解决迁移和数据填充功能。Nomad 可以像 Vagabond 一样整合进一个单独的 Composer 项目来管理应用的数据库迁移。
Vagabond 应用可以像一个 VCS 仓库一样引入到你的应用中,并使用服务提供者来指示 Laravel 加载这些迁移,以及使用应用中可能存在的任何迁移。
// Vagabond 项目中的服务提供者
public function boot()
{
$this->loadMigrationsFrom(dirname(__FILE__).'/../database/migrations');
}
在实践中的 Nomad
我们在 Nomad 的方法中遇到的第一个问题是:如果你不在迁移文件中明确指出运行的迁移连接,那么它们将会运行在你的默认连接上。
// In your migration file
// 在你的迁移文件中
public function up()
{
Schema::connection('the_connection')->create('table', function (Blueprint $table)
{
//
}
}
第二个问题是,尽管 Laravel 应用将会对正确的连接运行迁移,如果你对三种不同的连接运行迁移,那么它将会对默认连接的迁移进行追踪,它们造成的迁移历史都将会被记录到你的应用程序中的 migrations
表中
为什么是这个问题?如果你的数据库用户有足够的权限,那么它将在你的数据库中反复执行相同的迁移任务,而数据库中这些表已经存在了。
如果你有许多不同的应用程序使用集中迁移文件,并且尝试在同一时间进行迁移,那么问题将会更加复杂。
为了解决这个问题,我们在迁移项目的 “数据库/迁移文件” 中为每个连接的迁移创建了文件夹。
database/migrations/
/crm
/gis
/coverage
通过这样做,我们可以为各种迁移命令使用 path
和 database
参数,允许我们显示的对每个连接运行迁移: php nomad migrate --database=gis --path=database/migrations/gis
。 这样确保只运行 gis
迁移, 同时迁移的历史记录被记录到 gis
数据库的 migrations
表中。
这样就解决了需求 1 和 3; 我们现在使用的是独立存储的 Laravel-style 的迁移,并且我们有一个可以运行迁移的独立的应用程序。 这就意味着我们可以对于在代码 a) 访问数据库服务器, b) 拥有足够权限的用户的任何地方为特定的数据库连接运行迁移。
在测试中使用模型迁移
还有一个问题就是我们在测试的时候。
在测试环境中,我们可以使用 Laravel 的 RefreshDatabase
辅助类, 它会自动的创建和删除数据库。但是在创建的时候,虽然它正确的运行了所有迁移文件,但是只会移除默认数据库上的表。
这就意味着当我们对使用共享数据库的运用程序进行测试时,每次都会测试失败,因为 Laravel 会尝试运行没有被移除的迁移。这儿有一个来自于 Sepehr Lajevardi 的 解决方法 ,最早是 Keith Damiani 提出来的。
Sepehr 的方案会覆盖 Laravel 中默认的 refreshTestDatabase
方法,其中有一个属性包含了所有要移除的表。
数据库配置
现在,您的模型和迁移都被打包在自己的存储库中,那么最后一个不需要手动从项目复制到项目的就是是配置本身了。
Laravel 实际上很容易将第三方软件包的配置合并到主配置中。
在我们的生产应用程序中,我们的数据库配置绝对没有配置连接。
相反,此功能存在于每个数据库连接服务的提供者内部。我们有一个顶级提供者,每个提供者都从中扩展,默认情况下,每个提供者只需要定义一个受保护的属性即可; $connectionName
.
您可以在这里看到该功能的独立示例 here.
我们所需要做的就是将服务提供者添加到您的应用程序中的 config/app.php
文件的数组里,并且为每个链接定义所需要的环境变量。
持续集成
对于我们来说最后的难题就是要在 CI 管道中进行测试,于我们而言,这是 BitBucket。
由于我们现存的数据库中含有很多的枚举类型的
字段(我并不推荐使用它们,尤其是因为库不支持它们 - 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_db
和 third_db
两个数据库。
MYSQL_ROOT_PASSWORD
变量被定义成一个静态的字符串,是因为我无法弄清楚如何在部署步骤中注入一个随机密码,但如果你知道怎么做,请告诉我吧!
结论
如果你曾经发现自己需要与共享两个或多个数据库的应用程序一起工作,我希望你会在这篇文章中了解到有关管理和工作的一些知识。
具体包括以下几点:
- 包模型和迁移文件在一个独立的 Vagabond 项目中
- 作为一个独立的应用程序与 Nomad 运行迁移文件
- 在测试中处理多个数据库连接
- 使用多个数据库的 BitBucket Pipelines 成功的运行你的测试
由于应用程序与数据库的分离,我们必须考虑的一个因素是迁移应该如何以及何时运行,因为我们现在需要将其作为单独的操作来完成。它当然会根据具体情况而有所不同,我们需要确保针对每个应用程序进行测试,以确保不会对数据库引入重大更改。
我花了好几个月的时间才达到这样的工作状态,因此,我希望将来如果你与我遇到了同样的问题的话,我可以帮你帮你节约一些时间!
感谢 Keith Damiani 和Sepehr Lajevardi ,他们为我指出了最后的问题,
Jake Bennett 和我在《北方与南方》播客的第 43 集(http://www.northmeetssouth.audio/43) 讨论了这种迁移行为。
如果你对本文所涉及到的内容有任何的疑问,或者有什么建议,可以随时在 Twitter 上联系(https://twitter.com/michaeldyrynda)。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: