SlideShare a Scribd company logo
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
Ad

More Related Content

What's hot (18)

Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
Arturo Herrero
 
Deep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explainedDeep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explained
Holger Schill
 
C# operators
C# operatorsC# operators
C# operators
baabtra.com - No. 1 supplier of quality freshers
 
springdatajpa-up.pdf
springdatajpa-up.pdfspringdatajpa-up.pdf
springdatajpa-up.pdf
ssuser0562f1
 
Java static keyword
Java static keywordJava static keyword
Java static keyword
Lovely Professional University
 
Doctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document MapperDoctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document Mapper
Jonathan Wage
 
C 210 manual
C 210 manualC 210 manual
C 210 manual
rimagot
 
Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional World
Debasish Ghosh
 
Structure in c sharp
Structure in c sharpStructure in c sharp
Structure in c sharp
rrajeshvhadlure
 
How To Use IO Monads in Scala?
 How To Use IO Monads in Scala? How To Use IO Monads in Scala?
How To Use IO Monads in Scala?
Knoldus Inc.
 
Lab manual asp.net
Lab manual asp.netLab manual asp.net
Lab manual asp.net
Vivek Kumar Sinha
 
Abreviaturasnotams oaci 8400
Abreviaturasnotams oaci 8400Abreviaturasnotams oaci 8400
Abreviaturasnotams oaci 8400
Arlet Ballesteros
 
New Technology: 核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
New Technology:  核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區New Technology:  核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
New Technology: 核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
Taiwan Heart Rhythm Society
 
Classes&objects
Classes&objectsClasses&objects
Classes&objects
M Vishnuvardhan Reddy
 
Android animations
Android animationsAndroid animations
Android animations
Kumar
 
Super keyword in java
Super keyword in javaSuper keyword in java
Super keyword in java
Hitesh Kumar
 
Sql functions
Sql functionsSql functions
Sql functions
Ankit Dubey
 
Getting started-with-grails-second-edition
Getting started-with-grails-second-editionGetting started-with-grails-second-edition
Getting started-with-grails-second-edition
Hồng Sơn
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
Arturo Herrero
 
Deep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explainedDeep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explained
Holger Schill
 
springdatajpa-up.pdf
springdatajpa-up.pdfspringdatajpa-up.pdf
springdatajpa-up.pdf
ssuser0562f1
 
Doctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document MapperDoctrine MongoDB Object Document Mapper
Doctrine MongoDB Object Document Mapper
Jonathan Wage
 
C 210 manual
C 210 manualC 210 manual
C 210 manual
rimagot
 
Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional World
Debasish Ghosh
 
How To Use IO Monads in Scala?
 How To Use IO Monads in Scala? How To Use IO Monads in Scala?
How To Use IO Monads in Scala?
Knoldus Inc.
 
Abreviaturasnotams oaci 8400
Abreviaturasnotams oaci 8400Abreviaturasnotams oaci 8400
Abreviaturasnotams oaci 8400
Arlet Ballesteros
 
New Technology: 核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
New Technology:  核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區New Technology:  核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
New Technology: 核磁共振相容節律器 - ”MRI Standard of Care for Pacemaker”)_20130907北區
Taiwan Heart Rhythm Society
 
Android animations
Android animationsAndroid animations
Android animations
Kumar
 
Super keyword in java
Super keyword in javaSuper keyword in java
Super keyword in java
Hitesh Kumar
 
Getting started-with-grails-second-edition
Getting started-with-grails-second-editionGetting started-with-grails-second-edition
Getting started-with-grails-second-edition
Hồng Sơn
 

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
Aaron 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 php
Leonardo 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.5
Leonardo Proietti
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
Ross 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 PHP5
Vance 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 repositories
Sten Hiedel
 
Implementing DDD Concepts in PHP
Implementing DDD Concepts in PHPImplementing DDD Concepts in PHP
Implementing DDD Concepts in PHP
Steve Rhoades
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
wajrcs
 
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
Phill Sparks
 
Capturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQLCapturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQL
Ronald Bradford
 
Midwest php 7 things keynote
Midwest php   7 things keynoteMidwest php   7 things keynote
Midwest php 7 things keynote
Aaron 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 API
fightmaster
 
Mysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extensionMysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extension
julien pauli
 
Clean architecture - PHP
Clean architecture - PHPClean architecture - PHP
Clean architecture - PHP
Michal Giergielewicz
 
What is CANopen? | ElmoMC
What is CANopen? | ElmoMCWhat is CANopen? | ElmoMC
What is CANopen? | ElmoMC
Elmo Motion Control
 
Dealing with fear in legacy projects #PHPDS15
Dealing with fear in legacy projects #PHPDS15Dealing with fear in legacy projects #PHPDS15
Dealing with fear in legacy projects #PHPDS15
Aitor Suso Gáceta
 
Enterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and services
Aaron 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 php
Leonardo 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.5
Leonardo Proietti
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
Ross 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 PHP5
Vance 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 repositories
Sten Hiedel
 
