SlideShare a Scribd company logo
1 of 273
Download to read offline
Surgeon General's Warning 
This talk is clocked at 1 slide per 12.8 seconds and features unsafe 
amounts of code. Presenter is a registered Class 3 Fast Talker (equal 
to 1 Gilmore Girls episode). 
Viewing is not recommended for those hungover, expected to become 
hungover or consuming excessive amounts of caffeine. Do not watch 
and operate motor vehicles. 
If you accidentally consume this talk, flush brain with kitten pictures 
and seek emergency help in another talk. No hard feelings, seriously. 
It's almost the weekend, after all. Why are we even here? 
Notice in accordance with the PHP Disarmament Compact of 1992. Void where prohibited.
Models & Service Layers 
Hemoglobin & Hobgoblins 
DrupalCon Amsterdam 
Ross Tuck
Freerange Codemonkey 
Know-It-All 
Hot-Air Balloon
@rosstuck 
rosstuck.com
About Today
Hemoglobin
Anemia.
Objects can too.
Model 
class TodoList { 
function setName($name); 
function getName(); 
function setStatus($status); 
function getStatus(); 
function addTask($task); 
function setTasks($tasks); 
function getTasks(); 
}
array( 
'name' => '', 
'status' => '', 
'tasks' => '' 
); 
Model
Bad ThingTM
“In essence the problem with anemic domain 
models is that they incur all of the costs of a 
domain model, without yielding any of the 
benefits.” 
-Martin Fowler
Our industry standard i s a n a n t i p a t t ern.
Ouch.
Important Note
Models 
Stuf
Integration over implementation
Our Setup
Model 
class TodoList { 
function setName($name); 
function getName(); 
function setStatus($status); 
function getStatus(); 
function addTask($task); 
function setTasks($tasks); 
function getTasks(); 
}
Model 
class Task { 
function setDescription($desc); 
function getDescription(); 
function setPriority($priority); 
function getPriority(); 
}
An ORM that's not Doctrine 2. 
A framework that's not Symfony2. 
I promise.
CRUD
Controller 
function addTaskAction($req) { 
$task = new Task(); 
$task->setDescription($req->get('desc')); 
$task->setPriority($req->get('priority')); 
$list = $this->todoRepo->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$task = new Task(); 
$task->setDescription($req->get('desc')); 
$task->setPriority($req->get('priority')); 
$list = $this->todoRepo->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
return $this->redirect('edit_page'); 
}
Anemic Model 
Hard to Maintain 
Testability 
SRP wha?
In Defense Of CRUD. 
No, seriously.
Low Barrier to Entry.
Easy to follow. 
If you can keep it 
in your head.
Sometimes it really is just data entry. 
(but it usually isn't) 
(but sometimes it is)
Not entirely a technical issue.
Service Layer
• Service Layer 
• Service Container 
• Web Service 
• Service Oriented Architecture 
• Domain Service 
• Stateless Service 
• Software-as-a-service 
• Platform-as-a-service 
• Whatever-as-a-service meme 
• Delivery Service 
• Laundry Service
Application Service
Model 
Controller 
View
Model 
Service Layer 
Controller 
View
Why?
1) Multiple User Interfaces 
Web + REST API 
+ CLI 
+ Workers
2) “In between” Logic
3) Decouple from frameworks
Model 
Service Layer 
Controller 
View
Just Build The Stupid Thing
Service 
Layer
Controller 
function addTaskAction($req) { 
$task = new Task(); 
$task->setDescription($req->get('desc')); 
$task->setPriority($req->get('priority')); 
$list = $this->todoRepo->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
return $this->redirect('edit_page'); 
}
Service 
class TodoService { 
function addTask(TodoList $list, $desc, $priority) { 
$task = new Task(); 
$task->setDescription($desc); 
$task->setPriority($priority); 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Service 
class TodoService { 
function findById($id) { 
return $this->repository->findById($id); 
} 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($id, $desc, $priority) { 
$list = $this->todoService->findById($id); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
function addTaskCommand($id, $desc, $priority) { 
$list = $this->todoService->findById($id); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask($list, $desc, $priority); 
CLI
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($id, $desc, $priority) { 
$list = $this->todoService->findById($id); 
if (!$list) { throw new NotFoundException(); } 
$this->todoService->addTask($list, $desc, $priority);
Service 
class TodoService { 
function findById($id) { 
return $this->repository->findById($id); 
} 
}
Service 
class TodoService { 
function findById($id) { 
$todo = $this->repository->findById($id); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
} 
not http exception
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($id, $desc, $priority) { 
$list = $this->todoService->findById($id); 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($id, $desc, $priority) { 
$list = $this->todoService->findById($id); 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($name, $desc, $priority) { 
$list = $this->todoService->findByName($name); 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($name, $desc, $priority) { 
$list = $this->todoService->findByName($name); 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($name, $desc, $priority) { 
$list = $this->todoService->findByName($name); 
$this->todoService->addTask($list, $desc, $priority);
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
CLI 
function addTaskCommand($name, $desc, $priority) { 
$listId = $this->todoService->findIdByName($name); 
$this->todoService->addTask($listId, $desc, $priority);
Service 
class TodoService { 
public function findLatestLists() { 
return $this->repository->findLatestLists(); 
} 
}
Service 
class TodoService { 
public function findLatestLists() { 
if ($this->cache->has('latest:lists')) { 
return $this->cache->get('latest:lists'); 
} 
$results = $this->repository->findLatestLists(); 
$this->cache->set('latest:lists', $results); 
return $results; 
} 
}
Indirect Advantages
Readability
Junior Protection
Discoverability
Service 
class TodoService { 
function findById($id); 
function addTask($todo, $desc, $priority); 
function prance(); 
}
Mission Accomplished
Model 
class TodoList { 
function setName($name); 
function getName(); 
function setStatus($status); 
function getStatus(); 
function setTasks($tasks); 
function getTasks(); 
}
Dumb as a box of rocks.
Model 
class TodoList { 
function setName($name); 
function getName(); 
function setStatus($status); 
function getStatus(); 
function setTasks($tasks); 
function getTasks(); 
} 
Where's mah logic?
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$task = new Task(); 
$task->setDescription($desc); 
$task->setPriority($priority); 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
“Organizes business logic by procedures 
where each procedure handles a single 
request from the presentation.” 
-Fowler
Transaction Scripts
Simple
More flexible 
Than CRUD, 
at least
Don't scale quite as well
What does belong in a service layer?
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$task = new Task(); 
$task->setDescription($desc); 
$task->setPriority($priority); 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Orchestration
Transactions 
Security 
Notifications 
Bulk operations
Facade
Fat Model, Skinny Controller
Fat Model, Skinny Service Layer
(re)Thinking
addTask() 
findById() 
findLatestLists() 
Service 
write 
read 
read
Remodeling our Reading 
by 
Refactoring our Repository 
Redux
Service 
class TodoService { 
function findById($id) { 
$todo = $this->repository->findById($id); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
}
Repository 
interface TodoRepository { 
public function findById($id); 
public function findLatestLists(); 
}
Repository 
class TodoDbRepository implements TodoRepository { 
public function findById($id) { 
$todo = $this->db->select(...); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
}
Repository 
class TodoDbRepository implements TodoRepository { 
public function findById($id) { 
$todo = $this->db->select(...); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
} 
raw db connection
Repository 
class TodoDbRepository implements TodoRepository { 
public function findById($id) { 
$todo = $this->repository->find($id); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
} 
FIXED
Repository 
interface TodoRepository { 
public function findById($id); 
public function findLatestLists(); 
}
Repository 
interface EntityRepository { 
public function createQueryBuilder($alias); 
public function createResultSetMappingBuilder($alias); 
public function createNamedQuery($queryName); 
public function createNativeNamedQuery($queryName); 
public function clear(); 
public function find($id, $lockMode, $lockVersion); 
public function findAll(); 
public function findBy($criteria, $orderBy, $limit, $offset); 
public function findOneBy($criteria, $orderBy); 
public function __call($method, $arguments); 
public function getClassName(); 
public function matching(Criteria $criteria); 
}
Repository 
interface TodoRepository { 
public function findById($id); 
public function findLatestLists(); 
}
Service 
class TodoService { 
function findById($id) { 
$todo = $this->repository->findById($id); 
if (!$todo) { 
throw new TodoNotFoundException(); 
} 
return $todo; 
} 
}
Service 
class TodoService { 
function findById($id) { 
return $this->repository->findById($id); 
} 
}
Service 
class TodoService { 
public function findLatestLists() { 
if ($this->cache->has('latest:lists')) { 
return $this->cache->get('latest:lists'); 
} 
$results = $this->repository->findLatestLists(); 
$this->cache->set('latest:lists', $results); 
return $results; 
} 
}
Repository 
class TodoDbRepository implements TodoRepository { 
public function findLatestLists() { 
if ($this->cache->has('latest:lists')) { 
return $this->cache->get('latest:lists'); 
} 
$results = $this->repository->query(...); 
$this->cache->set('latest:lists', $results); 
return $results; 
} 
}
Repository Decorator Decorator object 
class CachingTodoRepository implements TodoRepository { 
public function findLatestLists() { 
if ($this->cache->has('latest:lists')) { 
return $this->cache->get('latest:lists'); 
} 
$results = $this->innerRepository->findLatestLists(); 
$this->cache->set('latest:lists', $results); 
return $results; 
} 
} 
TodoDbRepository
DI Layer 
new TodoService( 
new CachingTodoRepository( 
new TodoDbRepository( 
$entityManager->getRepository('TodoList') 
) 
) 
)
The Inverse Biggie Law
Mo' classes 
Mo' decoupling and reduced overall design issues
Too many finder methods?
Controller 
$this->todoService->matching(array( 
new ListIsClosedCriteria(), 
new HighPriorityCriteria() 
));
DoctrineCriteria
Interlude: Services here... 
...services there... 
...services everywhere!
TaskService 
Task 
TodoListService 
TodoList 
TagService 
Tag 
TaskRepository TodoListRepository TagRepository
TaskService 
Task 
TodoListService 
TodoList 
TagService 
Tag 
TaskRepository TodoListRepository TagRepository
Task 
TodoService 
TodoList 
Tag
Task 
UserService 
TodoService 
TodoList 
Tag 
User
Task 
TodoService 
TodoList 
Tag 
UserService 
User
Service 
class TodoListService { 
public function findByUser(User $user) { 
return $this->repository->findByUser($user); 
} 
}
Service 
class TodoListService { 
public function findByUser(User $user) { 
return $user->getTodoLists(); 
} 
}
Task 
TodoService 
TodoList 
Tag 
Interfaces! 
UserService 
User
Services aren't only for entities
Scale can differ wildly
PrintingService
Quality of Implementation
(re)Modeling our Writing
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$task = new Task(); 
$task->setDescription($desc); 
$task->setPriority($priority); 
$task->setTodoList($list); 
$this->repository->save($task); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Model 
class TodoList { 
function addTask(Task $task) { 
$this->tasks[] = $task; 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task(); 
$task->setDescription($desc); 
$task->setPriority($priority); 
$this->tasks[] = $task; 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority); 
$this->tasks[] = $task; 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
} 
} 
ORM allowance
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority); 
$this->tasks[] = $task; 
} 
}
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority); 
$this->tasks[] = $task; 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority); 
$this->tasks[] = $task; 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
} 
}
Meaningful Tests
Working Together
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Model 
class TodoList { 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority); 
$this->tasks[] = $task; 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
} 
}
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
if (count($this->tasks) > 10) { 
$this->auditLog->logTooAmbitious($task); 
$this->mailer->sendMessage('Too unrealistic'); 
} 
Service
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
if (count($this->tasks) > 10) { 
$this->auditLog->logTooAmbitious($task); 
$this->mailer->sendMessage('Too unrealistic'); 
} 
Service
TodoService 
PrintingService
Something new...
Something better...
Domain Events
Common Pattern
Observer
New usage
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
}
Event 
class TaskAddedEvent { 
protected $description; 
protected $priority; 
function __construct($desc, $priority) { 
$this->description = $desc; 
$this->priority = $priority; 
} 
}
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
}
Model 
class TodoList { 
protected $pendingEvents = array(); 
protected function raise($event) { 
$this->pendingEvents[] = $event; 
} 
public function releaseEvents() { 
$events = $this->pendingEvents; 
$this->pendingEvents = array(); 
return $events; 
} 
} 
Excellent Trait
No dispatcher
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$this->auditLog->logNewTask($task); 
$this->mailer->sendMessage('New thingy!'); 
} 
}
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
} 
}
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
$list->addTask($desc, $priority); 
$this->repository->save($list); 
$events = $list->releaseEvents(); 
$this->eventDispatcher->dispatch($events); 
} 
}
Event Listeners 
class EmailListener { 
function onTaskAdded($event) { 
$taskDesc = $event->getDescription(); 
$this->mailer->sendMessage('New thingy: '.$taskDesc); 
} 
function onUserRegistered($event) { 
$this->mailer->sendMessage('welcome sucka!'); 
} 
}
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
}
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
$this->raise(new WasTooAmbitiousEvent($this->id)); 
} 
}
Nice things:
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
$this->raise(new WasTooAmbitiousEvent($this->id)); 
} 
} 
Logic is here!
Service 
class TodoListService { 
protected $dependency1; 
protected $dependency2; 
protected $dependency3; 
protected $dependency4; 
protected $dependency5; 
protected $dependency6; 
} 
Big ball of mud 
in the making
Event Listeners 
class EmailListener { 
function onTaskAdded($event) { 
$taskName = $event->task->getName(); 
$this->mailer->sendMessage('New thingy: '.$taskName); 
} 
function onUserRegistered($event) { 
$this->mailer->sendMessage('welcome sucka!'); 
} 
} 
Thin. Easy to test
TodoService 
Serialize & Send, 
Sucka! 
PrintingService
Model 
function addTask($desc, $priority) { 
$task = new Task($desc, $priority, $this); 
$this->tasks[] = $task; 
$this->raise( 
new TaskAddedEvent($this->id, $desc, $priority) 
); 
if (count($this->tasks) > 10) { 
$this->status = static::UNREALISTIC; 
} 
}
Less nice things.
Humans hate debugging events. 
Dev Logging. 
Debug commands.
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
Consuming Application Services
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$this->todoService->addTask( 
$list, $req->get('desc'), $req->get('priority') 
); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$list->addTask($req->get('desc'), $req->get('priority')); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$list->addTask($req->get('desc'), $req->get('priority')); 
$list->rename('blah'); 
return $this->redirect('edit_page'); 
}
Controller 
function addTaskAction($req) { 
$list = $this->todoService->findById($req->get('id')); 
$list->addTask($req->get('desc'), $req->get('priority')); 
$list->rename('blah'); 
$this->todoService->addTask(...); 
return $this->redirect('edit_page'); 
}
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
View Models
PHP version, not MVVM.
Service 
class TodoService { 
function findById($id) { 
$todoList = $this->repository->findById($id); 
return $todoList; 
} 
}
Service 
class TodoService { 
function findById($id) { 
$todoList = $this->repository->findById($id); 
return new TodoDTO($todoList); 
} 
}
TodoDTO 
class TodoDTO { 
public function getName(); 
public function getStatus(); 
public function getMostRecentTask(); 
}
Service 
class TodoService { 
function generateReport() { 
$data = $this->repository->performSomeCrazyQuery(); 
return new AnnualGoalReport($data); 
} 
}
Ain't rocket science.
Reverse it: DTOs not for output...
...but for input.
Going Commando
Command 
class AddTaskCommand { 
public $description; 
public $priority; 
public $todoListId; 
}
Controller 
function addTaskAction($req) { 
$command = new AddTaskCommand(); 
$command->description = $req->get('description'); 
$command->priority = $req->get('priority'); 
$command->todoListId = $req->get('todo_id'); 
$this->todoService->execute($command); 
return $this->redirect('edit_page'); 
} 
}
Handler Foo 
Controller Service Handler Bar 
Handler Baz
Controller Service 
Handler Baz
Service 
class TodoListService { 
}
Service 
class TodoListService { 
function addTask(TodoList $list, $desc, $priority) { 
} 
}
Service 
class TodoListService { 
function execute($command) { 
} 
}
Service 
class TodoListService { 
function execute($command) { 
get_class($command); 
} 
}
Service 
class TodoListService { 
function execute($command) { 
$command->getName(); 
} 
}
Service 
class TodoListService { 
function execute($command) { 
$command->execute(); 
} 
}
Service 
class TodoListService { 
function execute($command) { 
} 
}
What goes in a handler?
Handler 
class TodoListHandler { 
function handleAddTask($cmd) { 
$list = $this->repository->findById($cmd->todoListId); 
$list->addTask($cmd->description, $cmd->priority); 
} 
function handleCompleteTask($command) 
function handleRemoveTask($command) 
}
Service 
class TodoListService { 
function execute($command) { 
} 
}
Service 
class CommandBus { 
function execute($command) { 
} 
}
Service 
class MyCommandBus implements CommandBus { 
function execute($command) { 
} 
}
Service 
class LazyLoadingCommandBus implements CommandBus { 
function execute($command) { 
} 
}
Service 
class ValidatingCommandBus implements CommandBus { 
function execute($command) { 
if (!$this->validator->isValid($command)) { 
throw new InvalidCommandException(); 
} 
$this->innerCommandBus->execute($command); 
} 
}
Command 
class AddTaskCommand { 
public $description; 
public $priority; 
public $todoListId; 
}
Command 
use SymfonyComponentValidatorConstraints as Assert; 
class AddTaskCommand { 
/** @AssertLength(max="50") */ 
public $description; 
public $priority; 
public $todoListId; 
}
Logging 
Transactions 
Event Dispatching
Command
Fewer Dependencies per class. 
Simple layers. 
Easy to test.
View Models + Commands
Model 
Service Layer 
Commands ViewModels 
Controller 
View
forms 
templates 
validators 
CRUD for the framework. 
Domain Model for the chewy center. 
tough logic 
semantics 
testing
Diverge Further
CQRS
On the surface, it looks the same.
Controller 
function addTaskAction($req) { 
$command = new AddTaskCommand(); 
$command->description = $req->get('description'); 
$command->priority = $req->get('priority'); 
$command->todoListId = $req->get('todo_id'); 
$this->commandBus->execute($command); 
return $this->redirect('edit_page'); 
} 
}
CQS
Commands = Change Data 
Queries = Read Data
CQRS
Model 
class TodoList { 
function rename($name); 
function addTask($desc, $priority); 
function getName(); 
function getTasks(); 
}
Two Models
Write Model 
class TodoListModel { 
function rename($name); 
function addTask($desc, $priority); 
} 
Read Model 
class TodoListView { 
function getName(); 
function getTasks(); 
}
Model 
class TodoList { 
function rename($name); 
function addTask($desc, $priority); 
function getName(); 
function getTasks(); 
function getParticipatingUsers(); 
}
Write Model 
class TodoListModel { 
function rename($name); 
function addTask($desc, $priority); 
} 
Read Model 
class TodoListView { 
function getName(); 
function getTasks(); 
function getParticipatingUsers(); 
}
Write Model 
class TodoListModel { 
function rename($name); 
function addTask($desc, $priority); 
} 
Read Model 
class TodoListView { 
function getName(); 
function getTasks(); 
function getParticipatingUsers(); 
} 
ORM entity 
SQL query
Read and Write are two different systems.
User and Shopping Cart?
Same kind of split.
Surrounding classes?
A lot of it looks the same.
Handler 
class TodoListHandler { 
function handleAddTask($cmd) { 
$list = $this->repository->findById($cmd->todoListId); 
$list->addTask($cmd->description, $cmd->priority); 
} 
}
Service 
class TodoListService { 
public function findByUser(User $user) { 
return $this->repository->findByUser($user); 
} 
}
Handler 
class TodoListHandler { 
Service 
class TodoListService { 
public function findByUser(User $user) { 
return $this->repository->findByUser($user); 
} 
} 
function handleAddTask($cmd) { 
$list = $this->repository->findById($cmd->todoListId); 
$list->addTask($cmd->description, $cmd->priority); 
} 
}
$todoList = new TodoList(); 
$this->repository->save($todoList); 
$todoList->getId(); 
Controller
$command = new CreateTodoCommand(UUID::create()); 
$commandBus->execute($command); 
$command->uuid; 
Controller
Zoom Out
Martin Fowler 
waz here
Domain 
events
DB Views
Big 
Honking 
Queue
github.com/beberlei/litecqrs-php/ 
github.com/qandidate-labs/broadway 
github.com/gregoryyoung/m-r
Pros & Cons
Big mental leap. 
Usually more LOC. 
Not for every domain. 
Can be mixed.
Easy to Scale. 
Bears Complexity. 
Async Operations. 
Event Sourcing.
Event Sourcing?
CQRS 
+ 
Event Sourcing
Instead of storing the current state in the db...
...store the domain events?
Snapshots 
Debugging 
Audit Log 
Business Intelligence 
Online/Offline users
Google it. 
Or ask me afterwards.
Epilogue
"A foolish consistency is the hobgoblin of 
little minds." 
- Ralph Waldo Emerson
Strong opinions, weakly held.
Strong techniques, weakly held.

