大规模 Laravel 应用

2,069 阅读7分钟

文章转发自专业的Laravel开发者社区,原始链接:learnku.com/laravel/t/3…

(如果你只想查看解决方案, 单击此处 )

Laravel是迄今为止最流行的PHP框架,目录结构明确清晰,语法优雅。在中小型项目中使用Laravel提供的默认目录结构效果是非常好的,但是当有一个超过50个模型的大型应用,代码库可能就有点让人窒息。

维护一个大型的应用程序并不简单,尤其是当组织错乱,而Laravel的默认结构对这样的场景肯定没有很大帮助。

首先,我们看看Laravel默认结构以及它对大型应用程序的影响。

Laravel的默认应用程序结构:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Providers/
   |- User.php
|- database/
   |- factories/
   |- migrations/
   |- seeders/
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

结构没什么问题。但是,当我们在大型应用程序中工作时,我们通常将业务逻辑划分为存储库、转换器等……如下所示:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

这显然是一个结构良好的Laravel项目。现在,让我们看看models文件夹:

|- app/
  |- Models/
     |- User.php
     |- Role.php
     |- Permission.php
     |- Merchant.php
     |- Store.php
     |- Product.php
     |- Category.php
     |- Tag.php
     |- Client.php
     |- Delivery.php
     |- Invoice.php
     |- Wallet.php
     |- Payment.php
     |- Report.php

我们还需处理所有业务逻辑的服务。还有Repositories, Transformers, Validators文件夹,其中包含相同或更多的类。但是,处理单个Entity/Model需要浏览不同的文件夹和文件。

问题不在于浏览不同的文件夹,而在于维护代码和服务之间的通信。

分析代码库,我们发现:

  • 它是一个整体 应用程序
  • 对于开发人员来说很难 维护
  • 开发效率 低 (需要时刻考虑相互联系)
  • Scaling 存在问题

解决方案显而易见-微服务。即使我们使用soa(面向服务的体系结构),我们仍然必须将我们的整体应用程序分解成更小的独立部分来分别扩展它们。

通常分离服务需要两个简单的步骤:

  • 将服务的子程序 (Models, Repositories, Transformers etc.) 移动到一个新的较小的php微服务应用程序中。
  • 重新设置服务函数调用,将目标重定向到微服务(例如:创建http请求).

现在需要找到与该服务相关的所有文件,你可能会发现通过绕过该服务而在代码中的其他地方使用了它的模型或存储库。总结一下可能遇到的问题:

  • 要考虑的文件繁多
  • 犯错的几率很高
  • 产生烦躁
  • 有时需要重新考虑域逻辑
  • 有新的程序员祭天

最后一个原因非常重要,因为新开发人员很难在短时间内掌握整个应用程序。但是,项目经理不会给他太多时间。这会导致胡乱打补丁,错误的代码放置,让下一个新开发人员将更加困惑。

幸运的是,我们已经有了一个解决方案 -HMVC。将整个应用程序分成较小的部分,每个部分都有自己的文件和文件夹(例如 app/ 文件夹),但是HMVC增加了更多的复杂性,并且当我们要将特定模块移入微服务时也是如此。我们仍然需要将控制器,中间件等保留在主代码库中。在大多数情况下,转向微服务需要重新定义路由和控制器。因此,我们必须做多余的工作。因此,我不是这种结构的忠实拥护者。因为,我只想分开我(必须)不必分开的东西。并通过composer.json自动加载方式加载,如下所示:

|- auth/
   |- Exceptions/
   |- Http/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- merchant/
   |- Console/
   |- Events/
   |- Exceptions/
   |- Http/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

但是HMVC增加了更多的复杂性,并且当我们要将特定模块移入微服务时也是如此。我们仍然需要将控制器,中间件等保留在主代码库中。在大多数情况下,转向微服务需要重新定义路由和控制器,我们必须做多余的工作。但是,我只想分开我(必须)不得不分开的东西。

领域驱动设计

本文不再对领域驱动设计做过多介绍,Developerul DeLaUnu 在 这里对DDD进行了很好的描述。

以本文的观点,DDD(可以)将Laravel应用程序分为4部分(或3…  参见) :

  • 应用程序 — 通常包含,控制器,中间件,路由
  • 领域 — 通常包含业务逻辑(模型,存储库,变压器,策略等)
  • 基础架构 —通常拥有常用服务,例如日志记录,电子邮件等
  • 界面 —通常包含视图,语言,资产

如果我们这样构造应用程序并使用命名空间会产生什么结果呢?

