veeenex/command-center

Laravel 框架的命令和领域事件

dev-master 2016-11-18 15:51 UTC

This package is auto-updated.

Last update: 2024-09-14 00:01:55 UTC


README

该包是从 airbornfoxx/commandcenter 分支出来的

此包提供了一种框架无关的架构,用于在您的应用程序中利用命令和领域事件。任何组件都可以轻松扩展以适应您的特定用途。

灵感来源于并在 Jeffrey WayLaracasts 上得到扩展

安装

通过 Composer 安装 CommandCenter。

添加到 composer.json

"require": {
    "veeenex/command-center": "~1.0"
}

安装

composer require "veeenex/command-center"

Laravel

更新应用程序配置以包含包的服务提供者。

'VeeeneX\CommandCenter\Provider\CommandCenterServiceProvider'

查看此文档末尾的 Laravel 部分,了解更多功能!

用法

在开始之前,不建议在架构不那么重要的较小项目中使用这种方法。此包有助于构建您的业务逻辑,坚持单一职责原则,并保持您的控制器精简。

CommandApplication

为了开始,您必须通过实现包的 CommandApplication 接口并注册任何绑定来将您的特定应用程序与 CommandCenter 进行注册。请使用包含的 Laravel 实现和服务提供者作为其他框架的参考。参考类如下

  • Flyingfoxx\CommandCenter\Laravel\Application
  • Flyingfoxx\CommandCenter\Laravel\CommandCenterServiceProvider

命令

一切从命令开始。命令是对领域执行的特定操作的“指令”。它表示为一个简单的 DTO(数据传输对象),它携带执行该特定命令所需的数据。

例如,假设您需要注册新用户。您将创建一个 RegisterUserCommand,它看起来像这样

<?php namespace Foxx\Users;

class RegisterUserCommand
{
    public $username;

    public $password;

    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
}

Foxx 作为示例应用程序

因此,您现在可以将所有逻辑放入控制器中,而不是现在创建一个命令并将数据传递到包含逻辑的处理程序。但是,现在您需要一个传递命令到相应处理程序的方法。那么,公交系统如何呢?

命令总线

首先,您需要将包的 CommandBus 注入到您的控制器中。这将是通过将命令传递到相应处理程序的方式。

<?php

use Flyingfoxx\CommandCenter\CommandBus;

class RegistrationController
{
    protected $commandBus;

    public function __construct(CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
    }
}

接下来,您创建并传递命令到命令总线。

<?php

use Flyingfoxx\CommandCenter\CommandBus;
use Foxx\Users\RegisterUserCommand;

class RegistrationController
{
    protected $commandBus;

    public function __construct(CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
    }

    public function store()
    {
        // Grab the input (using Laravel in this example)
        $input = Input::only('username', 'password');

        // Create command
        $command = new RegisterUserCommand($input['username'], $input['password']);

        // Pass command to command bus
        $this->commandBus->execute($command);
    }
}

通过这样做,命令总线将命令传递到相应的处理程序,在那里将执行命令的逻辑。

它是通过以下方式将命令类映射到相应的处理程序类的

  • RegisterUserCommand => RegisterUserHandler
  • PostBlogEntryCommand => PostBlogEntryHandler

请注意,您可以通过实现包的 CommandTranslator 类轻松地更改此映射。不要忘记更新任何应用程序绑定。

命令处理器

现在您需要一个处理命令的处理程序类。这将是命令总线交付命令的地方。如果命令类是 RegisterUserCommand,则处理程序类必须是 RegisterUserHandler

处理程序类必须实现包的 CommandHandler 接口,需要 handle() 方法。

<?php namespace Foxx\Users;

use Flyingfoxx\CommandCenter\CommandHandler;

class RegisterUserHandler implements CommandHandler
{
    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle($command)
    {
        $user = $this->user->register($command->username, $command->password);

        return $user;
    }
}

