flyingfoxx/commandcenter

适用于任何框架的命令和领域事件。包括Laravel的实现。

1.2.1 2014-09-17 12:02 UTC

This package is not auto-updated.

Last update: 2024-09-24 02:26:58 UTC


README

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

包括Laravel的实现。

灵感来源于并扩展了 Jeffrey WayLaracasts 的作品

安装

通过Composer安装CommandCenter。

"require": {
    "flyingfoxx/commandcenter": "~1.0"
}

Laravel

如果您使用Laravel,请更新 app/config/app.php 以包含软件包的服务提供者。

'Flyingfoxx\CommandCenter\Laravel\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特性,如下所示

<?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特性)。这样,所有分发的事件都会从引发的事件中删除。

事件监听器

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

遵循惯例,如果我们引发事件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用户,那么在这个包中为您提供了现成的解决方案。它包括Laravel实现包的CommandApplication接口以及一个可以加载到配置中的服务提供者。默认的命令总线使用验证装饰器,已经为您设置好了。再次提醒,如果您需要提供额外的装饰器,按照验证命令总线的模式进行,应该可以正常工作。

这个包包含了几个为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上的Commands and Domain Events系列,了解更多相关信息。