中大型项目架构
Laravel 的初学者分为两种:
一种是乖乖的将程序在写 MVC 架构内,这将导致 Controller 和 Model 非常臃肿,日后代码很难维护。
一种是不知道将程序写在哪一个Class内而犹豫不决。
本文整理出最适合 Laravel 的中大型项目架构,兼具易维护、易扩展、易复用和易测试的特点。
Controller 过于肥大
受RoR的影响,初学者常认为 MVC 架构就是 Model,View,Controller:
Model 就是数据库。
Controller 负责与 HTTP 通信,调用 Model 与 View。
View 就是 HTML。
假如按照此定义,以下需求应该写在哪里呢?
1.发送 Email,使用外部 API。
2.使用 PHP 写的逻辑。
3.依需求将显示格式作转换。
4.依需求是否显示某些资料。
5.依需求显示不同资料。
其中1, 2 属于商业逻辑,而3, 4, 5 属于显示逻辑,若依照一般人对 MVC 的定义,Model 是数据库,而 View 是HTML,以上这些需求都不能写在Model 与View,只能勉强写在Controller。
因此初学者开始将大量程序写在 Controller,造成 Controller 的肥大难以维护。
Model 过于肥大
既然逻辑写在 Controller 不方便维护,那我将逻辑都写在 Model 就好了?
当你将逻辑从 Controller 搬到 Model 后,虽然 Controller 变瘦了,但却肥了 Model,Model 从原本只处理数据库逻辑,现在变成还要负担商业逻辑与显示逻辑,结果更惨。
Model 代表数据库吗?把它想成是 Eloquent class就好,数据库逻辑应该写在 repository 里,这也是为什么 Laravel(Lumen) 已经没有 Models目录,Eloquent class 仅仅是放在 app 根目录下而已。
中大型项目架构
那我们该怎么写呢?别将我们的思维局限在 MVC 内 :
Model : 仅当成 Eloquent class。
Repository : 辅助 Model,处理数据库逻辑,然后注入到 service。
Service : 辅助 Controller,处理商业逻辑,然后注入到 Controller。
Controller : 接收 HTTP request,调用其他 service。
Presenter : 处理显示逻辑,然后注入到 View。
View : 使用 blade 将资料 binding 到 HTML。
其中蓝色为原本的 MVC,而紫色为本文要介绍的的重点 : Repository 模式,Service 模式与 Presenter 模式。
箭头表示物件依赖注入的方向。
我们可以发现 MVC 架构还在,由于 SOLID 的单一职责原则与依赖反转原则 :
我们将数据库逻辑从 Model 分离出来,由 repository 辅助 Model,将 Model 依赖注入进 repository。
我们将商业逻辑从 Controller 分离出来,由 service 辅助 Controller,将 service 依赖注入进 Controller。
我们将显示逻辑从 View 分离出来,由 presenter 辅助 View,将 presenter 依赖注入进 View。
建立目录在 src 的 Bundle 目录下建立 Repositories,Services 与 Presenters 目录。
别害怕在 Laravel 预设目录以外建立的其他目录,根据 SOLID 的单一职责原则,class 功能越多,责任也越多,因此越违反单一职责原则。
所以你应该将你的程序分割成更小的部分,每个部分都有它专属的功能,而不是一个 class 功能包山包海,也就是所谓的万能类
。
所以整个项目不应该只有 MVC 三个部分,放手根据你的需求建立适当的目录,并将适当的 class 放到该目录下,只要我们的class 有namespace 帮我们分类即可。
Repository
由于篇幅的关系,将 repository 独立成专文讨论,请参考如何使用 Repository 模式 ?
Service
由于篇幅的关系,将 service 独立成专文讨论,请参考如何使用 Service 模式?
单元测试
由于现在Model、View、Controller 的依赖都已经拆开,也都使用依赖注入,因此每个部分都可以单独的做单元测试,如要测试service,就将repository 加以 mock,也可以将其他 service 加以 mock。
结论
本文谈到的架构只是开始,你可以依照实际需求增加更多的目录与 class,当你发现你的 MVC 违反 SOLID原则时,就大胆的将 class 从 MVC 拆开重构,然后依照以下手法 :
建立新的 class 或 interface。
将依赖类依赖注入到 class。
在 class 内处理他的职责。
将 class 或 interface 注入到 Controller 或 View。
最后搭配单元测试,测试重构后的架构是否与原来的需求结果相同。
Last updated