adhenrique/laravel-domain-oriented

在 Laravel 框架上构建面向领域的应用程序

v2.1.1 2022-01-08 19:39 UTC

This package is auto-updated.

Last update: 2024-09-09 01:46:19 UTC


README

此包构建面向领域 API 的结构(不是 DDD,它们是不同的事物)。具有搜索过滤器、验证和整洁的代码。

需求

  • PHP 7.2+,8.0(新版本)
  • Laravel 7.x,8(推荐稳定版)

介绍

我的需求很简单:以有组织且高效的方式构建结构。支持过滤器、验证和数据缓存(CQRS)的结构。

在继续之前,先看看最终的架构

app
├── ...
├── Domain
│   └── Dummy
│       ├── DummyFilterService.php
│       ├── DummyPersistenceModel.php
│       ├── DummyPersistenceService.php
│       ├── DummyPolicy.php
│       ├── DummyResource.php
│       ├── DummySearchModel.php
│       ├── DummySearchService.php
│       └── DummyValidateService.php
├── Http
│   ├── Controllers
│   │   ├── ...
│   │   └── DummyController.php
├── ...
database
├── factories
│   └── ...
│   └── DummyFactory.php
├── migrations
│   ├── ...
│   └── 2021_01_06_193044_create_dummies_table.php
└── seeders
    ├── DatabaseSeeder.php
    └── DummySeeder.php

你可能想知道

  1. 为什么不使用仓储模式?
    A. 无法获得比 Eloquent 提供的更高级的数据库抽象。1
  2. 持久化模型和搜索模型的想法是什么?
    A. 实际上,我更进一步。Eloquent 的模型实例不应返回。因此我们保证一个“只读”实例(它不用于数据库中的持久化)2 3
  3. 文件很多,我如何构建所有这些?
    A. 很简单,来杯咖啡,我们开始吧...

设置

  1. 运行以下 Composer 命令以安装最新版本
$ composer require adhenrique/laravel-domain-oriented
  1. 如果您愿意,可以导出位置文件
php artisan vendor:publish --provider="LaravelDomainOriented\ServiceProvider" --tag="lang"
  1. 运行此命令以构建领域结构
$ php artisan domain:create Dummy
  1. 请保持冷静。如果结构已存在,控制台会询问您是否要重写它,除非您传递 --force 标志
$ php artisan domain:create Dummy --force
  1. 当然,如果您想删除结构,只需运行此命令即可
$ php artisan domain:remove Dummy

就这样,尽情享受吧!

配置

调整您的模型

我们的模型遵循Eloquent 模型约定

  • 持久化模型:仅用于数据库中的持久化。定义您的字段、类型转换等...
  • 搜索模型:用于搜索。您的关系很可能在这里。

调整您的迁移

我们的迁移遵循Laravel 迁移结构

调整您的种子文件和工厂文件

在这里,我们也遵循 Laravel 的做法

调整您的策略

再次,策略遵循Laravel 策略授权

注意:您不必担心注册策略,因为我们已经在幕后完成了。但是,在这里我们遵循类名约定。当创建领域时,您的类必须命名为 SomethingPolicy 并属于 App\Domain\Something 命名空间。

配置验证

ValidateService 位于 app/Domain/YourDomainName/*

use LaravelDomainOriented\Services\ValidateService;

class DummyValidateService extends ValidateService
{
    protected array $rules = [
        // You can define general validation rules, which will be inherited
        // for all actions, or you can define validation rules for each action:
        // SHOW, STORE, UPDATE, DESTROY

        // General rules validation.
        // If any action validation rule is not defined, it will inherit from here.
        'name' => 'required|string',

        // Specific action rules validation. If set, ignores general validations.
        self::SHOW => [
            'id' => 'required|integer',
        ],
        self::UPDATE => [
            'id' => 'required|integer',
            'name' => 'required|string',
        ],
        self::DESTROY => [
            'id' => 'required|integer',
        ],
    ];
}

配置路由

我们遵循Laravel 路由模式。但由于我们处理的是 API,请修改 routes/api.php 文件,并添加以下路由

Route::get('dummies', 'App\Http\Controllers\DummyController@index');
Route::get('dummies/{id}', 'App\Http\Controllers\DummyController@show');
Route::post('dummies', 'App\Http\Controllers\DummyController@store');
Route::put('dummies/{id}', 'App\Http\Controllers\DummyController@update');
Route::delete('dummies/{id}', 'App\Http\Controllers\DummyController@destroy');

使用方法

在搜索过滤器之前

在SearchService类中,您有两个方法可以帮助您根据需求预先启动查询: beforeAllbeforeFindById。每个方法接收2个参数:具有启动的Eloquent实例的builder,以及包含用户会话的auth(如果已登录)。您只需重写这些方法,但请确保返回Eloquent的Builder。看看

class DummySearchService extends SearchService
{
    protected SearchModel $model;
    protected FilterService $filterService;

    public function __construct(DummySearchModel $model, DummyFilterService $filterService)
    {
        $this->model = $model;
        $this->filterService = $filterService;
    }

    public function beforeAll(Builder $builder, Guard $auth): Builder
    {
        return $builder;
    }

    public function beforeFindById(Builder $builder, Guard $auth): Builder
    {
        return $builder;
    }
}

在我的使用场景中,以管理员身份登录,我通常从用户列表中筛选出我自己的用户。

// ...
public function beforeAll(Builder $builder, Guard $auth): Builder
{
    return $this->removeLoggedFromSearches($builder, $auth);
}

private function removeLoggedFromSearches($builder, $auth)
{
    $id = $auth->id();
    return $builder->where('id', '<>', $id);
}

使用筛选进行搜索

您可以在列表路由上筛选和分页数据。为此,请使用您喜欢的客户端在请求中发送负载。

简单的Where条件

{
    "name": "adhenrique",
    "email": "eu@adhenrique.com.br"
}

Where in条件

{
    "id": [1,2,3]
}

Where条件运算符(如,>,>=,<,<=,<>)

{
    "name": {
        "operator": "like",
        "value": "%adhenrique%"
    }
}

Where between条件

{
    "birthdate": {
        "start": "1988-13-12",
        "end": "2021-01-01"
    }
}

分页结果

{
    "paginate": {
        "per_page": 1,
        "page": 1
    }
}

注意:您可以将筛选和分页一起使用。

待办事项

  • CQRS
  • 对旧Laravel版本的支持
  • 或Where筛选
  • 面向对象改进
  • 添加beforeAll和beforeFindById测试
  • 要求确认名称
  • 添加测试策略的方法

测试

$ composer test

变更日志

有关最近更改的更多信息,请参阅变更日志

贡献

有关详细信息,请参阅贡献指南

安全性

如果您发现任何安全相关的问题,请通过电子邮件eu@adhenrique.com.br而不是使用问题跟踪器。

鸣谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件

阅读文章

[1] 请,停止讨论Eloquent的Repository模式
[2] 有用的Eloquent Repositories?
[3] 您理解Repository模式吗?您确定是这样吗?
Laravel — 为什么您一直在错误地使用Repository模式