Talk I gave at Maceió DEV Meetup #6. Not only about Command Bus/Command Interface or whatever you name it, but a compilation of cool articles I found only that may help with understanding this architecture.
7. <?php
$order = new Order();
$order->setStatus('paid');
$order->setPaidAmount(120);
$order->setPaidCurrency('EUR');
$order->setCustomer($customer);
8. <?php
$order = new Order();
$money = new Money(120, new Currency('EUR'));
$order->pay($customer, $money);
9. 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');
}
}
10. 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');
}
}
11. 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');
}
}
12. 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');
}
}
13. class Post extends Model
{
// ...
public function comment(User $user, Comment $comment)
{
$comment->user_id = $user->id;
$this->comments()->save($comment);
}
// ...
}
14. 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();
}
}
15. 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();
}
}
16. 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();
}
}
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 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,
]);
}
}
31. 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');
}
}
32. 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');
}
}
33. 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');
}
}
34. 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');
}
}
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;
}
}
43. class LeaveCommentCommandHandler
{
public function handle(LeaveCommentCommand $command)
{
$post = Post::find($command->postId);
$comment = new Comment(['message' => $command->message]);
$post->comment($command->user, $comment);
}
}
44. what if I want 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
{
// ...
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)
);
}
}
50. 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));
}
}
51. trait EventGenerator
{
protected $domainEvents = [];
public function raise($event)
{
$this->domainEvents[] = $event;
}
public function releaseEvents()
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}
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 extends ServiceProvider
{
/**
* The event handler mappings for the application.
* @param array
*/
protected $listen = [
CommentWasLeft::class => [
NotifyPostOwnerAboutNewCommentHandler::class
]
];
}
57. Recap:
➔ Boundaries interacts through commands;
➔ Command is executed by its handler;
➔ Command handlers fires/triggers domain
events;
➔ Events are listened by event handlers/listeners.
67. 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)
);
}
}
69. 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
70. Resources
➔ Commands and Domain Events
(Laracasts)
➔ Task-based UIs
➔ CRUD is an antipattern by Mathias
Verraes