Models and Service Layers, Hemoglobin and Hobgoblins

Ross Tuck
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 end of the conference, 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 
ZendCon 2014 
Ross Tuck
Freerange Codemonkey 
Know-It-All 
Hot-Air Balloon
@rosstuck 
rosstuck.com
About Today
Hemoglobin
Anemia.
Models and Service Layers, Hemoglobin and Hobgoblins
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 and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
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'); 
}
Models and Service Layers, Hemoglobin and Hobgoblins
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
Models and Service Layers, Hemoglobin and Hobgoblins
Model 
Controller 
View
Model 
Service Layer 
Controller 
View
Why?
Models and Service Layers, Hemoglobin and Hobgoblins
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);
Models and Service Layers, Hemoglobin and Hobgoblins
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
Interface 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!'); 
} 
}
Models and Service Layers, Hemoglobin and Hobgoblins
“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
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
TaskService 
Task 
TodoListService 
TodoList 
TagService 
Tag 
TaskRepository TodoListRepository TagRepository
Models and Service Layers, Hemoglobin and Hobgoblins
Task 
TodoService 
TodoList 
Tag
Task 
UserService 
TodoService 
TodoList 
Tag 
User
Models and Service Layers, Hemoglobin and Hobgoblins
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(UserId $userId) { 
return $this->repository->findByUser($userId); 
} 
}
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'); 
}
Models and Service Layers, Hemoglobin and Hobgoblins
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
Model 
Service Layer 
Controller 
View
Models and Service Layers, Hemoglobin and Hobgoblins
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(); 
}
Models and Service Layers, Hemoglobin and Hobgoblins
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
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
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 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 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
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(); 
}
Models and Service Layers, Hemoglobin and Hobgoblins
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 
1 Model 
SQL query 
N Models
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); 
} 
}
Models and Service Layers, Hemoglobin and Hobgoblins
$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
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
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 
Retroactively Fix Bugs
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.
PHP 3
PHP 4 -5
PHP 5.3+
PHP 7
Might seem crazy.
Bang for the buck.
People ARE doing this.
It IS working for them.
You can too.
Questions?
Further Reading 
• codebetter.com/gregyoung 
• martinfowler.com/tags/domain driven design.html 
• shawnmc.cool/domain-driven-design 
• whitewashing.de 
• verraes.net
Thanks To: 
• Warnar Boekkooi @boekkooi 
• Daan van Renterghem @DRvanR 
• Matthijs van den Bos @matthijsvandenb
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
joind.in/12101 
Ross Tuck rosstuck.com 
@rosstuck
1 of 284

Recommended

Advanced javascript by
Advanced javascriptAdvanced javascript
Advanced javascriptDoeun KOCH
487 views56 slides
PHP para Adultos: Clean Code e Object Calisthenics by
PHP para Adultos: Clean Code e Object CalisthenicsPHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsGuilherme Blanco
17.2K views90 slides
Clean architecture with ddd layering in php by
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in phpLeonardo Proietti
38.9K views177 slides
Towards Functional Programming through Hexagonal Architecture by
Towards Functional Programming through Hexagonal ArchitectureTowards Functional Programming through Hexagonal Architecture
Towards Functional Programming through Hexagonal ArchitectureCodelyTV
2.5K views94 slides
도메인구현 KSUG 20151128 by
도메인구현 KSUG 20151128도메인구현 KSUG 20151128
도메인구현 KSUG 20151128beom kyun choi
18K views78 slides
Refactoring for Domain Driven Design by
Refactoring for Domain Driven DesignRefactoring for Domain Driven Design
Refactoring for Domain Driven DesignDavid Berliner
9.1K views36 slides

More Related Content

What's hot

Domain Driven Design 101 by
Domain Driven Design 101Domain Driven Design 101
Domain Driven Design 101Richard Dingwall
39.6K views55 slides
Java Beans by
Java BeansJava Beans
Java BeansAnkit Desai
8.3K views60 slides
12 tips on Django Best Practices by
12 tips on Django Best Practices12 tips on Django Best Practices
12 tips on Django Best PracticesDavid Arcos
50.8K views26 slides
Advanced JavaScript by
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScriptStoyan Stefanov
4K views113 slides
Introduction to jQuery by
Introduction to jQueryIntroduction to jQuery
Introduction to jQuerymanugoel2003
2.2K views36 slides
Java SE 8 best practices by
Java SE 8 best practicesJava SE 8 best practices
Java SE 8 best practicesStephen Colebourne
100.8K views88 slides