More Related Content

What's hot

Object Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPObject Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPChad Gray
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
R57shell
R57shellR57shell
R57shellady36
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Masahiro Nagano
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkG Woo
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday DeveloperRoss Tuck
 
Gta v savegame
Gta v savegameGta v savegame
Gta v savegamehozayfa999
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixturesBill Chang
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix itRafael Dohms
 
Internationalizing CakePHP Applications
Internationalizing CakePHP ApplicationsInternationalizing CakePHP Applications
Internationalizing CakePHP ApplicationsPierre MARTIN
 
Teaching Your Machine To Find Fraudsters
Teaching Your Machine To Find FraudstersTeaching Your Machine To Find Fraudsters
Teaching Your Machine To Find FraudstersIan Barber
 
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonfRafael Dohms
 
SfCon: Test Driven Development
SfCon: Test Driven DevelopmentSfCon: Test Driven Development
SfCon: Test Driven DevelopmentAugusto Pascutti
 
Drush. Secrets come out.
Drush. Secrets come out.Drush. Secrets come out.
Drush. Secrets come out.Alex S
 
Debugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionDebugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionIan Barber
 

What's hot (20)

Object Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHPObject Calisthenics Adapted for PHP
Object Calisthenics Adapted for PHP
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
R57shell
R57shellR57shell
R57shell
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
Shell.php
Shell.phpShell.php
Shell.php
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
 
