SlideShare uma empresa Scribd logo
1 de 70
Baixar para ler 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

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Semelhante a 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
 

Semelhante a 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
 

Mais de 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
 

Mais de 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
 

Último

Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Angel Borroy López
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)jennyeacort
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...Akihiro Suda
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfFerryKemperman
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfDrew Moseley
 

Último (20)

Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdf
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdf
 

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