What's hot(20)

12 tips on Django Best Practices by David Arcos
12 tips on Django Best Practices12 tips on Django Best Practices
12 tips on Django Best Practices
David Arcos50.8K views
Introduction to jQuery by manugoel2003
Introduction to jQueryIntroduction to jQuery
Introduction to jQuery
manugoel20032.2K views
From framework coupled code to #microservices through #DDD /by @codelytv by CodelyTV
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
CodelyTV1.5K views
Rules Programming tutorial by Srinath Perera
Rules Programming tutorialRules Programming tutorial
Rules Programming tutorial
Srinath Perera22.1K views
Event source 학습 내용 공유 by beom kyun choi
Event source 학습 내용 공유Event source 학습 내용 공유
Event source 학습 내용 공유
beom kyun choi7.7K views
Thrift vs Protocol Buffers vs Avro - Biased Comparison by Igor Anishchenko
Thrift vs Protocol Buffers vs Avro - Biased ComparisonThrift vs Protocol Buffers vs Avro - Biased Comparison
Thrift vs Protocol Buffers vs Avro - Biased Comparison
Igor Anishchenko240.7K views
ALL BASIC SQL SERVER QUERY by Rajesh Patel
ALL BASIC SQL SERVER QUERY ALL BASIC SQL SERVER QUERY
ALL BASIC SQL SERVER QUERY
Rajesh Patel214 views
JavaScript Basics by Mats Bryntse
JavaScript BasicsJavaScript Basics
JavaScript Basics
Mats Bryntse1.6K views
JavaScript - Chapter 13 - Browser Object Model(BOM) by WebStackAcademy
JavaScript - Chapter 13 - Browser Object Model(BOM)JavaScript - Chapter 13 - Browser Object Model(BOM)
JavaScript - Chapter 13 - Browser Object Model(BOM)
WebStackAcademy3.4K views
Hibernate Tutorial by Ram132
Hibernate TutorialHibernate Tutorial
Hibernate Tutorial
Ram1324.4K views
ASP.NET Developer Roadmap 2021 by Ronak Sankhala
ASP.NET Developer Roadmap 2021ASP.NET Developer Roadmap 2021
ASP.NET Developer Roadmap 2021
Ronak Sankhala1.1K views

Viewers also liked

Enterprise PHP: mappers, models and services by
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesAaron Saray
12.1K views37 slides
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe... by
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...Aaron Saray
29.4K views56 slides
Rich domain model with symfony 2.5 and doctrine 2.5 by
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
12K views90 slides
Command Bus To Awesome Town by
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome TownRoss Tuck
7.2K views183 slides
Symfony: Your Next Microframework (SymfonyCon 2015) by
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
7.6K views90 slides
Building Data Mapper PHP5 by
Building Data Mapper PHP5Building Data Mapper PHP5
Building Data Mapper PHP5Vance Lucas
10.1K views32 slides

Viewers also liked(20)

