大规模 Laravel 应用

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/

领域驱动设计

本文不再对领域驱动设计做过多介绍,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.phpAcl.php 是内部的服务文件 app/Account/ 夹。控制器将仅访问这两个类并调用其函数。其他类(域之外)将不会访问到app/Account/ 文件夹中的其他剩余类 。这些服务中的功能将只接受基本的PHP数据类型,例如 arraystringintbool 和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 命令在分离过程中来自动生成文件 . 完成后我讲在这里放上包的链接.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容