Gta v savegame
Gta v savegameGta v savegame
Gta v savegame
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
Php 101: PDO
Php 101: PDOPhp 101: PDO
Php 101: PDO
 
Agile database access with CakePHP 3
Agile database access with CakePHP 3Agile database access with CakePHP 3
Agile database access with CakePHP 3
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix it
 
Internationalizing CakePHP Applications
Internationalizing CakePHP ApplicationsInternationalizing CakePHP Applications
Internationalizing CakePHP Applications
 
Teaching Your Machine To Find Fraudsters
Teaching Your Machine To Find FraudstersTeaching Your Machine To Find Fraudsters
Teaching Your Machine To Find Fraudsters
 
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
 
SfCon: Test Driven Development
SfCon: Test Driven DevelopmentSfCon: Test Driven Development
SfCon: Test Driven Development
 
Drush. Secrets come out.
Drush. Secrets come out.Drush. Secrets come out.
Drush. Secrets come out.
 
Debugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionDebugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 Version
 

Viewers also liked

Visualizing the Problem Domain for Spreadsheet Users: A Mental Model Perspective
Visualizing the Problem Domain for Spreadsheet Users: A Mental Model PerspectiveVisualizing the Problem Domain for Spreadsheet Users: A Mental Model Perspective
Visualizing the Problem Domain for Spreadsheet Users: A Mental Model PerspectiveBennett Kankuzi
 
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
 
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)Carlos Buenosvinos
 