Enterprise PHP: mappers, models and services by Aaron Saray
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and services
Aaron Saray12.1K views
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe... by Aaron Saray
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
Aaron Saray29.4K views
Rich domain model with symfony 2.5 and doctrine 2.5 by Leonardo Proietti
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
Leonardo Proietti12K views
Command Bus To Awesome Town by Ross Tuck
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
Ross Tuck7.2K views
Symfony: Your Next Microframework (SymfonyCon 2015) by Ryan Weaver
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
Ryan Weaver7.6K views
Building Data Mapper PHP5 by Vance Lucas
Building Data Mapper PHP5Building Data Mapper PHP5
Building Data Mapper PHP5
Vance Lucas10.1K views
Anatomy of a Modern PHP Application Architecture by AppDynamics
Anatomy of a Modern PHP Application Architecture Anatomy of a Modern PHP Application Architecture
Anatomy of a Modern PHP Application Architecture
AppDynamics5.5K views
Composer in monolithic repositories by Sten Hiedel
Composer in monolithic repositoriesComposer in monolithic repositories
Composer in monolithic repositories
Sten Hiedel2.9K views
Implementing DDD Concepts in PHP by Steve Rhoades
Implementing DDD Concepts in PHPImplementing DDD Concepts in PHP
Implementing DDD Concepts in PHP
Steve Rhoades46.2K views
Domain Driven Design using Laravel by wajrcs
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
wajrcs18.5K views
Software Design Patterns in Laravel by Phill Sparks by Phill Sparks
Software Design Patterns in Laravel by Phill SparksSoftware Design Patterns in Laravel by Phill Sparks
Software Design Patterns in Laravel by Phill Sparks
Phill Sparks272.9K views
Capturing, Analyzing and Optimizing MySQL by Ronald Bradford
Capturing, Analyzing and Optimizing MySQLCapturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQL
Ronald Bradford5K views
Midwest php 7 things keynote by Aaron Saray
Midwest php   7 things keynoteMidwest php   7 things keynote
Midwest php 7 things keynote
Aaron Saray1.6K views
Handle complex POST/PATCH requests in RESTful API by fightmaster
Handle complex POST/PATCH requests in RESTful APIHandle complex POST/PATCH requests in RESTful API
Handle complex POST/PATCH requests in RESTful API
fightmaster14.9K views
Mysqlnd, an unknown powerful PHP extension by julien pauli
Mysqlnd, an unknown powerful PHP extensionMysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extension
julien pauli6.8K views
Dealing with fear in legacy projects #PHPDS15 by Aitor Suso Gáceta
Dealing with fear in legacy projects #PHPDS15Dealing with fear in legacy projects #PHPDS15
Dealing with fear in legacy projects #PHPDS15
Aitor Suso Gáceta1.6K views

Similar to Models and Service Layers, Hemoglobin and Hobgoblins

Things I Believe Now That I'm Old by
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm OldRoss Tuck
6.6K views273 slides
Advanced php testing in action by
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
2.1K views92 slides
Adding Dependency Injection to Legacy Applications by
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
1.8K views77 slides
Database Design Patterns by
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
11.4K views87 slides
Min-Maxing Software Costs by
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software CostsKonstantin Kudryashov
879 views96 slides
Why is crud a bad idea - focus on real scenarios by
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
743 views27 slides

Similar to Models and Service Layers, Hemoglobin and Hobgoblins(20)

Things I Believe Now That I'm Old by Ross Tuck
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
Ross Tuck6.6K views
Advanced php testing in action by Jace Ju
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju2.1K views
Adding Dependency Injection to Legacy Applications by Sam Hennessy
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy1.8K views
Database Design Patterns by Hugo Hamon
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon11.4K views
Why is crud a bad idea - focus on real scenarios by Divante
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
Divante743 views
The command dispatcher pattern by olvlvl
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
olvlvl699 views
PHPCon 2016: PHP7 by Witek Adamus / XSolve by XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve1K views
Design Patterns avec PHP 5.3, Symfony et Pimple by Hugo Hamon
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
Hugo Hamon6.1K views
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 by Alessandro Nadalin
 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
Alessandro Nadalin9.4K views
Can't Miss Features of PHP 5.3 and 5.4 by Jeff Carouth
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
Jeff Carouth3.1K views
Parsing with Perl6 Grammars by abrummett
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammars
abrummett142 views
Your code sucks, let's fix it - DPC UnCon by Rafael Dohms
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
Rafael Dohms32.5K views
PHPUnit elevato alla Symfony2 by eugenio pombi
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi1.5K views

Recently uploaded

