[!TIP]
Laravel
在领域驱动的实现转载请注明出处:https://janrs.com/sc6z
Laravel实现DDD
前言
[!TIP]
优秀的架构要做到的是,跟上级和老板沟通好未来的业务体量,市场情况,公司规模。然后根据现下的公司情况、人员情况、资金情况、项目落地周期等进行综合考虑和权衡。
能够站在业务的角度,加上有技术的拖底,能够从大局的角度,在业务发展的每个阶段适配技术架构,以及尽可能保证后期不需要重构,技术和架构能够随着业务的开展平滑升级,减少技术负债。
-
架构设计核心目标是支持业务,有些时候不合理的存在是合理的。
-
应用架构存在的首要目标是支持业务,很多成长性企业或初创公司面对生存的压力,不能为了保证架构的合理性而拖延系统实施速度导致企业错过发展时机。这种情况在互联网型企业更为常见。
-
业务还在试错期,系统需要尽快保证支持业务试错,如果一上来就谈论整体架构的合理性,很可能花费巨大成本实现了合理架构后,新业务已经取消或失败。
在创业初期,别尽是整一套高大上的超级架构。超级架构得有业务量,人员,成本,时间的支撑。
如果架构师一味的为了架构而架构,脱离公司的实际情况,脱离了当下业务的实际情况,那么整出来的架构就是空中阁楼。
或许能够整出一套满足和适配未来业务的架构,但是市场早已过了需求期,此时的架构也是一文不值了。
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/
这样的目录结构设计没有任何问题。 而当我们的业务逻辑稍微复杂一点时,我们通常会用 Repositories、Transformers 等这些个文件夹来划分。
跟下面这个差不多:
|- 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
看上去也不是那么糟糕对吧!这里面也建立了一个文件夹 Services
专门处理所有的业务逻辑。
还有 Repositories
、Transformers
、Validators
这些有着差不多相同的类的文件夹。
很多人也觉得这样的设计很不错,并且乐于这样去设计。
不过完成仅仅只是单个实体 / 模型的工作需要浏览不同的文件夹和文件,即操作很多类,写各种接口,光是这一点让一些开发者觉得很麻烦。
[!NOTE]
但问题的关键不在于用不同的文件夹去划分,而是开发者维护代码和服务之间的通信。
也就是架构师要慢慢站在业务的角度来设计架构
Laravel的SOA
[!NOTE]
SOA
:即面向服务架构
问题的出现
分析下前面的代码结构可以看到:
- 这是一个 庞大 的应用程序
- 对于一些开发人员来说,很难 维护
- 生产力 低(在考虑系统内部连接上需要耗费时间)
- 代码的 规模调整 也是一个问题
解决方案
解决方案是显而易见的 —— 微服务
即使我们使用 SOA(面向服务架构),我们还是需要将我们的庞大的应用分解成较小的独立的部分,以便日后将其分开扩展。
照理来说这个解决方法挺好的。但现实中我们并没有这么做。因为对于将代码分解成更小的部分这件事情,说的通常比做的要容易得多得多。
通常分离服务需要两个简单的步骤:
- 将子服务(
Models
、Repositories
、Transformers
等)移动到新的PHP
微服务应用程序中 - 重新确认服务函数调用的目标确确实实指向到新的微服务中(例如,创建
HTTP
请求)
然后查找与该服务相关的所有文件。会很惊奇地发现,我们可能有部分代码不经过服务而直接使用了它的模型或存储库。
而这还不是唯一的问题,我们甚至可以总结一下:
- 有太多的文件要考虑
- 犯错误的机会很高
- 容易让开发者沮丧
- 有时需要重新考虑域名之间的逻辑
- 新的开发者?还是洗洗睡吧!
最后一个原因非常重要,因为新的开发人员很难在短时间内掌握整个应用程序。
而通常项目经理不会给他太多的时间去研究。这容易产生给内置对象扩展方法、代码放在错误的位置、甚至会让下一个新的开发人员更加困惑等问题。
幸运的是,我们已经有一个解决方案 —— HMVC
。
即将整个应用程序分成较小的部分,每个部分都有自己的文件和文件夹,如 app/
文件夹,并通过 composer.json
自动加载,如下所示:
[!NOTE]
但当我们想要将特定模块移动到微服务中时,HMVC
让事情变得复杂起来。
因为我们还是需要在主要代码库中保留控制器、中间件等。大多数时候,将代码移动到微服务会需要重新定义路由和控制器。
这里面有太多不必要的工作,正常的开发者都只想分开那些不得不分开的东西。因此不是很推崇这种目录结构。
|- 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/
Laravel的DDD
引入DDD设计
[!NOTE]
正如开头所说的:
优秀的架构要做到的是,能够从大局的角度,在业务发展的每个阶段适配技术架构,以及尽可能保证后期不需要重构,技术和架构能够随着业务的开展平滑升级,减少技术负债。
结合Laravel
这一个优秀的框架,可是很好的在项目初期以及随着业务的发展,做到一个相对良好的权衡。
DDD
在Laravel
中,将应用程序构建成4个部分:
- 应用程序(
Application
) —— 掌管 控制器、中间件、路由 - 领域(
Domain
)—— 掌管业务逻辑Model
、Repository
、Transform
、Policy
等 - 基础设施(
Infrastructure
) —— 掌管Logging
、Email
等常见服务 - 接口(
Interface
) —— 掌管View
、lang
、assets
对应的目录结构:
|- 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() {
...
}
...
DDD分离
我们的应用程序变得越来越大时,我们希望将 Account
相关的内容分开到单独的微服务中并将其转换为 OAuth
服务器。
所以,我们只是需要移动以下部分
|- Account/
|- Console/
|- Exceptions/
|- Events/
|- Jobs/
|- Listeners/
|- Models/
|- User.php
|- Role.php
|- Permission.php
|- Repositories/
|- Presenters/
|- Transformers/
|- Validators/
|- Auth.php
|- Acl.php
如果是将应用程序搬到 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.php
和 Acl.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/
发表回复