lunarstorm/laravel-ddd

Laravel 应用程序中领域驱动设计(DDD)模式工具包

v1.1.2 2024-09-03 02:19 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Laravel-DDD 是一个工具包,用于在 Laravel 应用程序中支持领域驱动设计(DDD)。采用 DDD 的一个痛点是无法使用 Laravel 的原生 make 命令来生成领域对象,因为这些对象通常存储在 App\* 命名空间之外。此包旨在通过提供等效命令,如 ddd:modelddd:dtoddd:view-model 等,来填补这些空白。

安装

您可以通过 composer 安装此包

composer require lunarstorm/laravel-ddd

您可以使用 ddd:install artisan 命令初始化此包。这将发布 配置文件,代表您在项目的 composer.json psr-4 自动加载配置中注册领域路径,并允许您在需要时发布生成器占位符进行自定义。

php artisan ddd:install

部署

在生产过程中,运行 ddd:cache优化自动加载

php artisan ddd:cache

版本兼容性

有关从 0.x 升级的更多详细信息,请参阅 UPGRADING

用法

语法

所有领域生成器命令都使用以下语法

# Specifying the domain as an option
php artisan ddd:{object} {name} --domain={domain}

# Specifying the domain as part of the name (short-hand syntax)
php artisan ddd:{object} {domain}:{name}

# Not specifying the domain at all, which will then 
# prompt for it (with auto-completion)
php artisan ddd:{object} {name}

可用命令

生成器

以下生成器目前可用,使用简写语法表示

# Generate a domain model
php artisan ddd:model Invoicing:Invoice

# Generate a domain model with factory
php artisan ddd:model Invoicing:Invoice -f
php artisan ddd:model Invoicing:Invoice --factory

# Generate a domain factory
php artisan ddd:factory Invoicing:InvoiceFactory
php artisan ddd:factory Invoicing:InvoiceFactory --model=Invoice # optionally specifying the model

# Generate a data transfer object
php artisan ddd:dto Invoicing:LineItemPayload

# Generates a value object
php artisan ddd:value Shared:DollarAmount

# Generates a view model
php artisan ddd:view-model Invoicing:ShowInvoiceViewModel

# Generates an action
php artisan ddd:action Invoicing:SendInvoiceToCustomer

# Extended Commands 
# These extend Laravel's respective make:* commands and places the objects into the domain layer
php artisan ddd:cast Invoicing:MoneyCast
php artisan ddd:channel Invoicing:InvoiceChannel
php artisan ddd:command Invoicing:InvoiceDeliver
php artisan ddd:event Invoicing:PaymentWasReceived
php artisan ddd:exception Invoicing:InvoiceNotFoundException
php artisan ddd:job Invoicing:GenerateInvoicePdf
php artisan ddd:listener Invoicing:HandlePaymentReceived
php artisan ddd:mail Invoicing:OverduePaymentReminderEmail
php artisan ddd:notification Invoicing:YourPaymentWasReceived
php artisan ddd:observer Invoicing:InvoiceObserver
php artisan ddd:policy Invoicing:InvoicePolicy
php artisan ddd:provider Invoicing:InvoiceServiceProvider
php artisan ddd:resource Invoicing:InvoiceResource
php artisan ddd:rule Invoicing:ValidPaymentMethod
php artisan ddd:scope Invoicing:ArchivedInvoicesScope

# Laravel 11+ only
php artisan ddd:class Invoicing:Support/InvoiceBuilder
php artisan ddd:enum Customer:CustomerType
php artisan ddd:interface Customer:Contracts/Invoiceable
php artisan ddd:trait Customer:Concerns/HasInvoices

生成的对象将放置在 配置文件 中指定的 ddd.namespaces.* 所述的适当领域命名空间中。

其他命令

# Show a summary of current domains in the domain folder
php artisan ddd:list

# Cache domain manifests (used for autoloading)
php artisan ddd:cache

# Clear the domain cache
php artisan ddd:clear

高级用法

嵌套对象

对于任何 ddd:* 生成器命令,可以使用正斜杠指定嵌套对象。

php artisan ddd:model Invoicing:Payment/Transaction
# -> Domain\Invoicing\Models\Payment\Transaction

php artisan ddd:action Invoicing:Payment/ProcessTransaction
# -> Domain\Invoicing\Actions\Payment\ProcessTransaction

php artisan ddd:exception Invoicing:Payment/PaymentFailedException
# -> Domain\Invoicing\Exceptions\Payment\PaymentFailedException

这对于没有固定命名空间的对象(例如 classinterfacetrait)至关重要,每个对象默认都有一个空命名空间。换句话说,这些对象来自领域的根。

php artisan ddd:class Invoicing:Support/InvoiceBuilder
# -> Domain\Invoicing\Support\InvoiceBuilder

php artisan ddd:interface Invoicing:Contracts/PayableByCreditCard
# -> Domain\Invoicing\Contracts\PayableByCreditCard

php artisan ddd:interface Invoicing:Models/Concerns/HasLineItems
# -> Domain\Invoicing\Models\Concerns\HasLineItems

在运行时覆盖配置的命名空间

