大型laravel项目应按业务领域(domain)组织目录,如app/domains/user,内含模型、服务、控制器等完整功能单元;配合psr-4自动加载,分离http层与业务逻辑,用action处理原子操作、service编排流程,避免控制器臃肿。

大型 Laravel 项目一旦沿用默认 app/Models、app/Http/Controllers 这种扁平结构,两个月后就会开始出现控制器臃肿、模型塞满业务逻辑、改个用户注册流程要跳 5 个文件的问题——这不是代码写得差,是目录结构没跟上复杂度。
按领域(Domain)组织 app/Domains 而不是按技术层
别再把所有 User 相关类散在 Models、Controllers、Requests 里。真实协作中,新人想看“用户注册完整流程”,需要手动拼凑逻辑;而按领域组织后,app/Domains/User 下直接有:
-
Models/User.php—— 仅定义表、访问器、关联 -
Services/UserRegistrationService.php—— 处理发邮件、创建资料、触发事件 -
Http/Controllers/UserController.php—— 只调$service->register($request->validated()) -
Http/Requests/RegisterUserRequest.php—— 验证规则全在这里
这种结构让“一个业务功能的所有实现”物理上聚在一起,而不是被技术分类强行撕开。PSR-4 自动加载必须配好:"AppDomains\": "app/Domains/",否则命名空间引用会失败。
Service 层不是万能胶,要区分 Action 和 Service
很多人把所有业务逻辑都塞进 UserService,结果这个类越来越重、越来越难测。实际应按职责切分:
-
CreateUserAction.php:只做一件事,比如“根据邮箱创建用户并返回实例”,无副作用,可单元测试 -
UserActivationService.php:协调多步操作,比如“调用CreateUserAction+ 发送激活邮件 + 记录日志”
关键区别:Action 是原子操作,Service 是流程编排。不要让 Service 直接操作数据库或发 HTTP 请求,这些该由 Action 或 Repository 承担。
HTTP 层必须隔离,控制器里禁止出现 if/else 业务分支
常见错误:UserController@store 里写一堆判断:“如果是企业用户走 A 流程,个人用户走 B,海外用户还要加验证”。这会让控制器变成业务调度中心,彻底失去可读性。
正确做法:
- 用策略模式或简单工厂,把分支逻辑移到
Services或Policies中 - 控制器只做三件事:接收请求、调用一个明确的入口方法(如
UserRegistrationService::handle($data))、返回响应 - 所有数据转换交给
ApiResource,所有验证交给FormRequest,控制器不碰数组键名或状态码硬编码
如果某个接口要返回不同结构(比如 PC 端 vs App 端),别在控制器里 if ($request->isApp()),而是用不同 Resource 类或预设响应契约。
模块化不是银弹,先从 Domain 开始,再评估 laravel-modules
nwidart/laravel-modules 确实支持真正的模块自治(路由、配置、迁移、视图全封装),但引入它意味着额外学习成本和构建链路复杂度。大多数团队卡在两个地方:
- 模块间强依赖时,循环引用导致
composer dump-autoload失败 - 前端资源(Vite)无法自动识别模块内
resources/js,需手动配置vite-module-loader.js
建议路径:先用 app/Domains + PSR-4 实现逻辑模块化;等团队熟悉领域边界、模块复用需求明确、且出现跨模块发布需求时,再引入 laravel-modules。过早模块化,反而增加维护负担。
最常被忽略的一点:目录结构本身不会提升质量,真正起作用的是每次新增类时,你有没有下意识问一句——“它属于哪个业务域?它的职责是否单一?它会不会被其他域直接依赖?” 结构只是把这些问题显性化而已。











