SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 30 day free trial to unlock unlimited reading.
Models and Service Layers, Hemoglobin and Hobgoblins
As presented at ZendCon 2014, AmsterdamPHP, PHPBenelux 2014, Sweetlake PHP and PHP Northwest 2013, an overview of some different patterns for integrating and managing logic throughout your application.
As presented at ZendCon 2014, AmsterdamPHP, PHPBenelux 2014, Sweetlake PHP and PHP Northwest 2013, an overview of some different patterns for integrating and managing logic throughout your application.
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
9.
Model
class TodoList {
function setName($name);
function getName();
function setStatus($status);
function getStatus();
function addTask($task);
function setTasks($tasks);
function getTasks();
}
12.
“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
13.
Our industry standard i s a n a n t i p a t t ern.
19.
Model
class TodoList {
function setName($name);
function getName();
function setStatus($status);
function getStatus();
function addTask($task);
function setTasks($tasks);
function getTasks();
}
20.
Model
class Task {
function setDescription($desc);
function getDescription();
function setPriority($priority);
function getPriority();
}
21.
An ORM that's not Doctrine 2.
A framework that's not Symfony2.
I promise.
32.
• 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
43.
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');
}
44.
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!');
}
}
45.
Service
class TodoService {
function findById($id) {
return $this->repository->findById($id);
}
}
46.
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');
}
47.
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);
48.
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
49.
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);
50.
Service
class TodoService {
function findById($id) {
return $this->repository->findById($id);
}
}
51.
Service
class TodoService {
function findById($id) {
$todo = $this->repository->findById($id);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
not http exception
58.
Service
class TodoService {
public function findLatestLists() {
return $this->repository->findLatestLists();
}
}
59.
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;
}
}
66.
Model
class TodoList {
function setName($name);
function getName();
function setStatus($status);
function getStatus();
function setTasks($tasks);
function getTasks();
}
68.
Model
class TodoList {
function setName($name);
function getName();
function setStatus($status);
function getStatus();
function setTasks($tasks);
function getTasks();
}
Where's mah logic?
69.
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!');
}
}
70.
“Organizes business logic by procedures
where each procedure handles a single
request from the presentation.”
-Fowler
83.
addTask()
findById()
findLatestLists()
Service
write
read
read
84.
Remodeling our Reading
by
Refactoring our Repository
Redux
85.
Service
class TodoService {
function findById($id) {
$todo = $this->repository->findById($id);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
86.
Repository
interface TodoRepository {
public function findById($id);
public function findLatestLists();
}
87.
Repository
class TodoDbRepository implements TodoRepository {
public function findById($id) {
$todo = $this->db->select(...);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
88.
Repository
class TodoDbRepository implements TodoRepository {
public function findById($id) {
$todo = $this->db->select(...);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
raw db connection
89.
Repository
class TodoDbRepository implements TodoRepository {
public function findById($id) {
$todo = $this->repository->find($id);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
FIXED
90.
Repository
interface TodoRepository {
public function findById($id);
public function findLatestLists();
}
91.
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);
}
92.
Repository
interface TodoRepository {
public function findById($id);
public function findLatestLists();
}
93.
Service
class TodoService {
function findById($id) {
$todo = $this->repository->findById($id);
if (!$todo) {
throw new TodoNotFoundException();
}
return $todo;
}
}
94.
Service
class TodoService {
function findById($id) {
return $this->repository->findById($id);
}
}
95.
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;
}
}
96.
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;
}
}
97.
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
98.
DI Layer
new TodoService(
new CachingTodoRepository(
new TodoDbRepository(
$entityManager->getRepository('TodoList')
)
)
)
118.
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!');
}
}
119.
Model
class TodoList {
function addTask(Task $task) {
$this->tasks[] = $task;
}
}
120.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task();
$task->setDescription($desc);
$task->setPriority($priority);
$this->tasks[] = $task;
}
}
121.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task($desc, $priority);
$this->tasks[] = $task;
}
}
122.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task($desc, $priority, $this);
$this->tasks[] = $task;
}
}
ORM allowance
123.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task($desc, $priority);
$this->tasks[] = $task;
}
}
124.
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!');
}
}
125.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task($desc, $priority);
$this->tasks[] = $task;
}
}
126.
Model
class TodoList {
function addTask($desc, $priority) {
$task = new Task($desc, $priority);
$this->tasks[] = $task;
if (count($this->tasks) > 10) {
$this->status = static::UNREALISTIC;
}
}
}
153.
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!
154.
Service
class TodoListService {
protected $dependency1;
protected $dependency2;
protected $dependency3;
protected $dependency4;
protected $dependency5;
protected $dependency6;
}
Big ball of mud
in the making
155.
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
174.
Service
class TodoService {
function findById($id) {
$todoList = $this->repository->findById($id);
return $todoList;
}
}
175.
Service
class TodoService {
function findById($id) {
$todoList = $this->repository->findById($id);
return new TodoDTO($todoList);
}
}
176.
TodoDTO
class TodoDTO {
public function getName();
public function getStatus();
public function getMostRecentTask();
}
177.
Service
class TodoService {
function generateReport() {
$data = $this->repository->performSomeCrazyQuery();
return new AnnualGoalReport($data);
}
}
193.
Handler
class TodoListHandler {
function handleAddTask($cmd) {
$list = $this->repository->findById($cmd->todoListId);
$list->addTask($cmd->description, $cmd->priority);
}
function handleCompleteTask($command)
function handleRemoveTask($command)
}
194.
Service
class TodoListService {
function execute($command) {
}
}
195.
Service
class CommandBus {
function execute($command) {
}
}
196.
Service
class MyCommandBus implements CommandBus {
function execute($command) {
}
}
197.
Service
class ValidatingCommandBus implements CommandBus {
function execute($command) {
if (!$this->validator->isValid($command)) {
throw new InvalidCommandException();
}
$this->innerCommandBus->execute($command);
}
}
198.
Command
class AddTaskCommand {
public $description;
public $priority;
public $todoListId;
}
199.
Command
use SymfonyComponentValidatorConstraints as Assert;
class AddTaskCommand {
/** @AssertLength(max="50") */
public $description;
public $priority;
public $todoListId;
}
214.
Write Model
class TodoListModel {
function rename($name);
function addTask($desc, $priority);
}
Read Model
class TodoListView {
function getName();
function getTasks();
}
215.
Model
class TodoList {
function rename($name);
function addTask($desc, $priority);
function getName();
function getTasks();
function getParticipatingUsers();
}
216.
Write Model
class TodoListModel {
function rename($name);
function addTask($desc, $priority);
}
Read Model
class TodoListView {
function getName();
function getTasks();
function getParticipatingUsers();
}
217.
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
223.
Handler
class TodoListHandler {
function handleAddTask($cmd) {
$list = $this->repository->findById($cmd->todoListId);
$list->addTask($cmd->description, $cmd->priority);
}
}
224.
Service
class TodoListService {
public function findByUser(User $user) {
return $this->repository->findByUser($user);
}
}
225.
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);
}
}
226.
$todoList = new TodoList();
$this->repository->save($todoList);
$todoList->getId();
Controller
227.
$command = new CreateTodoCommand(UUID::create());
$commandBus->execute($command);
$command->uuid;
Controller