如果您需要出于某种原因在不同于 ddd.namespaces.* 中配置的命名空间下生成领域对象,您可以使用以 / 开头的绝对名称。这将从领域的根生成对象。

# The usual: generate a provider in the configured provider namespace
php artisan ddd:provider Invoicing:InvoiceServiceProvider 
# -> Domain\Invoicing\Providers\InvoiceServiceProvider

# Override the configured namespace at runtime
php artisan ddd:provider Invoicing:/InvoiceServiceProvider
# -> Domain\Invoicing\InvoiceServiceProvider

# Generate an event inside the Models namespace (hypothetical)
php artisan ddd:event Invoicing:/Models/EventDoesNotBelongHere
# -> Domain\Invoicing\Models\EventDoesNotBelongHere

# Deep nesting is supported
php artisan ddd:exception Invoicing:/Models/Exceptions/InvoiceNotFoundException
# -> Domain\Invoicing\Models\Exceptions\InvoiceNotFoundException

子域(嵌套域)

可以在接受领域选项的任何地方使用点符号指定子域。

# Domain/Reporting/Internal/ViewModels/MonthlyInvoicesReportViewModel
php artisan ddd:view-model Reporting.Internal:MonthlyInvoicesReportViewModel

# Domain/Reporting/Customer/ViewModels/MonthlyInvoicesReportViewModel
php artisan ddd:view-model Reporting.Customer:MonthlyInvoicesReportViewModel

# (supported by all commands where a domain option is accepted)

自定义

此包附带了一些具有见解(但合理)的默认配置。您可以通过发布 配置文件 和生成器占位符进行自定义

php artisan vendor:publish --tag="ddd-config"
php artisan vendor:publish --tag="ddd-stubs"

请注意,扩展命令不会发布 ddd-specific 占位符,而是继承 Laravel 发布的应用程序级别的占位符。

领域自动加载和发现

可以使用 ddd.autoload 配置选项配置自动加载行为。默认情况下,领域提供程序、命令、策略和工厂会自动发现并注册。

'autoload' => [
    'providers' => true,
    'commands' => true,
    'policies' => true,
    'factories' => true,
],

服务提供程序

ddd.autoload.providers 启用时,领域层中任何扩展 Illuminate\Support\ServiceProvider 的类都将自动注册为服务提供程序。

控制台命令

当启用 ddd.autoload.commands 时,在域层中继承自 Illuminate\Console\Command 的任何类,在控制台运行时都会自动注册为命令。

策略

当启用 ddd.autoload.policies 时,该包将注册一个自定义策略发现回调来解析域模型的策略名称,并在其他所有情况下回退到Laravel的默认设置。如果你的应用程序使用 Gate::guessPolicyNamesUsing() 实现了自己的策略发现,你应该将 ddd.autoload.policies 设置为 false 以确保它不会被覆盖。

工厂

当启用 ddd.autoload.factories 时,该包将注册一个自定义工厂发现回调来解析域模型的工厂名称,并在其他所有情况下回退到Laravel的默认设置。请注意,这不会影响使用 Lunarstorm\LaravelDDD\Factories\HasDomainFactory 特性的域模型。这很有用,尤其是对于域层中使用的标准 Illuminate\Database\Eloquent\Factories\HasFactory 特性的常规模型。

如果你的应用程序使用 Factory::guessFactoryNamesUsing() 实现了自己的工厂发现,你应该将 ddd.autoload.factories 设置为 false 以确保它不会被覆盖。

在自动加载过程中忽略路径

要指定在自动加载发现过程中应跳过的文件夹或路径,请将它们添加到 ddd.autoload_ignore 配置选项中。默认情况下,会忽略 TestsMigrations 文件夹。

'autoload_ignore' => [
    'Tests',
    'Database/Migrations',
],

这里指定的路径相对于每个域的根目录。例如,src/Domain/Invoicing/{path-to-ignore}。如果需要更高级的过滤,可以在 AppServiceProvider 的 boot 方法中使用 DDD::filterAutoloadPathsUsing(callback $filter) 注册一个回调。

use Lunarstorm\LaravelDDD\Facades\DDD;
use Symfony\Component\Finder\SplFileInfo;

DDD::filterAutoloadPathsUsing(function (SplFileInfo $file) {
    if (basename($file->getRelativePathname()) === 'functions.php') {
        return false;
    }
});

过滤器回调基于 Symfony 的 Finder 组件

禁用自动加载

您可以通过在配置文件中将相应的自动加载选项设置为 false 来禁用自动加载,或者通过取消注释自动加载配置的全部内容。

// 'autoload' => [
//     'providers' => true,
//     'commands' => true,
//     'policies' => true,
//     'factories' => true,
// ],

生产环境中的自动加载

在生产环境中,您应该将自动加载清单缓存起来,作为应用程序部署过程的一部分,使用 ddd:cache 命令。这将加速域提供者和命令的自动发现和注册。如果需要,可以使用 ddd:clear 命令清除缓存。

配置文件

这是已发布的配置文件(ddd.php)的内容。