Introduction to hexagonal architecture
Introduction to hexagonal architectureIntroduction to hexagonal architecture
Introduction to hexagonal architectureManel Sellés
 
Composer in monolithic repositories
Composer in monolithic repositoriesComposer in monolithic repositories
Composer in monolithic repositoriesSten Hiedel
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software designMatthias Noback
 
Clean architecture with ddd layering in php
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in phpLeonardo Proietti
 
Arquitectura hexagonal
Arquitectura hexagonalArquitectura hexagonal
Arquitectura hexagonal540deg
 
The framework as an implementation detail
The framework as an implementation detailThe framework as an implementation detail
The framework as an implementation detailMarcello Duarte
 

Viewers also liked (12)

Visualizing the Problem Domain for Spreadsheet Users: A Mental Model Perspective
Visualizing the Problem Domain for Spreadsheet Users: A Mental Model PerspectiveVisualizing the Problem Domain for Spreadsheet Users: A Mental Model Perspective
Visualizing the Problem Domain for Spreadsheet Users: A Mental Model Perspective
 
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
 
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)
Hexagonal Architecture - PHP Barcelona Monthly Talk (DDD)
 
Hexagonal symfony
Hexagonal symfonyHexagonal symfony
Hexagonal symfony
 
Introduction to hexagonal architecture
Introduction to hexagonal architectureIntroduction to hexagonal architecture
Introduction to hexagonal architecture
 
