SlideShare a Scribd company logo
1 of 284
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 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.
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
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!'); 
} 
}
“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(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'); 
}
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 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(); 
}
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); 
} 
}
$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 
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

More Related Content

What's hot

Redis data modeling examples
Redis data modeling examplesRedis data modeling examples
Redis data modeling examplesTerry Cho
 
Why your Spark job is failing
Why your Spark job is failingWhy your Spark job is failing
Why your Spark job is failingSandy Ryza
 
Storing 16 Bytes at Scale
Storing 16 Bytes at ScaleStoring 16 Bytes at Scale
Storing 16 Bytes at ScaleFabian Reinartz
 
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發Shengyou Fan
 
Serializing EMF models with Xtext
Serializing EMF models with XtextSerializing EMF models with Xtext
Serializing EMF models with Xtextmeysholdt
 
Idiomatic Kotlin
Idiomatic KotlinIdiomatic Kotlin
Idiomatic Kotlinintelliyole
 
Sequence and Traverse - Part 1
Sequence and Traverse - Part 1Sequence and Traverse - Part 1
Sequence and Traverse - Part 1Philip Schwarz
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Julien Truffaut
 
Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Jorge Vásquez
 
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Edureka!
 
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기OKKY
 
Where is my bottleneck? Performance troubleshooting in Flink
Where is my bottleneck? Performance troubleshooting in FlinkWhere is my bottleneck? Performance troubleshooting in Flink
Where is my bottleneck? Performance troubleshooting in FlinkFlink Forward
 
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015Advanced Apache Spark Meetup Project Tungsten Nov 12 2015
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015Chris Fregly
 
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Scott Wlaschin
 
Firebase remote config tips & tricks
Firebase remote config tips & tricksFirebase remote config tips & tricks
Firebase remote config tips & tricksGameCamp
 
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergen
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike SteenbergenMeet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergen
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergendistributed matters
 

What's hot (20)

Redis data modeling examples
Redis data modeling examplesRedis data modeling examples
Redis data modeling examples
 
Why your Spark job is failing
Why your Spark job is failingWhy your Spark job is failing
Why your Spark job is failing
 
Storing 16 Bytes at Scale
Storing 16 Bytes at ScaleStoring 16 Bytes at Scale
Storing 16 Bytes at Scale
 
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發
PHPCon China 2016 - 從學徒變大師:談 Laravel 框架擴充與套件開發
 
Serializing EMF models with Xtext
Serializing EMF models with XtextSerializing EMF models with Xtext
Serializing EMF models with Xtext
 
Idiomatic Kotlin
Idiomatic KotlinIdiomatic Kotlin
Idiomatic Kotlin
 
Redux Thunk
Redux ThunkRedux Thunk
Redux Thunk
 
PostgreSQL
PostgreSQLPostgreSQL
PostgreSQL
 
Sequence and Traverse - Part 1
Sequence and Traverse - Part 1Sequence and Traverse - Part 1
Sequence and Traverse - Part 1
 
Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!Implicit parameters, when to use them (or not)!
Implicit parameters, when to use them (or not)!
 
Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0
 
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
Lambda Expressions in Java | Java Lambda Tutorial | Java Certification Traini...
 
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKYCON] 정진욱 - 테스트하기 쉬운 코드로 개발하기
 
Unit Testing in Java
Unit Testing in JavaUnit Testing in Java
Unit Testing in Java
 
Where is my bottleneck? Performance troubleshooting in Flink
Where is my bottleneck? Performance troubleshooting in FlinkWhere is my bottleneck? Performance troubleshooting in Flink
Where is my bottleneck? Performance troubleshooting in Flink
 
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015Advanced Apache Spark Meetup Project Tungsten Nov 12 2015
Advanced Apache Spark Meetup Project Tungsten Nov 12 2015
 
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
 
Firebase remote config tips & tricks
Firebase remote config tips & tricksFirebase remote config tips & tricks
Firebase remote config tips & tricks
 
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergen
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike SteenbergenMeet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergen
Meet Spilo, Zalando’s HIGH-AVAILABLE POSTGRESQL CLUSTER - Feike Steenbergen
 
JavaScript Inheritance
JavaScript InheritanceJavaScript Inheritance
JavaScript Inheritance
 

Viewers also liked

Enterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesAaron Saray
 
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
 
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...
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...Aaron Saray
 
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
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome TownRoss Tuck
 
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
 
Building Data Mapper PHP5
Building Data Mapper PHP5Building Data Mapper PHP5
Building Data Mapper PHP5Vance Lucas
 
Anatomy of a Modern PHP Application Architecture
Anatomy of a Modern PHP Application Architecture Anatomy of a Modern PHP Application Architecture
Anatomy of a Modern PHP Application Architecture AppDynamics
 
Composer in monolithic repositories
Composer in monolithic repositoriesComposer in monolithic repositories
Composer in monolithic repositoriesSten Hiedel
 
Implementing DDD Concepts in PHP
Implementing DDD Concepts in PHPImplementing DDD Concepts in PHP
Implementing DDD Concepts in PHPSteve Rhoades
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravelwajrcs
 
Software Design Patterns in Laravel 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 SparksPhill Sparks
 
Capturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQLCapturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQLRonald Bradford
 
Midwest php 7 things keynote
Midwest php   7 things keynoteMidwest php   7 things keynote
Midwest php 7 things keynoteAaron Saray
 
Handle complex POST/PATCH requests in RESTful API
Handle complex POST/PATCH requests in RESTful APIHandle complex POST/PATCH requests in RESTful API
Handle complex POST/PATCH requests in RESTful APIfightmaster
 
Mysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extensionMysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extensionjulien pauli
 

Viewers also liked (20)

Enterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and services
 
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
 
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...
Enterprise PHP Architecture through Design Patterns and Modularization (Midwe...
 
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
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
Building Data Mapper PHP5
Building Data Mapper PHP5Building Data Mapper PHP5
Building Data Mapper PHP5
 
Anatomy of a Modern PHP Application Architecture
Anatomy of a Modern PHP Application Architecture Anatomy of a Modern PHP Application Architecture
Anatomy of a Modern PHP Application Architecture
 
Composer in monolithic repositories
Composer in monolithic repositoriesComposer in monolithic repositories
Composer in monolithic repositories
 
Implementing DDD Concepts in PHP
Implementing DDD Concepts in PHPImplementing DDD Concepts in PHP
Implementing DDD Concepts in PHP
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
 
Software Design Patterns in Laravel 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
 
Capturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQLCapturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQL
 
Canopen
CanopenCanopen
Canopen
 
Midwest php 7 things keynote
Midwest php   7 things keynoteMidwest php   7 things keynote
Midwest php 7 things keynote
 
Handle complex POST/PATCH requests in RESTful API
Handle complex POST/PATCH requests in RESTful APIHandle complex POST/PATCH requests in RESTful API
Handle complex POST/PATCH requests in RESTful API
 
Mysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extensionMysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extension
 
Clean architecture - PHP
Clean architecture - PHPClean architecture - PHP
Clean architecture - PHP
 
What is CANopen? | ElmoMC
What is CANopen? | ElmoMCWhat is CANopen? | ElmoMC
What is CANopen? | ElmoMC
 
Save Repository From Save
Save Repository From SaveSave Repository From Save
Save Repository From Save
 

Similar to Models and Service Layers, Hemoglobin and Hobgoblins

Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm OldRoss Tuck
 
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
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
 
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
 
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
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
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
 
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
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammarsabrummett
 
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
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2eugenio pombi
 

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

Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
 
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
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
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
 
PHPSpec BDD for PHP
PHPSpec BDD for PHPPHPSpec BDD for PHP
PHPSpec BDD for PHP
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
 
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
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Drupal7 dbtng
Drupal7  dbtngDrupal7  dbtng
Drupal7 dbtng
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
 
Oops in php
Oops in phpOops in php
Oops in php
 
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
 
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
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammars
 
Smelling your code
Smelling your codeSmelling your code
Smelling your code
 
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
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
 

Recently uploaded

Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
"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
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
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
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 

Recently uploaded (20)

Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
"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
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
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
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 

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
  • 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(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'); }
  • 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 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(); }
  • 233.
  • 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); } }
  • 245.
  • 246. $todoList = new TodoList(); $this->repository->save($todoList); $todoList->getId(); Controller
  • 247. $command = new CreateTodoCommand(UUID::create()); $commandBus->execute($command); $command->uuid; Controller
  • 250.
  • 251.
  • 252.
  • 253.
  • 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