它应该可以工作。现在您可以在您的应用程序中利用命令。但是,现在您需要一种方法来挂钩这些命令以执行其他任务。您可以使用领域事件和监听器,只有在事件发生时才会执行这些任务。

事件

领域事件是指领域内发生了重要的事件。从上一个例子继续,一旦执行了 RegisterUserCommand,就发生了一个事件,即用户已被注册。

因此,您可以调用事件 UserWasRegistered,它将作为一个简单的 DTO(数据传输对象)来传递事件监听器所需的数据。

<?php namespace Foxx\Events;

use Foxx\Users\User;

class UserWasRegistered
{
    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

现在,您已经有一个事件了,必须在您的模型类中引发事件(在应用程序中创建事件的实例)。为此,您可以使用包中的 EventGenerator trait,如下所示

<?php namespace Foxx\Users\User;

use Flyingfoxx\CommandCenter\Eventing\EventGenerator;
use Foxx\Events\UserWasRegistered;

class User
{
    use EventGenerator;

    protected $username;
    protected $password;

    public function register($username, $password)
    {
        $this->username = $username;
        $this->password = $password;

        $this->raise(new UserWasRegistered($this));

        return $this;
    }
}

好的,现在 UserWasRegistered 事件已经引发,并且现在准备好分发(让您的应用程序了解其发生)。您可以在命令处理器类中通过注入包的 EventDispatcher 类并调用 dispatch($events) 方法来完成此操作。

<?php namespace Foxx\Users;

use Flyingfoxx\CommandCenter\CommandHandler;
use Flyingfoxx\CommandCenter\Eventing\EventDispatcher;

class RegisterUserHandler implements CommandHandler
{
    protected $user;

    protected $dispatcher;

    public function __construct(User $user, EventDispatcher $dispatcher)
    {
        $this->user = $user;
        $this->dispatcher = $dispatcher;
    }

    public function handle($command)
    {
        $user = $this->user->register($command->username, $command->password);

        $this->dispatcher->dispatch($user->releaseEvents());

        return $user;
    }
}

别忘了在实体对象上调用 releaseEvents()(因为它使用了 EventGenerator trait)。这样,所有分发的事件都将从引发的事件中删除。

事件监听器

现在事件已经引发和分发,您需要为该事件注册监听器。

遵循约定,如果我们引发事件 Foxx\Events\UserWasRegistered,那么要监听的事件名称将是 Foxx.Events.UserWasRegistered

下一步是在您的应用程序中注册一个事件监听器类。您可能需要在用户注册后发送一封电子邮件给他们。例如,在 Laravel 中,您可能会这样做

Event::listen('Foxx.Events.UserWasRegistered', 'Foxx\Listeners\EmailNotifier');

或者要注册此监听器以使用任何应用程序事件,您可以尝试这样做

Event::listen('Foxx.Events.*', 'Foxx\Listeners\EmailNotifier');

因此,现在,当在此命名空间下分发任何事件时,此监听器类将触发其 handle() 方法。当然,您可能只想使用此监听器类响应某些事件。您可以使用包的 EventListener 类来完成此操作。

通过简单地扩展此 EventListener 类,您可以创建遵循约定来处理每个特定事件的函数。约定是如果分发的事件是 UserWasRegistered,则监听器类中触发的方法将是 whenUserWasRegistered。如果没有找到此方法,它将简单地继续。

有了这些,您的 EmailNotifier 类可能看起来像这样

<?php namespace Foxx\Listeners;

use Flyingfoxx\CommandCenter\Eventing\EventListener;
use Flyingfoxx\Events\UserWasRegistered;

class EmailNotifier extends EventListener
{
    public function whenUserWasRegistered(UserWasRegistered $event)
    {
        // send an email to the user
    }
}

装饰命令总线

有时您可能想要装饰命令总线以在处理命令之前执行额外的操作。此包已经包含了一个验证装饰器。

验证

包含的验证命令总线可以作为装饰器与主命令总线一起使用。这必须在您的特定应用程序中实现。一旦命令已传递给命令总线,它将检查关联的验证器类并调用其 validate($command) 方法。否则,它将继续。这样,您就可以在执行命令和引发任何领域事件之前执行任何验证。

创建验证器类的约定如下