|- app/
   |- Http/ (Application)
      |- Controllers/
      |- Middleware/
|- Domain/
   |- Models/
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Services/
|- Infrastructure/
   |- Console/
   |- Exceptions/
   |- Providers/
   |- Events/
   |- Jobs/
   |- Listeners/
|- resources/ (Interface)
   |- assets/
   |- lang/
   |- views/
|- routes/
   |- api.php
   |- web.php

将项目化分成这样的文件夹是行不通的,这意味着我们只添加了一个父命名空间。

解决方案

如下:

|- app/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Providers/
   |- Account/
      |- Console/
      |- Exceptions/
      |- Events/
      |- Jobs/
      |- Listeners/
      |- Models/
         |- User.php
         |- Role.php
         |- Permission.php
      |- Repositories/
      |- Presenters/
      |- Transformers/
      |- Validators/
      |- Auth.php
      |- Acl.php
   |- Merchant/
   |- Payment/
   |- Invoice/
|- resources/
|- routes/

该  Auth.php和  Acl.php 是内部的服务文件  app/Account/ 夹。控制器将仅访问这两个类并调用其函数。其他类(域之外)将不会访问到app/Account/ 文件夹中的其他剩余类  。这些服务中的功能将只接受基本的PHP数据类型,例如  array,  string,  int,  bool 和POPO(Plain Old PHP Object),但没有类的实例。例如:

public function register(array $attr) {
    ...
}
public function login(array $credentials) {
    ... 
}
public function logout() {
    ...
}

需要注意,该  register 函数接收的  array 属性而不是  User 对象。因为另一个正在调用该函数的类不应该知道该User 模型的存在  。这是整个结构的基本规则。

当我们想要分离代码时

我们的应用开始变得庞大然后我们想要将 Account域单独分离成一个微服务,并且将其转化为OAuth服务.

所以,我们只需移动以下部分-

|- Account/
   |- Console/
   |- Exceptions/
   |- Events/
   |- Jobs/
   |- Listeners/
   |- Models/
      |- User.php
      |- Role.php
      |- Permission.php
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Auth.php
   |- Acl.php

到一个新的 Laravel (或者 Lumen) 应用-

|- app/
   |- Http/
      |- Controllers/
      | - Middleware/
   |- Account/
      |- Events/
      |- Jobs/
      |- Listeners/
      |- Models/
         |- User.php
         |- Role.php
         |- Permission.php
      |- Repositories/
      |- Presenters/
      |- Transformers/
      |- Validators/
      |- Auth.php
      |- Acl.php
|- routes/
|- resources/

当然,我们必须在控制器和路由编写代码使其成为一个我们需要的OAuth服务.

但是我们需要在主代码库中修改什么呢?

我们只需要保留Auth.phpAcl.php的服务文件,并将其函数中的代码更改为针对新创建的微服务的 HTTP 的请求(或者其他消息传递的方法)。

...
public function login(array $credentials) {
 // change the code here
}
...

整个应用程序将保持不变. 应用程序结构如下所示 -

|- app/
   |- Console/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Providers/
   |- Account/
      |- Auth.php
      |- Acl.php
   |- Merchant/
   |- Payment/
   |- Invoice/
|- resources/
|- routes/

通过这一较少的努力,您可以将代码的一部分移动到完全独立的微服务中。我无法想象还有其他方法来通过更少的操作将代码的一部分移动到另一个微服务中去.

权衡

就像我之前所说的, 任何事情都需要权衡,这个解决方案也不例外. 这里我们有一个关于迁移的问题! 因为在上面的文件结构中(在分离之前),所有的迁移文件都在放置在database/migrations/目录. 但是当我们想要分离一个域名时,我们也需要识别这些文件并且移动到新的域名下. 这可能是很困难的事情,因为我们没有任何明确的迹象表明哪个迁移文件属于哪个域名. 我们需要知道如何在迁移文件中放置标识符.

该标识符可以是域名前缀. 比如, 我们可以给迁移文件命名为xxxxxxxxx_create_account_users_table.php而不是xxxxxxxxx_create_users_table.php . 如果需要,我们还可以使用account_users 表名代替users. 我更倾向于确定哪些表需要在分离中移动. 分离迁移文件可能有点令人沮丧,但是如果我们使用前缀或者其他类型的标记,这个过程将会变得不那么痛苦.

我仍然在试验这个迁移结构,并计划构建一个 Laravel 包,它将提供 artisan 命令在分离过程中来自动生成文件 . 完成后我讲在这里放上包的链接.