Perth MeetUp November 2023 by
Perth MeetUp November 2023 Perth MeetUp November 2023
Perth MeetUp November 2023 Michael Price
19 views44 slides
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N... by
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...James Anderson
66 views32 slides
ChatGPT and AI for Web Developers by
ChatGPT and AI for Web DevelopersChatGPT and AI for Web Developers
ChatGPT and AI for Web DevelopersMaximiliano Firtman
187 views82 slides
From chaos to control: Managing migrations and Microsoft 365 with ShareGate! by
From chaos to control: Managing migrations and Microsoft 365 with ShareGate!From chaos to control: Managing migrations and Microsoft 365 with ShareGate!
From chaos to control: Managing migrations and Microsoft 365 with ShareGate!sammart93
9 views39 slides
Report 2030 Digital Decade by
Report 2030 Digital DecadeReport 2030 Digital Decade
Report 2030 Digital DecadeMassimo Talia
15 views41 slides
handbook for web 3 adoption.pdf by
handbook for web 3 adoption.pdfhandbook for web 3 adoption.pdf
handbook for web 3 adoption.pdfLiveplex
22 views16 slides

Recently uploaded(20)

Perth MeetUp November 2023 by Michael Price
Perth MeetUp November 2023 Perth MeetUp November 2023
Perth MeetUp November 2023
Michael Price19 views
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N... by James Anderson
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
James Anderson66 views
From chaos to control: Managing migrations and Microsoft 365 with ShareGate! by sammart93
From chaos to control: Managing migrations and Microsoft 365 with ShareGate!From chaos to control: Managing migrations and Microsoft 365 with ShareGate!
From chaos to control: Managing migrations and Microsoft 365 with ShareGate!
sammart939 views
handbook for web 3 adoption.pdf by Liveplex
handbook for web 3 adoption.pdfhandbook for web 3 adoption.pdf
handbook for web 3 adoption.pdf
Liveplex22 views
6g - REPORT.pdf by Liveplex
6g - REPORT.pdf6g - REPORT.pdf
6g - REPORT.pdf
Liveplex10 views
Transcript: The Details of Description Techniques tips and tangents on altern... by BookNet Canada
Transcript: The Details of Description Techniques tips and tangents on altern...Transcript: The Details of Description Techniques tips and tangents on altern...
Transcript: The Details of Description Techniques tips and tangents on altern...
BookNet Canada135 views
Attacking IoT Devices from a Web Perspective - Linux Day by Simone Onofri
Attacking IoT Devices from a Web Perspective - Linux Day Attacking IoT Devices from a Web Perspective - Linux Day
Attacking IoT Devices from a Web Perspective - Linux Day
Simone Onofri15 views
Lilypad @ Labweek, Istanbul, 2023.pdf by Ally339821
Lilypad @ Labweek, Istanbul, 2023.pdfLilypad @ Labweek, Istanbul, 2023.pdf
Lilypad @ Labweek, Istanbul, 2023.pdf
Ally3398219 views
DALI Basics Course 2023 by Ivory Egg
DALI Basics Course  2023DALI Basics Course  2023
DALI Basics Course 2023
Ivory Egg16 views
Web Dev - 1 PPT.pdf by gdsczhcet
Web Dev - 1 PPT.pdfWeb Dev - 1 PPT.pdf
Web Dev - 1 PPT.pdf
gdsczhcet60 views
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas... by Bernd Ruecker
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...
Bernd Ruecker33 views
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院 by IttrainingIttraining
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院