Implementing DDD Concepts in PHP
Implementing DDD Concepts in PHPImplementing DDD Concepts in PHP
Implementing DDD Concepts in PHP
Steve Rhoades
 
Domain Driven Design using Laravel
Domain Driven Design using LaravelDomain Driven Design using Laravel
Domain Driven Design using Laravel
wajrcs
 
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
Phill Sparks
 
Capturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQLCapturing, Analyzing and Optimizing MySQL
Capturing, Analyzing and Optimizing MySQL
Ronald Bradford
 
Midwest php 7 things keynote
Midwest php   7 things keynoteMidwest php   7 things keynote
Midwest php 7 things keynote
Aaron 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 API
fightmaster
 
Mysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extensionMysqlnd, an unknown powerful PHP extension
Mysqlnd, an unknown powerful PHP extension
julien pauli
 
Dealing with fear in legacy projects #PHPDS15
Dealing with fear in legacy projects #PHPDS15Dealing with fear in legacy projects #PHPDS15
Dealing with fear in legacy projects #PHPDS15
Aitor Suso Gáceta
 
Ad

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
Ross Tuck
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
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
Divante
 
PHPSpec BDD for PHP
PHPSpec BDD for PHPPHPSpec BDD for PHP
PHPSpec BDD for PHP
Marcello Duarte
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
Marcello Duarte
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
olvlvl
 
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
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
Hugo Hamon
 
Drupal7 dbtng
Drupal7  dbtngDrupal7  dbtng
Drupal7 dbtng
Nicolas Leroy
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
Krzysztof Menżyk
 
Oops in php
Oops in phpOops in php
Oops in php
Gourishankar R Pujar
 
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
Alessandro 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.4
Jeff Carouth
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammars
abrummett
 
Smelling your code
Smelling your codeSmelling your code
Smelling your code
Raju Mazumder
 
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
Rafael Dohms
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi
 
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
Ross Tuck
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo 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 scenarios
Divante
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
olvlvl
 
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
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
Hugo 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-2011
Alessandro 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.4
Jeff Carouth
 
Parsing with Perl6 Grammars
Parsing with Perl6 GrammarsParsing with Perl6 Grammars
Parsing with Perl6 Grammars
abrummett
 
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
Rafael Dohms
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi
 
Ad

Recently uploaded (20)

Greenhouse_Monitoring_Presentation.pptx.
Greenhouse_Monitoring_Presentation.pptx.Greenhouse_Monitoring_Presentation.pptx.
Greenhouse_Monitoring_Presentation.pptx.
hpbmnnxrvb
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
Semantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AISemantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AI
artmondano
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded DevelopersLinux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Toradex
 
Drupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy ConsumptionDrupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy Consumption
Exove
 
Linux Professional Institute LPIC-1 Exam.pdf
Linux Professional Institute LPIC-1 Exam.pdfLinux Professional Institute LPIC-1 Exam.pdf
Linux Professional Institute LPIC-1 Exam.pdf
RHCSA Guru
 
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptxIncreasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Anoop Ashok
 
Big Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur MorganBig Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptxSpecial Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
shyamraj55
 
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
Alan Dix
 
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
Mobile App Development Company in Saudi Arabia
Mobile App Development Company in Saudi ArabiaMobile App Development Company in Saudi Arabia
Mobile App Development Company in Saudi Arabia
Steve Jonas
 
Greenhouse_Monitoring_Presentation.pptx.
Greenhouse_Monitoring_Presentation.pptx.Greenhouse_Monitoring_Presentation.pptx.
Greenhouse_Monitoring_Presentation.pptx.
hpbmnnxrvb
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
Semantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AISemantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AI
artmondano
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded DevelopersLinux Support for SMARC: How Toradex Empowers Embedded Developers
Linux Support for SMARC: How Toradex Empowers Embedded Developers
Toradex
 
Drupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy ConsumptionDrupalcamp Finland – Measuring Front-end Energy Consumption
Drupalcamp Finland – Measuring Front-end Energy Consumption
Exove
 
Linux Professional Institute LPIC-1 Exam.pdf
Linux Professional Institute LPIC-1 Exam.pdfLinux Professional Institute LPIC-1 Exam.pdf
Linux Professional Institute LPIC-1 Exam.pdf
RHCSA Guru
 
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptxIncreasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Increasing Retail Store Efficiency How can Planograms Save Time and Money.pptx
Anoop Ashok
 
Big Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur MorganBig Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptxSpecial Meetup Edition - TDX Bengaluru Meetup #52.pptx
Special Meetup Edition - TDX Bengaluru Meetup #52.pptx
shyamraj55
 
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul Shares 5 Steps to Implement AI Agents for Maximum Business Efficien...
Noah Loul
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
AI Changes Everything – Talk at Cardiff Metropolitan University, 29th April 2...
Alan Dix
 
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
Mobile App Development Company in Saudi Arabia
Mobile App Development Company in Saudi ArabiaMobile App Development Company in Saudi Arabia
Mobile App Development Company in Saudi Arabia
Steve Jonas
 

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