Stars: 349
Forks: 55
Pull Requests: 246
Issues: 16
Watchers: 9
Last Updated: 2023-09-12 18:04:14
Lumen 8 基础上扩展出的API 启动项目,精心设计的目录结构,规范统一的响应数据格式,Repository 模式架构的最佳实践。
License: MIT License
Languages: PHP, Shell
查找了网上很多的API 相关的开发规范文档,参考了不少大佬们总结的经验,决定尝试使用最新版本的 Lumen(当下最新版本是 Lumen 8.x)来构建一个基础功能完备,规范统一,能够快速应用于实际的 API 项目开发启动模板。同时,也希望通过合理的架构设计使其适用于中大型项目。
少许的依赖安装,遵循 Laravel 的思维进行扩展,不额外增加「负担」。
开箱即用,加速 Api 开发。
Lumen学习交流群:1105120693(QQ)
其他规划讨论中...
├── app
│ ├── Console
│ │ ├── Commands
│ │ └── Kernel.php // Schedule 调度
│ ├── Contracts // 定义 interface
│ │ └── Repositories
│ ├── Events
│ │ ├── Event.php
│ │ └── ExampleEvent.php
│ ├── Exceptions // 异常处理
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers // Controller 任务分发给不同 Service 处理,返回响应给客户端
│ │ ├── Middleware
│ │ └── Resources // Api Resource 数据转换
│ ├── Jobs // 异步任务
│ │ ├── ExampleJob.php
│ │ └── Job.php
│ ├── Listeners
│ │ └── ExampleListener.php
│ ├── Policies // 权限校验
│ │ └── PostPolicy.php
│ ├── Providers
│ │ ├── AppServiceProvider.php
│ │ ├── AuthServiceProvider.php
│ │ ├── EloquentUserProvider.php // 定制的 EloquentUserProvider,缓存授权用户信息
│ │ ├── EventServiceProvider.php
│ │ └── RepositoryServiceProvider.php // repository 模式架构中,将 interface 与 repository 进行对象绑定
│ ├── Repositories // Repository 层:数据仓库层
│ │ ├── Criteria // 数据查询条件的组装拼接;(可以将公共的或者复杂的查询条件放在这个地方)
│ │ ├── Eloquent // 定义针对某个数据表(或存在关联关系的数据表)的数据维护逻辑;不处理业务(动态数据;实质的 Repository;基于 Eloquent\Model 的封装 )
│ │ ├── Enums // 枚举集合(静态数据)
│ │ ├── Models // Laravel 原始的 Eloquent\Model:定义数据表特性、数据表之间的关联关系等;不处理业务
│ │ ├── Presenters // 配合 Transformer 使用
│ │ ├── Transformers // 响应前的数据转换,作用与 Api Resource 类似,但是功能更丰富
│ │ └── Validators // Eloquent 数据维护前的校验,与表单验证功能类似
│ ├── Services // Service 层:处理实际业务;调用 Repository
│ │ ├── PostService.php
│ │ └── UserService.php
│ └── Support // 对框架的扩展,或者实际项目中需要封装一些与业务无关的通用功能集
│ ├── Serializers // league/fratcal 的 ArraySerializer 扩展,支持简单分页数据格式转换
│ ├── Traits // Class 中常用的辅助功能集
│ └── helpers.php // 全局会用到的辅助函数
在添加这部分描述的时候,联想到了 Vue 中的 Vuex,熟悉 Vuex 的同学可以类比一下。
Controller => dispatch,校验请求后分发业务处理
Service => action,具体的业务实现
Repository => state、mutation、getter,具体的数据维护
为了更好地理解 Repository & Service 模式,对 Laravel 中文社区的教程 2 中的 Larabbs 项目使用该模式进行了重构,实际开发过程可以参考其中的分层设计。
Controller 岗位职责:
__construct()
依赖注入多个 Service。比如 UserController
中可能会注入 UserService
(用户相关的功能业务)和 EmailService
(邮件相关的功能业务)$this->response
调用sucess
或fail
方法来返回统一的数据格式return Response::success(new UserCollection($resource));
或return Response::success(new UserResource($user));
Service 岗位职责:
handleListPageDisplay
和handleProfilePageDisplay
,分别对应用户列表展示和用户详情页展示的需求。EmailService
中或许就只有调用第三方 API 的逻辑,不需要更新维护系统中的数据,就不需要注入 Repository;OrderService
中实现了订单出库逻辑后,还需要生成相应的财务结算单据,就需要注入 OrderReposoitory
和FinancialDocumentRepository
,财务单据中的原单号关联着订单号,存在着数据关联。Repository 岗位职责:
searchUsersByPage
、searchUsersById
和insertUser
。$this->model
实际就是绑定的 Model 实例,所以就有了这样的写法$this->model::all()
,与原先的 ORM 写法User::all()
是完全等价的。Model 岗位职责:
经过前面的 Service 和 Repository 「分层」,剥离了可能存在于 Model 中的很多逻辑,比如校验参数,拼接查询,处理业务和转换数据结构等。所以,现如今的 Model 只需要相对简单地数据定义就可以了。比如,对数据表的定义,字段的映射,以及数据表之间关联关系等,提供给 Repository 中使用就够了。
完整的执行顺序:Criteria -> Validator -> Presenter
Enums:
这个是 lumen-api-starter 新增的部分,用来定义应用系统中常量的数据。
Criteria:l5-repository criteria
作用类似 Eloquent Model 中的 Scope 查询,把常用的查询提取出来,但是比 Scope 更强大。
可以省去 Model 中大量的根据请求参数判断并拼接查询条件的代码,与此同时,能够做到将多种数据之间存在的通用筛选条件剥离出来。
比如 make:repository
创建生成的 Repository 中默认包含以下代码,就是给 Repository 默认配置了一个 RequestCriteria,就可以直接使用下面的方式来过滤数据,是不是非常方便?!
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
Presenter:L5-repository presenters
可选,使用 Api Resource 的同学可以略过。需要安装 composer require league/fractal
,Dingo Api 中的 transformer 也是使用了这个扩展包。
作用类似 Laravel 的 Api Resource,或者可以说 Api Resource 是 Transformer 的轻量实现。
L5-repository 认为你将数据表结构的数据转换后是为了用来展示的,所以它将数据转换相关的逻辑独立出来,称为 Presenter。本质是整合了 fractal 中的 transformer 功能。
Transformer 的优秀之处这里暂不做讨论,因为这里的主角是 Presenter。传送门
先对比一下几种数据转换方式:
在 Controller 中调用 Response 中的 item 返回数据时传入 transformer 来转换数据
return $this->item($user, new UserTransformer, ['key' => 'user']);
在 Controller 中调用 Resource 或者 ResourceCollection 转换数据
//return Response::success(new UserResource($user));// 使用 lumen-api-starter 统一 code\status\message\data
return new UserResource($user);// 未统一响应结构
需要先定义 transformer,然后在 Presenter 中「注册」,最后在调用 Repository 时使用。
举例:
定义 UserTransformer
// app/Repositories/Transformers/UserTransformer.php
<?php
namespace App\Repositories\Transformers;
use App\Repositories\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'nickname' => $user->name,
'email' => $user->email,
];
}
}
「注册」到 UserPresenter
// app/Repositories/Presenters/UserPresenter.php
<?php
namespace App\Repositories\Presenters;
use App\Repositories\Transformers\UserTransformer;
use League\Fractal\TransformerAbstract;use Prettus\Repository\Presenter\FractalPresenter;
class UserPresenter extends FractalPresenter
{
/**
* Prepare data to present
*
* @return TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
在调用 repository 的时候使用
// app/Services/UserService.php
public function listPage(Request $request)
{
$this->repository->pushCriteria(new UserCriteria($request));
$this->repository->setPresenter(UserPresenter::class);
return $this->repository->searchUsersByPage();
}
看得出 Dingo Api 和 Api Resource 都是在最后响应数据的环节来转换数据,而 Repository 模式中认为但凡是与数据有关的处理逻辑都应该被「装进 Repository中」,应用系统中的其他部分不需要关心数据如何去查询(Criteria),如何去校验(Validator),以及如何去转换后提供显示(Presenter)。其他部分做好相应的职责就行,但凡与数据打交道的地方都交给 Repository。
controller:
service:
UserService
、EmailService
和OrderService
动词+名词
,描述能够实现的业务需求。比如:handleRegistration
表示实现用户注册功能。repository
make:repository
命令可以直接生成。searchUsersByPage
依照惯例,如对您的日常工作有所帮助或启发,欢迎三连 star + fork + follow
。
如果有任何批评建议,通过邮箱([email protected])的方式(如果我每天坚持看邮件的话)可以联系到我。
总之,欢迎各路英雄好汉。
The Lumen Api Starter is open-sourced software licensed under the MIT license.