Models and Service Layers, Hemoglobin and Hobgoblins

  • 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 end of the conference, 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 ZendCon 2014 Ross Tuck
  • 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.
  • 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'); }
  • 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
  • 41. Model Service Layer Controller View
  • 42. Why?
  • 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);
  • 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!'); } }
  • 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
  • 117. TaskService Task TodoListService TodoList TagService Tag TaskRepository TodoListRepository TagRepository
  • 120. Task UserService TodoService TodoList Tag User
  • 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(UserId $userId) { return $this->repository->findByUser($userId); } }
  • 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'); }
  • 183. Model Service Layer Controller View
  • 184. Model Service Layer Controller View
  • 185. Model Service Layer Controller View
  • 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(); }
  • 193. Service class TodoService { function generateReport() { $data = $this->repository->performSomeCrazyQuery(); return new AnnualGoalReport($data); } }
  • 195. Reverse it: DTOs not for output...
  • 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 execute($command) { } }
  • 206. Service class TodoListService { function execute($command) { get_class($command); } }
  • 207. Service class TodoListService { function execute($command) { $command->getName(); } }
  • 208. Service class TodoListService { function execute($command) { $command->execute(); } }
  • 209. Service class TodoListService { function execute($command) { } }
  • 210. What goes in a handler?
  • 211. Handler class TodoListHandler { function handleAddTask($cmd) { $list = $this->repository->findById($cmd->todoListId); $list->addTask($cmd->description, $cmd->priority); } function handleCompleteTask($command) function handleRemoveTask($command) }
  • 212. Service class TodoListService { function execute($command) { } }
  • 213. Service class CommandBus { function execute($command) { } }
  • 214. Service class MyCommandBus implements CommandBus { function execute($command) { } }
  • 215. Service class ValidatingCommandBus implements CommandBus { function execute($command) { if (!$this->validator->isValid($command)) { throw new InvalidCommandException(); } $this->innerCommandBus->execute($command); } }
  • 216. Command class AddTaskCommand { public $description; public $priority; public $todoListId; }
  • 217. Command use SymfonyComponentValidatorConstraints as Assert; class AddTaskCommand { /** @AssertLength(max="50") */ public $description; public $priority; public $todoListId; }
  • 219. Fewer Dependencies per class. Simple layers. Easy to test.
  • 220. View Models + Commands
  • 221. Model Service Layer Commands ViewModels Controller View
  • 222. forms templates validators CRUD for the framework. Domain Model for the chewy center. tough logic semantics testing
  • 224. CQRS
  • 225. On the surface, it looks the same.
  • 226. 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'); } }
  • 227. CQS
  • 228. Commands = Change Data Queries = Read Data
  • 229. CQRS
  • 230. Model class TodoList { function rename($name); function addTask($desc, $priority); function getName(); function getTasks(); }
  • 232. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); }
  • 234. Model class TodoList { function rename($name); function addTask($desc, $priority); function getName(); function getTasks(); function getParticipatingUsers(); }
  • 235. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); function getParticipatingUsers(); }
  • 236. Write Model class TodoListModel { function rename($name); function addTask($desc, $priority); } Read Model class TodoListView { function getName(); function getTasks(); function getParticipatingUsers(); } ORM entity 1 Model SQL query N Models
  • 237. Read and Write are two different systems.
  • 239. Same kind of split.
  • 241. A lot of it looks the same.
  • 242. Handler class TodoListHandler { function handleAddTask($cmd) { $list = $this->repository->findById($cmd->todoListId); $list->addTask($cmd->description, $cmd->priority); } }
  • 243. Service class TodoListService { public function findByUser(User $user) { return $this->repository->findByUser($user); } }
  • 244. 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); } }
  • 246. $todoList = new TodoList(); $this->repository->save($todoList); $todoList->getId(); Controller
  • 247. $command = new CreateTodoCommand(UUID::create()); $commandBus->execute($command); $command->uuid; Controller
  • 259. Big mental leap. Usually more LOC. Not for every domain. Can be mixed.
  • 260. Easy to Scale. Bears Complexity. Async Operations. Event Sourcing.
  • 262. CQRS + Event Sourcing
  • 263. Instead of storing the current state in the db...
  • 265. Snapshots Debugging Audit Log Business Intelligence Online/Offline users Retroactively Fix Bugs
  • 266. Google it. Or ask me afterwards.
  • 268. "A foolish consistency is the hobgoblin of little minds." - Ralph Waldo Emerson
  • 271. PHP 3
  • 274. PHP 7
  • 276. Bang for the buck.
  • 278. It IS working for them.
  • 281. Further Reading • codebetter.com/gregyoung • martinfowler.com/tags/domain driven design.html • shawnmc.cool/domain-driven-design • whitewashing.de • verraes.net
  • 282. Thanks To: • Warnar Boekkooi @boekkooi • Daan van Renterghem @DRvanR • Matthijs van den Bos @matthijsvandenb
  • 283. 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
  • 284. joind.in/12101 Ross Tuck rosstuck.com @rosstuck