return [

    /*
    |--------------------------------------------------------------------------
    | Domain Path
    |--------------------------------------------------------------------------
    |
    | The path to the domain folder relative to the application root.
    |
    */
    'domain_path' => 'src/Domain',

    /*
    |--------------------------------------------------------------------------
    | Domain Namespace
    |--------------------------------------------------------------------------
    |
    | The root domain namespace.
    |
    */
    'domain_namespace' => 'Domain',

    /*
    |--------------------------------------------------------------------------
    | Domain Object Namespaces
    |--------------------------------------------------------------------------
    |
    | This value contains the default namespaces of generated domain
    | objects relative to the domain namespace of which the object
    | belongs to.
    |
    | e.g., Domain\Invoicing\Models\*
    |       Domain\Invoicing\Data\*
    |       Domain\Invoicing\ViewModels\*
    |       Domain\Invoicing\ValueObjects\*
    |       Domain\Invoicing\Actions\*
    |
    */
    'namespaces' => [
        'model' => 'Models',
        'data_transfer_object' => 'Data',
        'view_model' => 'ViewModels',
        'value_object' => 'ValueObjects',
        'action' => 'Actions',
        'cast' => 'Casts',
        'class' => '',
        'channel' => 'Channels',
        'command' => 'Commands',
        'enum' => 'Enums',
        'event' => 'Events',
        'exception' => 'Exceptions',
        'factory' => 'Database\Factories',
        'interface' => '',
        'job' => 'Jobs',
        'listener' => 'Listeners',
        'mail' => 'Mail',
        'notification' => 'Notifications',
        'observer' => 'Observers',
        'policy' => 'Policies',
        'provider' => 'Providers',
        'resource' => 'Resources',
        'rule' => 'Rules',
        'scope' => 'Scopes',
        'trait' => '',
    ],

    /*
    |--------------------------------------------------------------------------
    | Base Model
    |--------------------------------------------------------------------------
    |
    | The base class which generated domain models should extend. By default,
    | generated domain models will extend `Domain\Shared\Models\BaseModel`,
    | which will be created if it doesn't already exist.
    |
    */
    'base_model' => 'Domain\Shared\Models\BaseModel',

    /*
    |--------------------------------------------------------------------------
    | Base DTO
    |--------------------------------------------------------------------------
    |
    | The base class which generated data transfer objects should extend. By
    | default, generated DTOs will extend `Spatie\LaravelData\Data` from
    | Spatie's Laravel-data package, a highly recommended data object
    | package to work with.
    |
    */
    'base_dto' => 'Spatie\LaravelData\Data',

    /*
    |--------------------------------------------------------------------------
    | Base ViewModel
    |--------------------------------------------------------------------------
    |
    | The base class which generated view models should extend. By default,
    | generated domain models will extend `Domain\Shared\ViewModels\BaseViewModel`,
    | which will be created if it doesn't already exist.
    |
    */
    'base_view_model' => 'Domain\Shared\ViewModels\ViewModel',

    /*
    |--------------------------------------------------------------------------
    | Base Action
    |--------------------------------------------------------------------------
    |
    | The base class which generated action objects should extend. By default,
    | generated actions are based on the `lorisleiva/laravel-actions` package
    | and do not extend anything.
    |
    */
    'base_action' => null,

    /*
    |--------------------------------------------------------------------------
    | Autoloading
    |--------------------------------------------------------------------------
    |
    | Configure whether domain providers, commands, policies, and factories
    | should be auto-discovered and registered.
    |
    */
    'autoload' => [
        /**
         * When enabled, any class within the domain layer extending `Illuminate\Support\ServiceProvider`
         * will be auto-registered as a service provider
         */
        'providers' => true,

        /**
         * When enabled, any class within the domain layer extending `Illuminate\Console\Command`
         * will be auto-registered as a command when running in console.
         */
        'commands' => true,

        /**
         * When enabled, the package will register a custom policy discovery callback to resolve policy names
         * for domain models, and fallback to Laravel's default for all other cases.
         */
        'policies' => true,

        /**
         * When enabled, the package will register a custom factory discovery callback to resolve factory names
         * for domain models, and fallback to Laravel's default for all other cases.
         */
        'factories' => true,
    ],

    /*
    |--------------------------------------------------------------------------
    | Autoload Ignore Folders
    |--------------------------------------------------------------------------
    |
    | Folders that should be skipped during autoloading discovery,
    | relative to the root of each domain.
    |
    | e.g., src/Domain/Invoicing/<folder-to-ignore>
    |
    | If more advanced filtering is needed, a callback can be registered
    | using the `DDD::filterAutoloadPathsUsing(callback $filter)` in
    | the AppServiceProvider's boot method.
    |
    */
    'autoload_ignore' => [
        'Tests',
        'Database/Migrations',
    ],

    /*
    |--------------------------------------------------------------------------
    | Caching
    |--------------------------------------------------------------------------
    |
    | The folder where the domain cache files will be stored. Used for domain
    | autoloading.
    |
    */
    'cache_directory' => 'bootstrap/cache/ddd',
];

测试

composer test

变更日志

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

安全漏洞

请查看 我们的安全策略 了解如何报告安全漏洞。

致谢

许可

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