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

You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix itRafael Dohms
 
One Monad to Rule Them All
One Monad to Rule Them AllOne Monad to Rule Them All
One Monad to Rule Them AllJohn De Goes
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented ProgrammingScott Wlaschin
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvCodelyTV
 
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration수홍 이
 
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQAFest
 
Sling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatSling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatAEM HUB
 
Clean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software CraftsmanshipClean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software CraftsmanshipIvan Paulovich
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Scott Wlaschin
 
Page object with selenide
Page object with selenidePage object with selenide
Page object with selenideCOMAQA.BY
 
QA Fest 2019. Андрей Солнцев. Selenide для профи
QA Fest 2019. Андрей Солнцев. Selenide для профиQA Fest 2019. Андрей Солнцев. Selenide для профи
QA Fest 2019. Андрей Солнцев. Selenide для профиQAFest
 
non-strict functions, bottom and scala by-name parameters
non-strict functions, bottom and scala by-name parametersnon-strict functions, bottom and scala by-name parameters
non-strict functions, bottom and scala by-name parametersPhilip Schwarz
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代Shengyou Fan
 
DSpace-CRIS: new features and contribution to the DSpace mainstream
DSpace-CRIS: new features and contribution to the DSpace mainstreamDSpace-CRIS: new features and contribution to the DSpace mainstream
DSpace-CRIS: new features and contribution to the DSpace mainstream4Science
 
Javascript this keyword
Javascript this keywordJavascript this keyword
Javascript this keywordPham Huy Tung
 
Reactive programming with RxJava
Reactive programming with RxJavaReactive programming with RxJava
Reactive programming with RxJavaJobaer Chowdhury
 
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경Mintak Son
 

What's hot (20)

You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
One Monad to Rule Them All
One Monad to Rule Them AllOne Monad to Rule Them All
One Monad to Rule Them All
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented Programming
 
Applicative style programming
Applicative style programmingApplicative style programming
Applicative style programming
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration
스프링캠프 2016 발표 - Deep dive into spring boot autoconfiguration
 
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
 
Sling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak KhetawatSling Models Using Sightly and JSP by Deepak Khetawat
Sling Models Using Sightly and JSP by Deepak Khetawat
 
Clean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software CraftsmanshipClean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software Craftsmanship
 
Javascript Design Patterns
Javascript Design PatternsJavascript Design Patterns
Javascript Design Patterns
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
 
Page object with selenide
Page object with selenidePage object with selenide
Page object with selenide
 
QA Fest 2019. Андрей Солнцев. Selenide для профи
QA Fest 2019. Андрей Солнцев. Selenide для профиQA Fest 2019. Андрей Солнцев. Selenide для профи
QA Fest 2019. Андрей Солнцев. Selenide для профи
 
non-strict functions, bottom and scala by-name parameters
non-strict functions, bottom and scala by-name parametersnon-strict functions, bottom and scala by-name parameters
non-strict functions, bottom and scala by-name parameters
 
JavaScript JQUERY AJAX
JavaScript JQUERY AJAXJavaScript JQUERY AJAX
JavaScript JQUERY AJAX
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
 
DSpace-CRIS: new features and contribution to the DSpace mainstream
DSpace-CRIS: new features and contribution to the DSpace mainstreamDSpace-CRIS: new features and contribution to the DSpace mainstream
DSpace-CRIS: new features and contribution to the DSpace mainstream
 
Javascript this keyword
Javascript this keywordJavascript this keyword
Javascript this keyword
 
Reactive programming with RxJava
Reactive programming with RxJavaReactive programming with RxJava
Reactive programming with RxJava
 
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경
LetSwift 2017 - 토스 iOS 앱의 개발/배포 환경
 

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
 
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 #PHPDS15Aitor Suso Gáceta
 

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

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

Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
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
 
"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
 
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
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
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
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
"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
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 

Recently uploaded (20)

Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 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
 
"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
 
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
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
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
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
"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...
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 

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