Composer in monolithic repositories
Composer in monolithic repositoriesComposer in monolithic repositories
Composer in monolithic repositories
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software design
 
Clean architecture with ddd layering in php
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in php
 
Arquitectura hexagonal
Arquitectura hexagonalArquitectura hexagonal
Arquitectura hexagonal
 
The framework as an implementation detail
The framework as an implementation detailThe framework as an implementation detail
The framework as an implementation detail
 
Hexagonal architecture in PHP
Hexagonal architecture in PHPHexagonal architecture in PHP
Hexagonal architecture in PHP
 

Similar to Surgeon General's Warning for Fast-Paced Talk on Models and Services

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosDivante
 
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
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher patternolvlvl
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolveXSolve
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2eugenio pombi
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Jeff Carouth
 
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
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammarsabrummett
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of TransductionDavid Stockton
 

Similar to Surgeon General's Warning for Fast-Paced Talk on Models and Services (20)

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
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
 
Drupal7 dbtng
Drupal7  dbtngDrupal7  dbtng
Drupal7 dbtng
 
PHPSpec BDD for PHP
PHPSpec BDD for PHPPHPSpec BDD for PHP
PHPSpec BDD for PHP
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
Oops in php
Oops in phpOops in php
Oops in php
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
 
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
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammars
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of Transduction
 
Presentation1
Presentation1Presentation1
Presentation1
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Smelling your code
Smelling your codeSmelling your code
Smelling your code
 

Recently uploaded

Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????blackmambaettijean
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxBkGupta21
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionDilum Bandara
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 

Recently uploaded (20)

Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptx
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An Introduction
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 

