本文目录导读:

在 Laravel 项目的模块划分上,强烈建议按业务划分,而不是按技术划分,这是大型项目保持可维护性和可扩展性的关键原则。
下面详细解释原因、对比两种方式,并给出最佳实践建议。
按技术划分(不推荐)
这是许多初学者或小型项目常见的结构,所有文件按“是什么”来归类。
示例结构:
app/
├── Http/
│ ├── Controllers/
│ │ ├── UserController.php
│ │ └── OrderController.php
│ └── Requests/
│ ├── StoreUserRequest.php
│ └── StoreOrderRequest.php
├── Models/
│ ├── User.php
│ └── Order.php
├── Services/
│ ├── UserService.php
│ └── OrderService.php
└── ...
问题:
- 高耦合: 一个业务功能(如“用户注册”)的代码分散在多个目录中,难以定位和修改。
- 难以复用: 如果想将“订单”模块移植到另一个项目,需要手动提取多个目录中的文件。
- 扩展困难: 当项目变大,目录层级越来越深,查找文件变得困难。
- 团队协作冲突: 多人同时修改同一个
Controllers目录下的不同文件,虽然不会直接冲突,但逻辑上容易混乱。
按业务划分(推荐)
将每个独立业务单元(如用户、订单、支付)相关的所有代码放在一个模块目录下。
示例结构(使用 Laravel 的 Modules 或 Domains 目录):
app/
└── Modules/
├── User/
│ ├── Controllers/
│ │ └── UserController.php
│ ├── Requests/
│ │ └── StoreUserRequest.php
│ ├── Models/
│ │ └── User.php
│ ├── Services/
│ │ └── UserService.php
│ ├── Routes/
│ │ └── web.php
│ └── Providers/
│ └── UserServiceProvider.php
├── Order/
│ ├── Controllers/
│ │ └── OrderController.php
│ ├── Requests/
│ │ └── StoreOrderRequest.php
│ ├── Models/
│ │ └── Order.php
│ ├── Services/
│ │ └── OrderService.php
│ ├── Routes/
│ │ └── web.php
│ └── Providers/
│ └── OrderServiceProvider.php
└── ...
优点:
- 高内聚: 一个模块包含了完成该业务所需的所有组件(Controller、Model、Service、Route、Event、Listener等)。
- 独立性: 每个模块可以独立开发、测试、部署,甚至可以由不同团队负责。
- 易于复用: 整个
User模块可以轻松复制到另一个项目。 - 清晰导航: 查找“用户”相关代码,直接进入
Modules/User目录即可。 - 支持上下文边界: 可以防止模块之间的内部实现细节互相泄露(订单模块不应直接调用用户模块的模型,而应通过接口或事件通信)。
如何选择:什么时候用业务,什么时候用技术?
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 小型项目(1-2个开发者) | 按技术划分(简单) | 项目规模小,复杂度低,按技术划分更直观、快速。 |
| 中型/大型项目(多人团队) | 按业务划分 | 模块化、高内聚、低耦合,便于并行开发和维护。 |
| 微服务架构 | 按业务划分(独立仓库) | 每个业务模块直接是一个独立的服务。 |
| 共享基础设施(如日志、审计) | 技术型模块(如 Core) |
将通用组件放在一个 Core 或 Support 模块中,被其他业务模块引用。 |
最佳实践:结合 Laravel 特性
-
使用
nwidart/laravel-modules扩展包:这是 Laravel 中最流行的模块化扩展包,它帮你创建Module目录,并自动处理命名空间、路由、配置等。composer require nwidart/laravel-modules php artisan module:make User php artisan module:make Order
-
或手动创建
app/Domains/结构:不想用第三方包,可以手动在app下创建Domains目录,并手动调整composer.json的autoload配置。// composer.json "autoload": { "psr-4": { "App\\": "app/", "App\\Domains\\User\\": "app/Domains/User/" } } -
模块内部结构灵活:每个模块内部可以继续按技术划分(Controllers, Models, Services),但模块之间必须按业务划分。
-
模块间通信:
- 通过接口/契约:为模块定义接口,其他模块通过接口调用,而不是直接依赖具体实现。
- 通过事件/监听器:模块发出事件,其他模块监听处理(解耦)。
- 通过 Facade/Service Container:依赖注入时,绑定到接口。
常见问题及解决方案
问:两个模块需要共享同一个 Model 怎么办?
答: 这表明模型可能不属于某个单一模块,而是核心实体,解决方案:
- 将共享 Model 放在
Core或Shared模块中。 - 或者将 Model 作为“聚合根”,只在一个模块中定义,其他模块通过接口或服务来访问。
问:模块之间的 Controller 名冲突怎么办?
答: 使用 Laravel 的命名空间自动解决。App\Modules\User\Controllers\UserController 与 App\Modules\Order\Controllers\OrderController 不会冲突。
问:按业务划分会不会导致目录过深?
答: 是的,但这是可接受的代价,可以通过 IDE 的“跳转到文件”功能(Ctrl+Shift+N)快速定位,或使用 php artisan module:list 查看所有模块。
| 维度 | 按技术划分 | 按业务划分 |
|---|---|---|
| 逻辑相关性 | 低(代码按“是什么”散落) | 高(代码按“做什么”集中) |
| 可维护性 | 低(修改某功能需跨目录操作) | 高(修改某功能只需进一个模块) |
| 可复用性 | 低(无法整体迁移) | 高(整个模块可导出) |
| 团队协作 | 容易冲突(逻辑上交叉) | 并行开发(每个团队负责一个模块) |
| 适合场景 | 小型项目、原型验证 | 中大型项目、企业级应用 |
最终建议: 除非你的项目只有几百行代码且不会再增长,否则永远选择按业务划分模块,这是从“能用”到“好维护”的关键一步。