SlideShare a Scribd company logo
1 of 70
Download to read offline
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

More Related Content

What's hot

Kick start with j query
Kick start with j queryKick start with j query
Kick start with j queryMd. Ziaul Haq
 
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptjQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptDarren Mothersele
 
Cleaner, Leaner, Meaner: Refactoring your jQuery
Cleaner, Leaner, Meaner: Refactoring your jQueryCleaner, Leaner, Meaner: Refactoring your jQuery
Cleaner, Leaner, Meaner: Refactoring your jQueryRebecca Murphey
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code OrganizationRebecca Murphey
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Leonardo Proietti
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Colin O'Dell
 
Object Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPObject Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPChad Gray
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mockingKonstantin Kudryashov
 
Miniproject on Employee Management using Perl/Database.
Miniproject on Employee Management using Perl/Database.Miniproject on Employee Management using Perl/Database.
Miniproject on Employee Management using Perl/Database.Sanchit Raut
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuerysergioafp
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application frameworkDustin Filippini
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
JQuery In Rails
JQuery In RailsJQuery In Rails
JQuery In RailsLouie Zhao
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConRafael Dohms
 

What's hot (20)

Kick start with j query
Kick start with j queryKick start with j query
Kick start with j query
 
jQuery secrets
jQuery secretsjQuery secrets
jQuery secrets
 
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 JavascriptjQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
jQuery UI Widgets, Drag and Drop, Drupal 7 Javascript
 
Cleaner, Leaner, Meaner: Refactoring your jQuery
Cleaner, Leaner, Meaner: Refactoring your jQueryCleaner, Leaner, Meaner: Refactoring your jQuery
Cleaner, Leaner, Meaner: Refactoring your jQuery
 
Functionality Focused Code Organization
Functionality Focused Code OrganizationFunctionality Focused Code Organization
Functionality Focused Code Organization
 
BVJS
BVJSBVJS
BVJS
 
Presentation1
Presentation1Presentation1
Presentation1
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
 
Object Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPObject Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHP
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mocking
 
Miniproject on Employee Management using Perl/Database.
Miniproject on Employee Management using Perl/Database.Miniproject on Employee Management using Perl/Database.
Miniproject on Employee Management using Perl/Database.
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
JQuery In Rails
JQuery In RailsJQuery In Rails
JQuery In Rails
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
 
jQuery
jQueryjQuery
jQuery
 

Similar to Command-Oriented Architecture

Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operationsyeda zoya mehdi
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency InjectionRifat Nabi
 
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.comJak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.comWebScience1
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutesBarang CK
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurt
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011Alessandro Nadalin
 

Similar to Command-Oriented Architecture (20)

Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operation
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.comJak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
PHP || [Student Result Management System]
PHP || [Student Result Management System]PHP || [Student Result Management System]
PHP || [Student Result Management System]
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 

More from Luiz Messias

Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developersLuiz Messias
 
Queues & Async Apps
 Queues & Async Apps Queues & Async Apps
Queues & Async AppsLuiz Messias
 
Laravel's ecosystem
Laravel's ecosystemLaravel's ecosystem
Laravel's ecosystemLuiz Messias
 
Introduction to Elasticsearch
Introduction to ElasticsearchIntroduction to Elasticsearch
Introduction to ElasticsearchLuiz Messias
 
APIs seguras com OAuth2
APIs seguras com OAuth2APIs seguras com OAuth2
APIs seguras com OAuth2Luiz Messias
 
Google App Engine e PHP
Google App Engine e PHPGoogle App Engine e PHP
Google App Engine e PHPLuiz Messias
 

More from Luiz Messias (7)

Phoenix for laravel developers
Phoenix for laravel developersPhoenix for laravel developers
Phoenix for laravel developers
 
Turbolinks
TurbolinksTurbolinks
Turbolinks
 
Queues & Async Apps
 Queues & Async Apps Queues & Async Apps
Queues & Async Apps
 
Laravel's ecosystem
Laravel's ecosystemLaravel's ecosystem
Laravel's ecosystem
 
Introduction to Elasticsearch
Introduction to ElasticsearchIntroduction to Elasticsearch
Introduction to Elasticsearch
 
APIs seguras com OAuth2
APIs seguras com OAuth2APIs seguras com OAuth2
APIs seguras com OAuth2
 
Google App Engine e PHP
Google App Engine e PHPGoogle App Engine e PHP
Google App Engine e PHP
 

Recently uploaded

The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)Roberto Bettazzoni
 
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIInflectra
 
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...WSO2
 
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdfAzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdfryanfarris8
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024VictoriaMetrics
 
WSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2
 
WSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AIAGATSoftware
 
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...WSO2
 
WSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in UgandaWSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in UgandaWSO2
 
Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2WSO2
 
Effective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConEffective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConNatan Silnitsky
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2
 
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Eraconfluent
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Bert Jan Schrijver
 
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...WSO2
 

Recently uploaded (20)

The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)The mythical technical debt. (Brooke, please, forgive me)
The mythical technical debt. (Brooke, please, forgive me)
 
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST API
 
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
WSO2Con2024 - Navigating the Digital Landscape: Transforming Healthcare with ...
 
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdfAzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
AzureNativeQumulo_HPC_Cloud_Native_Benchmarks.pdf
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
WSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AI
 
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open SourceWSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
WSO2CON 2024 - Freedom First—Unleashing Developer Potential with Open Source
 
WSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security Program
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AI
 
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
WSO2Con2024 - Simplified Integration: Unveiling the Latest Features in WSO2 L...
 
WSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in UgandaWSO2CON 2024 - Building a Digital Government in Uganda
WSO2CON 2024 - Building a Digital Government in Uganda
 
Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2Driving Innovation: Scania's API Revolution with WSO2
Driving Innovation: Scania's API Revolution with WSO2
 
Effective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConEffective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeCon
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public AdministrationWSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
WSO2CON 2024 - How CSI Piemonte Is Apifying the Public Administration
 
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million PeopleWSO2Con2024 - Unleashing the Financial Potential of 13 Million People
WSO2Con2024 - Unleashing the Financial Potential of 13 Million People
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Era
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...
WSO2CON 2024 - IoT Needs CIAM: The Importance of Centralized IAM in a Growing...
 

Command-Oriented Architecture

  • 2. who am I? ➔ Tony Messias ~ @tony0x01 ➔ Building web stuff since ~2010
  • 3.
  • 4. before we start... ➔ CRUD thinking ➔ MVC ➔ Commands/Events ➔ Clean Architecture
  • 6. “CRUD doesn't express behaviour. Avoid setters, and use expressive, encapsulated operations instead.”
  • 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, ]); } }
  • 19. 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'); } }
  • 20. 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'); } }
  • 21. 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'); } }
  • 22. 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'); } }
  • 24. your framework is not your architecture
  • 25. $ tree rails/app rails/app ├── assets ├── controllers ├── helpers ├── mailers ├── models └── views
  • 26. “this is a rails app”
  • 28. ok, but what does it have to do with Commands?
  • 29.
  • 30. they are basically DTOs, with cool names
  • 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; } }
  • 36. how do I execute them?
  • 37. 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'); } }
  • 38. 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'); } }
  • 40. finds a handler for our command
  • 41. one Command can be executed by one and only one Handler
  • 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) ); } }
  • 47. works, but we can 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 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; } }
  • 52. events are also just 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 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.
  • 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
  • 61. $ tree app/Commands app/Commands ├── CreateUserCommand.php └── DeleteUserCommand.php └── UpdateUserCommand.php
  • 62.
  • 63.
  • 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 easily use queues to speed up your requests.
  • 66. 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; } }
  • 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