Surgeon General's Warning for Fast-Paced Talk on Models and Services

  • 1. Surgeon General's Warning This talk is clocked at 1 slide per 12.8 seconds and features unsafe amounts of code. Presenter is a registered Class 3 Fast Talker (equal to 1 Gilmore Girls episode). Viewing is not recommended for those hungover, expected to become hungover or consuming excessive amounts of caffeine. Do not watch and operate motor vehicles. If you accidentally consume this talk, flush brain with kitten pictures and seek emergency help in another talk. No hard feelings, seriously. It's almost the weekend, after all. Why are we even here? Notice in accordance with the PHP Disarmament Compact of 1992. Void where prohibited.
  • 2. Models & Service Layers Hemoglobin & Hobgoblins DrupalCon Amsterdam Ross Tuck
  • 8.
  • 10. Model class TodoList { function setName($name); function getName(); function setStatus($status); function getStatus(); function addTask($task); function setTasks($tasks); function getTasks(); }
  • 11. array( 'name' => '', 'status' => '', 'tasks' => '' ); Model
  • 13. “In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits.” -Martin Fowler
  • 14. Our industry standard i s a n a n t i p a t t ern.
  • 15. Ouch.
  • 17.
  • 18.
  • 19.
  • 23. Model class TodoList { function setName($name); function getName(); function setStatus($status); function getStatus(); function addTask($task); function setTasks($tasks); function getTasks(); }
  • 24. Model class Task { function setDescription($desc); function getDescription(); function setPriority($priority); function getPriority(); }
  • 25. An ORM that's not Doctrine 2. A framework that's not Symfony2. I promise.
  • 26. CRUD
  • 27. Controller function addTaskAction($req) { $task = new Task(); $task->setDescription($req->get('desc')); $task->setPriority($req->get('priority')); $list = $this->todoRepo->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); return $this->redirect('edit_page'); }
  • 28.
  • 29. Controller function addTaskAction($req) { $task = new Task(); $task->setDescription($req->get('desc')); $task->setPriority($req->get('priority')); $list = $this->todoRepo->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); return $this->redirect('edit_page'); }
  • 30. Anemic Model Hard to Maintain Testability SRP wha?
  • 31. In Defense Of CRUD. No, seriously.
  • 32. Low Barrier to Entry.
  • 33. Easy to follow. If you can keep it in your head.
  • 34. Sometimes it really is just data entry. (but it usually isn't) (but sometimes it is)
  • 35. Not entirely a technical issue.
  • 37. • Service Layer • Service Container • Web Service • Service Oriented Architecture • Domain Service • Stateless Service • Software-as-a-service • Platform-as-a-service • Whatever-as-a-service meme • Delivery Service • Laundry Service
  • 39.
  • 41. Model Service Layer Controller View
  • 42. Why?
  • 43.
  • 44. 1) Multiple User Interfaces Web + REST API + CLI + Workers
  • 46. 3) Decouple from frameworks
  • 47. Model Service Layer Controller View
  • 48. Just Build The Stupid Thing
  • 50. Controller function addTaskAction($req) { $task = new Task(); $task->setDescription($req->get('desc')); $task->setPriority($req->get('priority')); $list = $this->todoRepo->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); return $this->redirect('edit_page'); }
  • 51. Service class TodoService { function addTask(TodoList $list, $desc, $priority) { $task = new Task(); $task->setDescription($desc); $task->setPriority($priority); $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 52. Service class TodoService { function findById($id) { return $this->repository->findById($id); } }
  • 53. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); }
  • 54. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($id, $desc, $priority) { $list = $this->todoService->findById($id); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask($list, $desc, $priority);
  • 55. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); function addTaskCommand($id, $desc, $priority) { $list = $this->todoService->findById($id); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask($list, $desc, $priority); CLI
  • 56. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($id, $desc, $priority) { $list = $this->todoService->findById($id); if (!$list) { throw new NotFoundException(); } $this->todoService->addTask($list, $desc, $priority);
  • 57. Service class TodoService { function findById($id) { return $this->repository->findById($id); } }
  • 58. Service class TodoService { function findById($id) { $todo = $this->repository->findById($id); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } } not http exception
  • 59. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($id, $desc, $priority) { $list = $this->todoService->findById($id); $this->todoService->addTask($list, $desc, $priority);
  • 60. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($id, $desc, $priority) { $list = $this->todoService->findById($id); $this->todoService->addTask($list, $desc, $priority);
  • 61. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($name, $desc, $priority) { $list = $this->todoService->findByName($name); $this->todoService->addTask($list, $desc, $priority);
  • 62.
  • 63. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($name, $desc, $priority) { $list = $this->todoService->findByName($name); $this->todoService->addTask($list, $desc, $priority);
  • 64. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($name, $desc, $priority) { $list = $this->todoService->findByName($name); $this->todoService->addTask($list, $desc, $priority);
  • 65. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); CLI function addTaskCommand($name, $desc, $priority) { $listId = $this->todoService->findIdByName($name); $this->todoService->addTask($listId, $desc, $priority);
  • 66. Service class TodoService { public function findLatestLists() { return $this->repository->findLatestLists(); } }
  • 67. Service class TodoService { public function findLatestLists() { if ($this->cache->has('latest:lists')) { return $this->cache->get('latest:lists'); } $results = $this->repository->findLatestLists(); $this->cache->set('latest:lists', $results); return $results; } }
  • 72. Service class TodoService { function findById($id); function addTask($todo, $desc, $priority); function prance(); }
  • 74. Model class TodoList { function setName($name); function getName(); function setStatus($status); function getStatus(); function setTasks($tasks); function getTasks(); }
  • 75. Dumb as a box of rocks.
  • 76. Model class TodoList { function setName($name); function getName(); function setStatus($status); function getStatus(); function setTasks($tasks); function getTasks(); } Where's mah logic?
  • 77. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $task = new Task(); $task->setDescription($desc); $task->setPriority($priority); $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 78.
  • 79. “Organizes business logic by procedures where each procedure handles a single request from the presentation.” -Fowler
  • 82. More flexible Than CRUD, at least
  • 83. Don't scale quite as well
  • 84. What does belong in a service layer?
  • 85. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $task = new Task(); $task->setDescription($desc); $task->setPriority($priority); $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 89. Fat Model, Skinny Controller
  • 90. Fat Model, Skinny Service Layer
  • 92. addTask() findById() findLatestLists() Service write read read
  • 93. Remodeling our Reading by Refactoring our Repository Redux
  • 94. Service class TodoService { function findById($id) { $todo = $this->repository->findById($id); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } }
  • 95. Repository interface TodoRepository { public function findById($id); public function findLatestLists(); }
  • 96. Repository class TodoDbRepository implements TodoRepository { public function findById($id) { $todo = $this->db->select(...); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } }
  • 97. Repository class TodoDbRepository implements TodoRepository { public function findById($id) { $todo = $this->db->select(...); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } } raw db connection
  • 98. Repository class TodoDbRepository implements TodoRepository { public function findById($id) { $todo = $this->repository->find($id); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } } FIXED
  • 99. Repository interface TodoRepository { public function findById($id); public function findLatestLists(); }
  • 100. Repository interface EntityRepository { public function createQueryBuilder($alias); public function createResultSetMappingBuilder($alias); public function createNamedQuery($queryName); public function createNativeNamedQuery($queryName); public function clear(); public function find($id, $lockMode, $lockVersion); public function findAll(); public function findBy($criteria, $orderBy, $limit, $offset); public function findOneBy($criteria, $orderBy); public function __call($method, $arguments); public function getClassName(); public function matching(Criteria $criteria); }
  • 101. Repository interface TodoRepository { public function findById($id); public function findLatestLists(); }
  • 102. Service class TodoService { function findById($id) { $todo = $this->repository->findById($id); if (!$todo) { throw new TodoNotFoundException(); } return $todo; } }
  • 103. Service class TodoService { function findById($id) { return $this->repository->findById($id); } }
  • 104. Service class TodoService { public function findLatestLists() { if ($this->cache->has('latest:lists')) { return $this->cache->get('latest:lists'); } $results = $this->repository->findLatestLists(); $this->cache->set('latest:lists', $results); return $results; } }
  • 105. Repository class TodoDbRepository implements TodoRepository { public function findLatestLists() { if ($this->cache->has('latest:lists')) { return $this->cache->get('latest:lists'); } $results = $this->repository->query(...); $this->cache->set('latest:lists', $results); return $results; } }
  • 106. Repository Decorator Decorator object class CachingTodoRepository implements TodoRepository { public function findLatestLists() { if ($this->cache->has('latest:lists')) { return $this->cache->get('latest:lists'); } $results = $this->innerRepository->findLatestLists(); $this->cache->set('latest:lists', $results); return $results; } } TodoDbRepository
  • 107. DI Layer new TodoService( new CachingTodoRepository( new TodoDbRepository( $entityManager->getRepository('TodoList') ) ) )
  • 109. Mo' classes Mo' decoupling and reduced overall design issues
  • 110. Too many finder methods?
  • 111. Controller $this->todoService->matching(array( new ListIsClosedCriteria(), new HighPriorityCriteria() ));
  • 113. Interlude: Services here... ...services there... ...services everywhere!
  • 114. TaskService Task TodoListService TodoList TagService Tag TaskRepository TodoListRepository TagRepository
  • 115.
  • 116.
  • 117. TaskService Task TodoListService TodoList TagService Tag TaskRepository TodoListRepository TagRepository
  • 118.
  • 120. Task UserService TodoService TodoList Tag User
  • 121.
  • 122. Task TodoService TodoList Tag UserService User
  • 123. Service class TodoListService { public function findByUser(User $user) { return $this->repository->findByUser($user); } }
  • 124. Service class TodoListService { public function findByUser(User $user) { return $user->getTodoLists(); } }
  • 125. Task TodoService TodoList Tag Interfaces! UserService User
  • 126. Services aren't only for entities
  • 127. Scale can differ wildly
  • 131. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $task = new Task(); $task->setDescription($desc); $task->setPriority($priority); $task->setTodoList($list); $this->repository->save($task); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 132. Model class TodoList { function addTask(Task $task) { $this->tasks[] = $task; } }
  • 133. Model class TodoList { function addTask($desc, $priority) { $task = new Task(); $task->setDescription($desc); $task->setPriority($priority); $this->tasks[] = $task; } }
  • 134. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority); $this->tasks[] = $task; } }
  • 135. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; } } ORM allowance
  • 136. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority); $this->tasks[] = $task; } }
  • 137. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 138. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority); $this->tasks[] = $task; } }
  • 139. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority); $this->tasks[] = $task; if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } } }
  • 142. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 143. Model class TodoList { function addTask($desc, $priority) { $task = new Task($desc, $priority); $this->tasks[] = $task; if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } } }
  • 144. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 145. class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); if (count($this->tasks) > 10) { $this->auditLog->logTooAmbitious($task); $this->mailer->sendMessage('Too unrealistic'); } Service
  • 146. class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); if (count($this->tasks) > 10) { $this->auditLog->logTooAmbitious($task); $this->mailer->sendMessage('Too unrealistic'); } Service
  • 154. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } }
  • 155. Event class TaskAddedEvent { protected $description; protected $priority; function __construct($desc, $priority) { $this->description = $desc; $this->priority = $priority; } }
  • 156. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } }
  • 157. Model class TodoList { protected $pendingEvents = array(); protected function raise($event) { $this->pendingEvents[] = $event; } public function releaseEvents() { $events = $this->pendingEvents; $this->pendingEvents = array(); return $events; } } Excellent Trait
  • 159. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $this->auditLog->logNewTask($task); $this->mailer->sendMessage('New thingy!'); } }
  • 160. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); } }
  • 161. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { $list->addTask($desc, $priority); $this->repository->save($list); $events = $list->releaseEvents(); $this->eventDispatcher->dispatch($events); } }
  • 162. Event Listeners class EmailListener { function onTaskAdded($event) { $taskDesc = $event->getDescription(); $this->mailer->sendMessage('New thingy: '.$taskDesc); } function onUserRegistered($event) { $this->mailer->sendMessage('welcome sucka!'); } }
  • 163. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } }
  • 164. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; $this->raise(new WasTooAmbitiousEvent($this->id)); } }
  • 166. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; $this->raise(new WasTooAmbitiousEvent($this->id)); } } Logic is here!
  • 167. Service class TodoListService { protected $dependency1; protected $dependency2; protected $dependency3; protected $dependency4; protected $dependency5; protected $dependency6; } Big ball of mud in the making
  • 168. Event Listeners class EmailListener { function onTaskAdded($event) { $taskName = $event->task->getName(); $this->mailer->sendMessage('New thingy: '.$taskName); } function onUserRegistered($event) { $this->mailer->sendMessage('welcome sucka!'); } } Thin. Easy to test
  • 169. TodoService Serialize & Send, Sucka! PrintingService
  • 170. Model function addTask($desc, $priority) { $task = new Task($desc, $priority, $this); $this->tasks[] = $task; $this->raise( new TaskAddedEvent($this->id, $desc, $priority) ); if (count($this->tasks) > 10) { $this->status = static::UNREALISTIC; } }
  • 172. Humans hate debugging events. Dev Logging. Debug commands.
  • 173. Model Service Layer Controller View
  • 174. Model Service Layer Controller View
  • 175. Model Service Layer Controller View
  • 177. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); }
  • 178. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $this->todoService->addTask( $list, $req->get('desc'), $req->get('priority') ); return $this->redirect('edit_page'); }
  • 179. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $list->addTask($req->get('desc'), $req->get('priority')); return $this->redirect('edit_page'); }
  • 180. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $list->addTask($req->get('desc'), $req->get('priority')); $list->rename('blah'); return $this->redirect('edit_page'); }
  • 181. Controller function addTaskAction($req) { $list = $this->todoService->findById($req->get('id')); $list->addTask($req->get('desc'), $req->get('priority')); $list->rename('blah'); $this->todoService->addTask(...); return $this->redirect('edit_page'); }
  • 182.
  • 183. Model Service Layer Controller View
  • 184. Model Service Layer Controller View
  • 185. Model Service Layer Controller View
  • 186.
  • 189. Service class TodoService { function findById($id) { $todoList = $this->repository->findById($id); return $todoList; } }
  • 190. Service class TodoService { function findById($id) { $todoList = $this->repository->findById($id); return new TodoDTO($todoList); } }
  • 191. TodoDTO class TodoDTO { public function getName(); public function getStatus(); public function getMostRecentTask(); }
  • 192.
  • 193. Service class TodoService { function generateReport() { $data = $this->repository->performSomeCrazyQuery(); return new AnnualGoalReport($data); } }
  • 195. Reverse it: DTOs not for output...
  • 198.
  • 199.
  • 200. Command class AddTaskCommand { public $description; public $priority; public $todoListId; }
  • 201. Controller function addTaskAction($req) { $command = new AddTaskCommand(); $command->description = $req->get('description'); $command->priority = $req->get('priority'); $command->todoListId = $req->get('todo_id'); $this->todoService->execute($command); return $this->redirect('edit_page'); } }
  • 202. Handler Foo Controller Service Handler Bar Handler Baz
  • 205. Service class TodoListService { function addTask(TodoList $list, $desc, $priority) { } }
  • 206. Service class TodoListService { function execute($command) { } }
  • 207. Service class TodoListService { function execute($command) { get_class($command); } }
  • 208. Service class TodoListService { function execute($command) { $command->getName(); } }
  • 209. Service class TodoListService { function execute($command) { $command->execute(); } }
  • 210. Service class TodoListService { function execute($command) { } }
  • 211. What goes in a handler?
  • 212. Handler class TodoListHandler { function handleAddTask($cmd) { $list = $this->repository->findById($cmd->todoListId); $list->addTask($cmd->description, $cmd->priority); } function handleCompleteTask($command) function handleRemoveTask($command) }
  • 213. Service class TodoListService { function execute($command) { } }
  • 214. Service class CommandBus { function execute($command) { } }
  • 215. Service class MyCommandBus implements CommandBus { function execute($command) { } }
  • 216. Service class LazyLoadingCommandBus implements CommandBus { function execute($command) { } }
  • 217. Service class ValidatingCommandBus implements CommandBus { function execute($command) { if (!$this->validator->isValid($command)) { throw new InvalidCommandException(); } $this->innerCommandBus->execute($command); } }
  • 218. Command class AddTaskCommand { public $description; public $priority; public $todoListId; }
  • 219. Command use SymfonyComponentValidatorConstraints as Assert; class AddTaskCommand { /** @AssertLength(max="50") */ public $description; public $priority; public $todoListId; }
  • 222. Fewer Dependencies per class. Simple layers. Easy to test.
  • 223. View Models + Commands
  • 224. Model Service Layer Commands ViewModels Controller View
  • 225. forms templates validators CRUD for the framework. Domain Model for the chewy center. tough logic semantics testing
  • 227. CQRS
  • 228. On the surface, it looks the same.
  • 229. Controller function addTaskAction($req) { $command = new AddTaskCommand(); $command->description = $req->get('description'); $command->priority = $req->get('priority'); $command->todoListId = $req->get('todo_id'); $this->commandBus->execute($command); return $this->redirect('edit_page'); } }
  • 230. CQS
  • 231. Commands = Change Data Queries = Read Data
  • 232. CQRS
  • 233. Model class TodoList { function rename($name); function addTask($desc, $priority); function getName(); function getTasks(); }
  • 235. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); }
  • 236.
  • 237. Model class TodoList { function rename($name); function addTask($desc, $priority); function getName(); function getTasks(); function getParticipatingUsers(); }
  • 238. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); function getParticipatingUsers(); }
  • 239. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); function getParticipatingUsers(); } ORM entity SQL query
  • 240. Read and Write are two different systems.
  • 242. Same kind of split.
  • 244. A lot of it looks the same.
  • 245. Handler class TodoListHandler { function handleAddTask($cmd) { $list = $this->repository->findById($cmd->todoListId); $list->addTask($cmd->description, $cmd->priority); } }
  • 246. Service class TodoListService { public function findByUser(User $user) { return $this->repository->findByUser($user); } }
  • 247. Handler class TodoListHandler { Service class TodoListService { public function findByUser(User $user) { return $this->repository->findByUser($user); } } function handleAddTask($cmd) { $list = $this->repository->findById($cmd->todoListId); $list->addTask($cmd->description, $cmd->priority); } }
  • 248.
  • 249. $todoList = new TodoList(); $this->repository->save($todoList); $todoList->getId(); Controller
  • 250. $command = new CreateTodoCommand(UUID::create()); $commandBus->execute($command); $command->uuid; Controller
  • 253.
  • 254.
  • 255.
  • 256.
  • 262. Big mental leap. Usually more LOC. Not for every domain. Can be mixed.
  • 263. Easy to Scale. Bears Complexity. Async Operations. Event Sourcing.
  • 265. CQRS + Event Sourcing
  • 266. Instead of storing the current state in the db...
  • 268. Snapshots Debugging Audit Log Business Intelligence Online/Offline users
  • 269. Google it. Or ask me afterwards.
  • 271. "A foolish consistency is the hobgoblin of little minds." - Ralph Waldo Emerson
  • 274. PHP 3
  • 277. PHP 7
  • 279. Bang for the buck.
  • 281. It IS working for them.
  • 284. Further Reading • codebetter.com/gregyoung • martinfowler.com/tags/domain driven design.html • shawnmc.cool/domain-driven-design • whitewashing.de • verraes.net
  • 285. Thanks To: • Warnar Boekkooi @boekkooi • Daan van Renterghem @DRvanR • Matthijs van den Bos @matthijsvandenb
  • 286. Image Credits • http://www.flickr.com/photos/calgaryreviews/6427412605/sizes/l/ • http://msnbcmedia.msn.com/j/MSNBC/Components/Slideshows/_production/twisp_090511_ /twisp_090511_02.ss_full.jpg • http://shotthroughawindow.wordpress.com/2011/07/22/hp7b/ • http://www.sxc.hu/photo/605471 • http://martinfowler.com/bliki/images/cqrs/cqrs.png • http://www.flickr.com/photos/83346641@N00/5578975430/in/photolist-9uZH2y-8rTZL1-di xjR-ffBPiv-8SbK8K-ffS4md-6UeEGP • http://www.flickr.com/photos/lac-bac/7195938394/sizes/o/ • http://tdzdaily.org/wp-content/uploads/2013/03/Dumb-and-Dumber.png • http://upload.wikimedia.org/wikipedia/commons/1/17/Charlton_Heston_in_The_Ten_Comm andments_film_trailer.jpg • http://commons.wikimedia.org/wiki/File:Trench_construction_diagram_1914.png • http://www.flickr.com/photos/jeffreyww/4747314852/sizes/l/ • http://www.flickr.com/photos/jdhancock/3540861791/sizes/l/ • http://www.flickr.com/photos/superfantastic/50088733/sizes/l
  • 287. joind.in/11986 Ross Tuck rosstuck.com @rosstuck