brueggemann/ esf
事件源基础
Requires
- php: >=7.1.3
- illuminate/redis: ^5.0
- predis/predis: ~1.0
- ramsey/uuid: ^3.7
Requires (Dev)
- fzaninotto/faker: ~1.4
- laravel/tinker: ~1.0
- mockery/mockery: ~1.0
- oneup/flysystem-bundle: 1.0.0
- phpunit/phpunit: ~7.0
- symfony/var-dumper: ^4.0
README
安装
使用Composer - 执行
composer require brueggemann/esf
设置
在bootstrap/app.php中注册ESFoundationServiceProvider
$app->register(ESFoundation\ServiceProviders\ESFoundationServiceProvider::class);
对于ESF外观取消注释
$app->withFacades();
对于Redis需要RedisServiceProvider
$app->register(\Illuminate\Redis\RedisServiceProvider::class);
并加载正确的配置
$app->configure('database');
在config/database.php中需要类似以下的配置
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '10.0.2.2'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'events' => [
'host' => env('REDIS_HOST', '10.0.2.2'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 1,
],
'aggregates' => [
'host' => env('REDIS_HOST', '10.0.2.2'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 2,
],
'queries' => [
'host' => env('REDIS_HOST', '10.0.2.2'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 3,
],
],
.env
QueryRepository、EventStore、AggregateProjectionRepository和EventBus都有内存和redis实现。
QUERY_REPOSITORY=redis#
EVENT_STORE=redis
AGGREGATE_REPOSITORY=redis
EVENT_BUS=memory
COMMAND_BUS=memory
Artisan
有几个Artisan命令可以帮助创建所需的基类。
$ php artisan make:aggregateRoot
$ php artisan make:aggregateRootProjection
$ php artisan make:aggregateRootValidator
$ php artisan make:command
$ php artisan make:commandHandler
$ php artisan make:event
用法
命令
您应用程序事件源部分的入口应该是CommandHandler。
$ php artisan make:commandHandler -name NAME -command COMMANDNAME -command COMMANDNAME -eventBus
如果CommandHandler需要AggregateProjectionRepository,请添加
$ [...] -aggregateProjectionRepository
如果使用CommandBus,则需要在AppServiceProvider中注册CommandHandler
public function register()
{
$commandBus = ESF::commandBus();
$commandBus->subscribe(app(ShippingCommandHandler::class));
[...]
}
在创建的CommandHandler的handleCOMMAND方法中,可以通过以下方式加载AggregateProjection
AGGREGATEROOT::load(AggregateRootId::new($AGGREGATEROOTID));
或如果aggregateProjectionRepository在构造函数中注入
$this->aggregateProjectionRepository->load(new AggregateRootId($AGGREGATEROOTID), AGGREGATEROOT::class);
事件可以通过以下方式存储
ESF::eventBus()->dispatch($DOMAINEVENTSTREAM);
或如果eventStore在构造函数中注入
$this->eventBus->dispatch($DOMAINEVENTSTREAM);
在创建的Command类中,可以使用标准的Laravel验证方式定义规则
public function rules()
{
return [
'foo' => 'required|string'
];
}
Command的构造函数接受任何形式的负载,并将其与定义的rules()方法的规则进行验证。
- 如果没有定义规则,则接受任何负载。
- 如果负载中的键未在规则中定义,则将其丢弃。
- 如果负载中的值未通过验证,则抛出异常。
最好是定义一个命名的数组作为负载
new COMMAND([
'foo' => 'bar
]);
可以获取整个负载
$COMMAND->getPayload();
或按键获取
$COMMAND->foo;
CommandHandler可以直接调用
$commandHandler = app(COMMANDHANDLER::class);
$commandHandler->handle(new COMMAND(['foo => 'bar]));
或使用COMMANDBUS
ESF::commandBus()->dispatch(new COMMAND(['foo' => 'bar']));
聚合
聚合分为三个部分
- 逻辑:AggregateRoot
- 表示:AggregateRootProjection
- 验证:AggregateRootValidator
$ php artisan make:aggregateRoot -name NAME -event EVENTNAME -event EVENTNAME -projection
如果AggregateRoot需要Validator,请添加
$ [...] -validator
在创建的AggregateRoot类的applyThatEVENT方法中,可以将$EVENT中的数据应用于$AGGREGATEROOTPROJECTION
$AGGREGATEROOTPROJECTION->foo = $EVENT->bar;
return true;
创建的事件类类似于Command类。可以定义规则,以确定事件负载的验证。
如果类似的规则对命令和事件都有效,则建议使用ValueObject来表示规则和每个实例的值。
$ php artisan make:valueObject -name NAME
然后可以将这些规则导入到COMMAND或/和EVENT中
public function rules()
{
return [
'foo' => VALUEOBJECT::rules(),
];
}
如果创建了AggregateRootValidator类,则可以通过在AggregateProjection未满足给定要求时返回false来防止Event应用于AggregateRootProjection。当验证失败时,会抛出FailedValidation异常。
创建的AggregateRootProjection代表聚合的状态。它包含多个实例化的ValueObjects。
public static function valueObjects(): Collection
{
return collect([
'foo' => Foo::class,
'foo2'=> Foo::class
]);
}
要使用AggregateRoot的逻辑将事件应用于AggregateRootProjection,有多种选择
AGGREGATEROOT::applyOn($AGGREGATEPROJECTION)->that($EVENT); // 1
AGGREGATEROOT::applyThat($EVENT, $AGGREGATEROOTPROJECTION); // 2
$AGGREGATEROOTPROJECTION->applyThat($EVENT); // 3
在1和2中可以选择AggregateRoot的逻辑,而在3中使用默认逻辑。
将事件应用于AggregateRootProjection的每个事件都会保存并可以检索
$events = $AGGREGATEROOTPROJECTION->popUncommittedEvents();
然后可以通过EventBus或如果需要,直接通过EventStore提交这些事件
ESF::eventBus()->dispatch($events);
ESF::eventStore()->push($events);
查询
由于没有检索事件或甚至聚合的特定模式的方法,例如SQL中的where子句,因此数据累积是通过查询完成的
查询应表示一个页面或视图。为了管理查询,需要使用查询存储库(QueryRepository)。
$queryRepository = ESF::queryReporitory();
添加方法保存键值对。如果值被更新,则在同一键下插入一个新条目。
$queryRepository->add('foo', 'bar');
要检索查询,使用get方法。如果只提供键,则返回最新的查询。如果还提供了一个索引,则返回对应的较旧查询。
$queryRepository->get('foo'); //bar
$queryRepository->get('foo', 0); //bar
查询存储库可用于保存长期计算、爬虫或仅间接持久化的数据。
事件监听器
事件监听器是监听器模式的基本实现。任何事件监听器都需要订阅它打算监听的事件总线(EventBus)。在AppServiceProvider中进行此操作是一个不错的选择。
$eventBus = ESF::eventBus();
$eventBus->subscribe(app(EVENTLISTENER::class));
如果需要同步处理事件数据,但在聚合根(AggregateRoot)中无法完成,这是一个创建和更新查询的好地方。