Command-Oriented
Architecture
Maceió DEV Meetup #6
who am I?
➔ Tony Messias ~ @tony0x01
➔ Building web stuff since ~2010
before we start...
➔ CRUD thinking
➔ MVC
➔ Commands/Events
➔ Clean Architecture
“CRUD is an
antipattern”
(Mathias Verraes)
“CRUD doesn't express
behaviour. Avoid setters, and
use expressive, encapsulated
operations instead.”
<?php
$order = new Order();
$order->setStatus('paid');
$order->setPaidAmount(120);
$order->setPaidCurrency('EUR');
$order->setCustomer($customer);
<?php
$order = new Order();
$money = new Money(120, new Currency('EUR'));
$order->pay($customer, $money);
class CommentsController extends Controller {
public function store($postId)
{
$post = Post::find($postId);
$comment = new Comment([
'message' => 'A new comment.',
'user_id' => Auth::user()->id
]);
$post->comments()->save($comment);
return redirect()
->route('posts.view, $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller {
public function store($postId)
{
$post = Post::find($postId);
$comment = new Comment([
'message' => 'A new comment.',
'user_id' => Auth::user()->id
]);
$post->comments()->save($comment);
return redirect()
->route('posts.view, $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller {
public function store($postId)
{
$post = Post::find($postId);
$comment = new Comment(['message' => 'A new comment.']);
$user = Auth::user();
$post->comment($user, $comment);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller {
public function store($postId)
{
$post = Post::find($postId);
$comment = new Comment(['message' => 'A new comment.']);
$user = Auth::user();
$post->comment($user, $comment);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class Post extends Model
{
// ...
public function comment(User $user, Comment $comment)
{
$comment->user_id = $user->id;
$this->comments()->save($comment);
}
// ...
}
class SendSMS {
public function fire($job, $data)
{
$twilio = new Twilio_SMS($apiKey);
$twilio->sendTextMessage(array(
'to' => $data['user']['phone_number'],
'message' => $data['message'],
));
$user = User::find($data['user']['id']);
$user->messages()->create([
'to' => $data['user']['phone_number'],
'message' => $data['message'],
]);
$job->delete();
}
}
class SendSMS {
public function fire($job, $data)
{
$twilio = new Twilio_SMS($apiKey);
$twilio->sendTextMessage(array(
'to' => $data['user']['phone_number'],
'message' => $data['message'],
));
$user = User::find($data['user']['id']);
$user->messages()->create([
'to' => $data['user']['phone_number'],
'message' => $data['message'],
]);
$job->delete();
}
}
class SendSMS {
public function fire($job, $data)
{
$twilio = new Twilio_SMS($apiKey);
$twilio->sendTextMessage(array(
'to' => $data['user']['phone_number'],
'message' => $data['message'],
));
$user = User::find($data['user']['id']);
$user->messages()->create([
'to' => $data['user']['phone_number'],
'message' => $data['message'],
]);
$job->delete();
}
}
class SendSMS {
function __construct(UserRepository $users, SmsCourierInterface $courier)
{
$this->users = $users;
$this->courier = $courier;
}
public function fire($job, $data)
{
$user = $this->users->find($data['user']['id']);
$user->sendSmsMessage($this->courier, $data['message']);
$job->delete();
}
}
use IlluminateDatabaseEloquentModel;
class User extends Model
{
public function sendSmsMessage(SmsCourierInterface $courier, $message)
{
$courier->sendMessage($this->phone_number, $message);
return $this->messages()->create([
'to' => $this->phone_number,
'message' => $message,
]);
}
}
class SmsTest extends PHPUnit_Framework_TestCase {
public function test_user_can_send_sms_message() {
$user = Mockery::mock('User[messages]');
$relation = Mockery::mock('StdClass');
$courier = Mockery::mock('SmsCourierInterface');
$user->shouldReceive('messages')->once()->andReturn($relation);
$relation->shouldReceive('create')->once()->with(array(
'to' => '555-555-5555',
'message' => 'Test',
));
$courier->shouldReceive('sendMessage')->once()->with(
'555-555-5555', 'Test'
);
$user->phone_number = '555-555-5555';
$user->sendSmsMessage($courier, 'Test');
}
}
class SmsTest extends PHPUnit_Framework_TestCase {
public function test_user_can_send_sms_message() {
$user = Mockery::mock('User[messages]');
$relation = Mockery::mock('StdClass');
$courier = Mockery::mock('SmsCourierInterface');
$user->shouldReceive('messages')->once()->andReturn($relation);
$relation->shouldReceive('create')->once()->with(array(
'to' => '555-555-5555',
'message' => 'Test',
));
$courier->shouldReceive('sendMessage')->once()->with(
'555-555-5555', 'Test'
);
$user->phone_number = '555-555-5555';
$user->sendSmsMessage($courier, 'Test');
}
}
class SmsTest extends PHPUnit_Framework_TestCase {
public function test_user_can_send_sms_message() {
$user = Mockery::mock('User[messages]');
$relation = Mockery::mock('StdClass');
$courier = Mockery::mock('SmsCourierInterface');
$user->shouldReceive('messages')->once()->andReturn($relation);
$relation->shouldReceive('create')->once()->with(array(
'to' => '555-555-5555',
'message' => 'Test',
));
$courier->shouldReceive('sendMessage')->once()->with(
'555-555-5555', 'Test'
);
$user->phone_number = '555-555-5555';
$user->sendSmsMessage($courier, 'Test');
}
}
class SmsTest extends PHPUnit_Framework_TestCase {
public function test_user_can_send_sms_message() {
$user = Mockery::mock('User[messages]');
$relation = Mockery::mock('StdClass');
$courier = Mockery::mock('SmsCourierInterface');
$user->shouldReceive('messages')->once()->andReturn($relation);
$relation->shouldReceive('create')->once()->with(array(
'to' => '555-555-5555',
'message' => 'Test',
));
$courier->shouldReceive('sendMessage')->once()->with(
'555-555-5555', 'Test'
);
$user->phone_number = '555-555-5555';
$user->sendSmsMessage($courier, 'Test');
}
}
be careful with
MVC
your framework is not
your architecture
$ tree rails/app
rails/app
├── assets
├── controllers
├── helpers
├── mailers
├── models
└── views
“this is a rails app”
Screaming Architecture
ok, but what does it have
to do with Commands?
they are basically DTOs,
with cool names
class CommentsController extends Controller
{
public function store($postId)
{
$user = Auth::user();
$post = Post::find($postId);
$comment = new Comment(['message' => 'A new comment.']);
$post->comment($user, $comment);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller
{
public function store($postId)
{
$user = Auth::user();
$post = Post::find($postId);
$comment = new Comment(['message' => 'A new comment.']);
$post->comment($user, $comment);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller
{
public function store($postId)
{
$user = Auth::user();
$post = Post::find($postId);
$comment = new Comment(['message' => 'A new comment.']);
$post->comment($user, $comment);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class CommentsController extends Controller
{
public function store($postId)
{
$user = Auth::user();
$message = Input::get('message');
$command = new LeaveCommentCommand($user, $postId, $message);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
class LeaveCommentCommand
{
public $user;
public $postId;
public $message;
public function __construct(User $user, $postId, $message)
{
$this->user = $user;
$this->postId = $postId;
$this->message = $message;
}
}
how do I execute them?
class CommentsController extends Controller
{
public function store($postId)
{
$user = Auth::user();
$message = Input::get('message');
$command = new LeaveCommentCommand($user, $postId, $message);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
use IlluminateFoundationBusDispatchesCommands;
class CommentsController extends Controller {
use DispatchesCommands;
public function store($postId) {
$user = Auth::user();
$message = Input::get('message');
$command = new LeaveCommentCommand($user, $postId, $message);
$this->dispatch($command);
return redirect()
->route('posts.index', $post)
->withMessage('Your comment was successfully created');
}
}
what does dispatch do?
finds a handler
for our command
one Command can be
executed by one and
only one Handler
LeaveCommentCommand
LeaveCommentCommandHandler
class LeaveCommentCommandHandler
{
public function handle(LeaveCommentCommand $command)
{
$post = Post::find($command->postId);
$comment = new Comment(['message' => $command->message]);
$post->comment($command->user, $comment);
}
}
what if I want to notify
the post creator about
that new comment?
class LeaveCommentCommandHandler {
private $mailer;
function __construct(UserMailer $mailer) {
$this->mailer = $mailer;
}
public function handle(LeaveCommentCommand $command) {
$post = Post::find($command->postId);
$comment = new Comment(['message' => $command->message]);
$post->comment($command->user, $comment);
$this->notifyPostCreator($post->creator, $post, $comment);
}
// ...
}
class LeaveCommentCommandHandler
{
// ...
private function notifyPostCreator(
User $creator, Post $post, Comment $comment)
{
$this->mailer->sendTo(
$creator->email,
sprintf("New comment on [%s]", $post->title),
sprintf("User @%s left a comment for you: n%s",
$comment->user->username,
$comment->message)
);
}
}
works, but we can do
better...
use IlluminateContractsEventsDispatcher;
class LeaveCommentCommandHandler {
private $events;
function __construct(Dispatcher $events) {
$this->events = $events;
}
public function handle(LeaveCommentCommand $command) {
$post = Post::find($command->postId);
$comment = new Comment(['message' => $command->message]);
$post->comment($command->user, $comment);
$this->dispatchEvents($post->releaseEvents());
}
// ...
}
use IlluminateContractsEventsDispatcher;
class LeaveCommentCommandHandler {
// ...
private function dispatchEvents(array $events)
{
foreach ($events as $event)
$this->events->fire($event);
}
}
class Post extends Model
{
use EventGenerator;
public function comment(User $user, Comment $comment)
{
$comment->user_id = $user->id;
$this->comments()->save($comment);
$this->raise(new CommentWasLeft($post, $comment, $user));
}
}
trait EventGenerator
{
protected $domainEvents = [];
public function raise($event)
{
$this->domainEvents[] = $event;
}
public function releaseEvents()
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}
events are also just DTOs
class CommentWasLeft
{
public $post;
public $user;
public $comment;
public function __construct(Post $post, User $user, Comment $comment)
{
$this->post = $post;
$this->user = $user;
$this->comment = $comment;
}
}
but they can (and most
of the time they do)
have lots of
listeners/handlers
class NotifyPostOwnerAboutNewCommentHandler {
private $mailer;
function __construct(UserMailer $mailer) {
$this->mailer = $mailer;
}
public function handle(CommentWasLeft $event) {
$this->mailer->sendTo(
$event->post->creator->email,
sprintf("New comment on [%s]", $event->post->title),
sprintf("User @%s left a comment for you: n%s",
$event->user->username, $event->comment->message)
);
}
}
class EventServiceProvider extends ServiceProvider
{
/**
* The event handler mappings for the application.
* @param array
*/
protected $listen = [
CommentWasLeft::class => [
NotifyPostOwnerAboutNewCommentHandler::class
]
];
}
Recap:
➔ Boundaries interacts through commands;
➔ Command is executed by its handler;
➔ Command handlers fires/triggers domain
events;
➔ Events are listened by event handlers/listeners.
$ tree app
app
├── Commands
├── Console
├── Events
├── Exceptions
├── Handlers
├── Http
├── Providers
├── Services
└── User.php
$ tree app/Commands
app/Commands
├── Command.php
└── LeaveCommentCommand.php
$ tree app/Handlers
app/Handlers
├── Commands
│ └── LeaveCommentCommandHandler.php
└── Events
└── NotifyPostOwnerAboutNewCommentHandler.php
avoid CRUD thinking
$ tree app/Commands
app/Commands
├── CreateUserCommand.php
└── DeleteUserCommand.php
└── UpdateUserCommand.php
class DeactivateInventoryItemCommand
{
public $userId;
public $itemId;
public $comment;
public function __construct($userId, $itemId, $comment)
{
$this->userId = $userId;
$this->itemId = $itemId;
$this->comment = $comment;
}
}
you can easily use
queues to speed up your
requests.
use IlluminateContractsQueueShouldBeQueued;
class DeactivateInventoryItemCommand implements ShouldBeQueued {
public $userId;
public $itemId;
public $comment;
public function __construct($userId, $itemId, $comment) {
$this->userId = $userId;
$this->itemId = $itemId;
$this->comment = $comment;
}
}
use IlluminateContractsQueueShouldBeQueued;
class NotifyPostOwnerAboutNewCommentHandler implements ShouldBeQueued {
private $mailer;
function __construct(UserMailer $mailer) {
$this->mailer = $mailer;
}
public function handle(CommentWasLeft $event) {
$this->mailer->sendTo(
$event->post->creator->email,
sprintf("New comment on [%s]", $event->post->title),
sprintf("User @%s left a comment for you: n%s",
$event->user->username, $event->comment->message)
);
}
}
questions?
Resources
➔ Command Bus by Shawn Mccool
➔ Dev Discussions - The Command Bus
➔ Screaming Archirecture by Uncle Bob
➔ The Clean Archirecture by Uncle Bob
➔ Laravel: From Apprentice to Artisan
Resources
➔ Commands and Domain Events
(Laracasts)
➔ Task-based UIs
➔ CRUD is an antipattern by Mathias
Verraes

Command-Oriented Architecture

  • 1.
  • 2.
    who am I? ➔Tony Messias ~ @tony0x01 ➔ Building web stuff since ~2010
  • 4.
    before we start... ➔CRUD thinking ➔ MVC ➔ Commands/Events ➔ Clean Architecture
  • 5.
  • 6.
    “CRUD doesn't express behaviour.Avoid setters, and use expressive, encapsulated operations instead.”
  • 7.
    <?php $order = newOrder(); $order->setStatus('paid'); $order->setPaidAmount(120); $order->setPaidCurrency('EUR'); $order->setCustomer($customer);
  • 8.
    <?php $order = newOrder(); $money = new Money(120, new Currency('EUR')); $order->pay($customer, $money);
  • 9.
    class CommentsController extendsController { public function store($postId) { $post = Post::find($postId); $comment = new Comment([ 'message' => 'A new comment.', 'user_id' => Auth::user()->id ]); $post->comments()->save($comment); return redirect() ->route('posts.view, $post) ->withMessage('Your comment was successfully created'); } }
  • 10.
    class CommentsController extendsController { public function store($postId) { $post = Post::find($postId); $comment = new Comment([ 'message' => 'A new comment.', 'user_id' => Auth::user()->id ]); $post->comments()->save($comment); return redirect() ->route('posts.view, $post) ->withMessage('Your comment was successfully created'); } }
  • 11.
    class CommentsController extendsController { public function store($postId) { $post = Post::find($postId); $comment = new Comment(['message' => 'A new comment.']); $user = Auth::user(); $post->comment($user, $comment); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 12.
    class CommentsController extendsController { public function store($postId) { $post = Post::find($postId); $comment = new Comment(['message' => 'A new comment.']); $user = Auth::user(); $post->comment($user, $comment); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 13.
    class Post extendsModel { // ... public function comment(User $user, Comment $comment) { $comment->user_id = $user->id; $this->comments()->save($comment); } // ... }
  • 14.
    class SendSMS { publicfunction fire($job, $data) { $twilio = new Twilio_SMS($apiKey); $twilio->sendTextMessage(array( 'to' => $data['user']['phone_number'], 'message' => $data['message'], )); $user = User::find($data['user']['id']); $user->messages()->create([ 'to' => $data['user']['phone_number'], 'message' => $data['message'], ]); $job->delete(); } }
  • 15.
    class SendSMS { publicfunction fire($job, $data) { $twilio = new Twilio_SMS($apiKey); $twilio->sendTextMessage(array( 'to' => $data['user']['phone_number'], 'message' => $data['message'], )); $user = User::find($data['user']['id']); $user->messages()->create([ 'to' => $data['user']['phone_number'], 'message' => $data['message'], ]); $job->delete(); } }
  • 16.
    class SendSMS { publicfunction fire($job, $data) { $twilio = new Twilio_SMS($apiKey); $twilio->sendTextMessage(array( 'to' => $data['user']['phone_number'], 'message' => $data['message'], )); $user = User::find($data['user']['id']); $user->messages()->create([ 'to' => $data['user']['phone_number'], 'message' => $data['message'], ]); $job->delete(); } }
  • 17.
    class SendSMS { function__construct(UserRepository $users, SmsCourierInterface $courier) { $this->users = $users; $this->courier = $courier; } public function fire($job, $data) { $user = $this->users->find($data['user']['id']); $user->sendSmsMessage($this->courier, $data['message']); $job->delete(); } }
  • 18.
    use IlluminateDatabaseEloquentModel; class Userextends Model { public function sendSmsMessage(SmsCourierInterface $courier, $message) { $courier->sendMessage($this->phone_number, $message); return $this->messages()->create([ 'to' => $this->phone_number, 'message' => $message, ]); } }
  • 19.
    class SmsTest extendsPHPUnit_Framework_TestCase { public function test_user_can_send_sms_message() { $user = Mockery::mock('User[messages]'); $relation = Mockery::mock('StdClass'); $courier = Mockery::mock('SmsCourierInterface'); $user->shouldReceive('messages')->once()->andReturn($relation); $relation->shouldReceive('create')->once()->with(array( 'to' => '555-555-5555', 'message' => 'Test', )); $courier->shouldReceive('sendMessage')->once()->with( '555-555-5555', 'Test' ); $user->phone_number = '555-555-5555'; $user->sendSmsMessage($courier, 'Test'); } }
  • 20.
    class SmsTest extendsPHPUnit_Framework_TestCase { public function test_user_can_send_sms_message() { $user = Mockery::mock('User[messages]'); $relation = Mockery::mock('StdClass'); $courier = Mockery::mock('SmsCourierInterface'); $user->shouldReceive('messages')->once()->andReturn($relation); $relation->shouldReceive('create')->once()->with(array( 'to' => '555-555-5555', 'message' => 'Test', )); $courier->shouldReceive('sendMessage')->once()->with( '555-555-5555', 'Test' ); $user->phone_number = '555-555-5555'; $user->sendSmsMessage($courier, 'Test'); } }
  • 21.
    class SmsTest extendsPHPUnit_Framework_TestCase { public function test_user_can_send_sms_message() { $user = Mockery::mock('User[messages]'); $relation = Mockery::mock('StdClass'); $courier = Mockery::mock('SmsCourierInterface'); $user->shouldReceive('messages')->once()->andReturn($relation); $relation->shouldReceive('create')->once()->with(array( 'to' => '555-555-5555', 'message' => 'Test', )); $courier->shouldReceive('sendMessage')->once()->with( '555-555-5555', 'Test' ); $user->phone_number = '555-555-5555'; $user->sendSmsMessage($courier, 'Test'); } }
  • 22.
    class SmsTest extendsPHPUnit_Framework_TestCase { public function test_user_can_send_sms_message() { $user = Mockery::mock('User[messages]'); $relation = Mockery::mock('StdClass'); $courier = Mockery::mock('SmsCourierInterface'); $user->shouldReceive('messages')->once()->andReturn($relation); $relation->shouldReceive('create')->once()->with(array( 'to' => '555-555-5555', 'message' => 'Test', )); $courier->shouldReceive('sendMessage')->once()->with( '555-555-5555', 'Test' ); $user->phone_number = '555-555-5555'; $user->sendSmsMessage($courier, 'Test'); } }
  • 23.
  • 24.
    your framework isnot your architecture
  • 25.
    $ tree rails/app rails/app ├──assets ├── controllers ├── helpers ├── mailers ├── models └── views
  • 26.
    “this is arails app”
  • 27.
  • 28.
    ok, but whatdoes it have to do with Commands?
  • 30.
    they are basicallyDTOs, with cool names
  • 31.
    class CommentsController extendsController { public function store($postId) { $user = Auth::user(); $post = Post::find($postId); $comment = new Comment(['message' => 'A new comment.']); $post->comment($user, $comment); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 32.
    class CommentsController extendsController { public function store($postId) { $user = Auth::user(); $post = Post::find($postId); $comment = new Comment(['message' => 'A new comment.']); $post->comment($user, $comment); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 33.
    class CommentsController extendsController { public function store($postId) { $user = Auth::user(); $post = Post::find($postId); $comment = new Comment(['message' => 'A new comment.']); $post->comment($user, $comment); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 34.
    class CommentsController extendsController { public function store($postId) { $user = Auth::user(); $message = Input::get('message'); $command = new LeaveCommentCommand($user, $postId, $message); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 35.
    class LeaveCommentCommand { public $user; public$postId; public $message; public function __construct(User $user, $postId, $message) { $this->user = $user; $this->postId = $postId; $this->message = $message; } }
  • 36.
    how do Iexecute them?
  • 37.
    class CommentsController extendsController { public function store($postId) { $user = Auth::user(); $message = Input::get('message'); $command = new LeaveCommentCommand($user, $postId, $message); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 38.
    use IlluminateFoundationBusDispatchesCommands; class CommentsControllerextends Controller { use DispatchesCommands; public function store($postId) { $user = Auth::user(); $message = Input::get('message'); $command = new LeaveCommentCommand($user, $postId, $message); $this->dispatch($command); return redirect() ->route('posts.index', $post) ->withMessage('Your comment was successfully created'); } }
  • 39.
  • 40.
  • 41.
    one Command canbe executed by one and only one Handler
  • 42.
  • 43.
    class LeaveCommentCommandHandler { public functionhandle(LeaveCommentCommand $command) { $post = Post::find($command->postId); $comment = new Comment(['message' => $command->message]); $post->comment($command->user, $comment); } }
  • 44.
    what if Iwant to notify the post creator about that new comment?
  • 45.
    class LeaveCommentCommandHandler { private$mailer; function __construct(UserMailer $mailer) { $this->mailer = $mailer; } public function handle(LeaveCommentCommand $command) { $post = Post::find($command->postId); $comment = new Comment(['message' => $command->message]); $post->comment($command->user, $comment); $this->notifyPostCreator($post->creator, $post, $comment); } // ... }
  • 46.
    class LeaveCommentCommandHandler { // ... privatefunction notifyPostCreator( User $creator, Post $post, Comment $comment) { $this->mailer->sendTo( $creator->email, sprintf("New comment on [%s]", $post->title), sprintf("User @%s left a comment for you: n%s", $comment->user->username, $comment->message) ); } }
  • 47.
    works, but wecan do better...
  • 48.
    use IlluminateContractsEventsDispatcher; class LeaveCommentCommandHandler{ private $events; function __construct(Dispatcher $events) { $this->events = $events; } public function handle(LeaveCommentCommand $command) { $post = Post::find($command->postId); $comment = new Comment(['message' => $command->message]); $post->comment($command->user, $comment); $this->dispatchEvents($post->releaseEvents()); } // ... }
  • 49.
    use IlluminateContractsEventsDispatcher; class LeaveCommentCommandHandler{ // ... private function dispatchEvents(array $events) { foreach ($events as $event) $this->events->fire($event); } }
  • 50.
    class Post extendsModel { use EventGenerator; public function comment(User $user, Comment $comment) { $comment->user_id = $user->id; $this->comments()->save($comment); $this->raise(new CommentWasLeft($post, $comment, $user)); } }
  • 51.
    trait EventGenerator { protected $domainEvents= []; public function raise($event) { $this->domainEvents[] = $event; } public function releaseEvents() { $events = $this->domainEvents; $this->domainEvents = []; return $events; } }
  • 52.
    events are alsojust DTOs
  • 53.
    class CommentWasLeft { public $post; public$user; public $comment; public function __construct(Post $post, User $user, Comment $comment) { $this->post = $post; $this->user = $user; $this->comment = $comment; } }
  • 54.
    but they can(and most of the time they do) have lots of listeners/handlers
  • 55.
    class NotifyPostOwnerAboutNewCommentHandler { private$mailer; function __construct(UserMailer $mailer) { $this->mailer = $mailer; } public function handle(CommentWasLeft $event) { $this->mailer->sendTo( $event->post->creator->email, sprintf("New comment on [%s]", $event->post->title), sprintf("User @%s left a comment for you: n%s", $event->user->username, $event->comment->message) ); } }
  • 56.
    class EventServiceProvider extendsServiceProvider { /** * The event handler mappings for the application. * @param array */ protected $listen = [ CommentWasLeft::class => [ NotifyPostOwnerAboutNewCommentHandler::class ] ]; }
  • 57.
    Recap: ➔ Boundaries interactsthrough commands; ➔ Command is executed by its handler; ➔ Command handlers fires/triggers domain events; ➔ Events are listened by event handlers/listeners.
  • 58.
    $ tree app app ├──Commands ├── Console ├── Events ├── Exceptions ├── Handlers ├── Http ├── Providers ├── Services └── User.php
  • 59.
    $ tree app/Commands app/Commands ├──Command.php └── LeaveCommentCommand.php $ tree app/Handlers app/Handlers ├── Commands │ └── LeaveCommentCommandHandler.php └── Events └── NotifyPostOwnerAboutNewCommentHandler.php
  • 60.
  • 61.
    $ tree app/Commands app/Commands ├──CreateUserCommand.php └── DeleteUserCommand.php └── UpdateUserCommand.php
  • 64.
    class DeactivateInventoryItemCommand { public $userId; public$itemId; public $comment; public function __construct($userId, $itemId, $comment) { $this->userId = $userId; $this->itemId = $itemId; $this->comment = $comment; } }
  • 65.
    you can easilyuse queues to speed up your requests.
  • 66.
    use IlluminateContractsQueueShouldBeQueued; class DeactivateInventoryItemCommandimplements ShouldBeQueued { public $userId; public $itemId; public $comment; public function __construct($userId, $itemId, $comment) { $this->userId = $userId; $this->itemId = $itemId; $this->comment = $comment; } }
  • 67.
    use IlluminateContractsQueueShouldBeQueued; class NotifyPostOwnerAboutNewCommentHandlerimplements ShouldBeQueued { private $mailer; function __construct(UserMailer $mailer) { $this->mailer = $mailer; } public function handle(CommentWasLeft $event) { $this->mailer->sendTo( $event->post->creator->email, sprintf("New comment on [%s]", $event->post->title), sprintf("User @%s left a comment for you: n%s", $event->user->username, $event->comment->message) ); } }
  • 68.
  • 69.
    Resources ➔ Command Busby Shawn Mccool ➔ Dev Discussions - The Command Bus ➔ Screaming Archirecture by Uncle Bob ➔ The Clean Archirecture by Uncle Bob ➔ Laravel: From Apprentice to Artisan
  • 70.
    Resources ➔ Commands andDomain Events (Laracasts) ➔ Task-based UIs ➔ CRUD is an antipattern by Mathias Verraes