  • RegisterUserCommand > RegisterUserValidator

只需包含一个 validate($command) 方法,并按您通常的方式执行验证。

<?php namespace Foxx\Users;

use Flyingfoxx\CommandCenter\CommandValidator;

class RegisterUserValidator implements CommandValidator
{
    public function validate($command)
    {
        var_dump('validating register user command');
    }
}

您可以通过创建一个实现包的 CommandBus 接口的类并遵循装饰器设计模式来创建自己的装饰器。您的最佳选择是复制包含的 ValidationCommandBus 类并根据您的需求进行修改。

别忘了在您的特定应用程序中正确加载您的新装饰器。并且别忘了为您的新的装饰器包含一个新的翻译方法,无论是通过扩展 MainCommandTranslator 类还是创建一个实现包的 CommandTranslator 类的新类。

Laravel

如果您是 Laravel 用户,那么在这个包中为您提供了现成的解决方案。它包括包的 CommandApplication 接口的 Laravel 实现,以及一个可以加载到配置中的服务提供者。默认的命令总线使用验证装饰器,已经为您设置好了。再次强调,如果您需要提供额外的装饰器,请按照验证命令总线的模式操作,应该可以正常工作。

此包包含了一些为 Laravel 设计的特质。这些特质可以帮助您清理类,并提高代码的可读性。

Commander

这个特质本质上是对命令总线进行了封装,可以在任何控制器中使用。您不必注入命令总线,可以按照以下方式注入包的 Commander 特质:

<?php

use Flyingfoxx\CommandCenter\Laravel\Commander;
use Foxx\Users\RegisterUserCommand;

class RegistrationController
{
    use Commander;

    public function store()
    {
        // Grab the input (using Laravel in this example)
        $input = Input::only('username', 'password');

        // Create command
        $command = new RegisterUserCommand($input['username'], $input['password']);

        // Pass command to command bus
        $this->execute($command);
    }
}

现在您可以直接在控制器本身上调用 execute 方法。另一种选择是将此方法放入基控制器中,一次编写即可。

Dispatcher

这个特质本质上是对事件分发器进行了封装,可以在您的处理类中使用。您不必注入事件分发器,可以按照以下方式注入包的 Dispatcher 特质:

<?php namespace Foxx\Users;

use Flyingfoxx\CommandCenter\CommandHandler;
use Flyingfoxx\CommandCenter\Laravel\Dispatcher;

class RegisterUserHandler implements CommandHandler
{
    use Dispatcher;

    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle($command)
    {
        $user = $this->user->register($command->username, $command->password);

        $this->dispatchEventsFor($user);

        return $user;
    }
}

因此,您不需要调用 dispatch($events),而是应该在处理类上调用 dispatchEventsFor($entity),传入实体。这个特质还会自动释放传入实体的事件。

Laravel 5 更新

现在在 Laravel 5 中,您可以使用方法注入以及使用表单请求对象作为命令对象。映射如下:

  • RegisterUserRequest > RegisterUserHandler
  • PostBlogEntryRequest > PostBlogEntryHandler

其他所有内容的工作方式应保持不变。以下是一个示例控制器使用情况:

<?php namespace Foxx\Http\Controllers;

use Flyingfoxx\CommandCenter\Laravel\Commander;
use Foxx\Users\RegisterUserRequest;

class RegistrationController
{
    use Commander;

    public function store(RegisterUserRequest $request)
    {
        // Pass command to command bus
        $this->execute($request);
    }
}

结论

以上就是所有内容。请随意扩展,提出问题或发表评论。此外,请务必查看 Jeffrey Way 在 Laracasts 上的 Laracasts 系列,了解更多关于这方面的知识。