Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Save Repository From Save

7,472 views

Published on

How to work with repositories that are not aware of any persistence layer.

Published in: Software

Save Repository From Save

  1. 1. Save repository from save
  2. 2. Norbert Orzechowicz
 @norzechowicz
  3. 3. Repository Here I should add some complicated description of repository pattern. 
 But instead of that…
  4. 4. Keep calm and think about… bookshelf
  5. 5. Bookshelf specification • it contains books • it contains books from mixed categories • it allows you to add new books when it’s not full • it allows you to find and pick a book or books • it allows you to remove specific book or books from it
  6. 6. Bookshelf specification • it contains books • it contains books from mixed categories • it allows you to add new books when it’s not full • it allows you to find and pick specific books/book • it allows you to remove specific books/book from it
  7. 7. <?php interface Bookshelf { /** * @param Book $book * @return bool */ public function contains(Book $book); /** * @param Book $book */ public function add(Book $book); /** * @param Title $title * @return Book */ public function findBy(Title $title); /** * @param Book $book */ public function remove(Book $book); }
  8. 8. Conclusions • bookshelf acts as a collection • bookshelf does not handle book changes • bookshelf implements repository pattern
  9. 9. Repository Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. Martin Fowler
  10. 10. From pure data to entity
  11. 11. Database +----+-------------------+------------+----------------------------------+ | id | title | author | description | +----+-------------------+------------+----------------------------------+ | 1 | 50 shades of grey | E.L. James | Fifty Shades of Grey is a... | +----+-------------------+------------+----------------------------------+
  12. 12. But first let me introduce you few building blocks
  13. 13. Data Access Object <?php class BookDataAccessObject { public function getByTitle($title);
 
 public function saveNew(array $data); }
  14. 14. Hydrator <?php class BookHydrator { /** * @param array $data * @return Book */ public function hydrateBook($data = []); }
  15. 15. Converter <?php class BookConverter { /** * @param Book $book * @return array */ public function toArray(Book $book); }
  16. 16. Do you already know where to assemble them?
  17. 17. Book Repository <?php class Bookshelf { /** * @param Title $title * @return Book */ public function findBy(Title $title) { $bookData = $this->dao->getByTitle((string) $title); $book = $this->hydrator->hydrateBook($bookData); return $book; } }
  18. 18. Book Repository <?php class Bookshelf { /** * @param Book $book */ public function add(Book $book) { $data = $this->converter->toArray($book); $this->dao->saveNew($data); } }
  19. 19. What about changes?
  20. 20. Well if you don’t ask your bookshelf to handle changes why would you like to ask repository for that?
  21. 21. Example of repository with too many responsibilities <?php interface Bookshelf { public function contains(Book $book); public function add(Book $book); public function findBy(Title $title); public function remove(Book $book); /** * Sometimes also Update/Handle/Persist * @param Book $book */ public function save(Book $book); }
  22. 22. Persistence repositories? Excuses…
  23. 23. So how to handle changes?
  24. 24. Unit of Work Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. Martin Fowler
  25. 25. Unit of Work <?php class UnitOfWork { public function watch($entity); public function remove($entity); public function commit(); public function rollback(); }
  26. 26. UoW expected extension points • Entity Created • Entity Updated • Entity Removed
  27. 27. Repository & UoW <?php class BookRepository { public function add(Book $book) { $this->uow->watch($book); } public function findBy(Title $title) { $bookData = $this->dao->getByTitle((string) $title); $book = $this->hydrator->hydrateBook($bookData); $this->uow->watch($book); return $book; } }
  28. 28. Commit? (save changes, create new entities, delete entities)
  29. 29. Dummy Update Example <?php class BookController { public function updateBookDescriptionAction(Request $request, $title) { $book = $this->get('book.repository')->findBy(new Title($title)); $form = $this->createForm(new FormType()); $form->handle($request); if ($form->isValid()) { $book->updateDescription($form->get('description')->getData()); $this->get('unit_of_work')->commit(); return $this->redirect($this->generateUrl('homepage'); } return $this->render('book/updateDescription.html.twig', [ 'book' => $book, 'form' => $form->createView() ]); } }
  30. 30. Dummy Remove Example <?php class BookController { public function removeBookAction($title) { $book = $this->get('book.repository')->remove(new Title($title)); $this->get('unit_of_work')->commit(); return $this->render('book/updateDescription.html.twig', [ 'book' => $book, 'form' => $form->createView() ]); } }
  31. 31. Rollback?
  32. 32. In most web applications there is no need for rollback because objects become useless when response is created.
  33. 33. Still wanna see more? (more of abstraction of course)
  34. 34. https://github.com/isolate-org Isolate is a PHP framework that will help you in isolating business logic from persistence layer. current version: 1.0.0-alpha2 https://twitter.com/isolate_php
  35. 35. Isolate Think about it as a registry of persistence contexts
  36. 36. Persistence Context It’s responsible for opening and closing transactions
  37. 37. Transaction It’s an abstraction over the unit of work (more or less)
  38. 38. Repository with Isolate <?php class BookRepository { public function add(Book $book) { $persistenceContext = $this->get('isolate')->getContext(); $transaction = $persistenceContext->openTransaction(); $transaction->persist($book); } public function findBy(Title $title) { $bookData = $this->dao->getByTitle((string) $title); $book = $this->hydrator->hydrateBook($bookData); $persistenceContext = $this->get('isolate')->getContext(); if ($persistenceContext->hasOpenTransaction()) { $transaction = $persistenceContext->openTransaction(); $transaction->persist($book); } return $book; } }
  39. 39. Update action example <?php class BookController { public function updateBookDescriptionAction(Request $request, $title) { $this->get('isolate')->getContext()->openTransaction(); $book = $this->get('book.repository')->findBy(new Title($title)); $form = $this->createForm(new FormType()); $form->handle($request); if ($form->isValid()) { $book->updateDescription($form->get('description')->getData()); $this->get('isolate')->getContext()->closeTransaction(); return $this->redirect($this->generateUrl('homepage'); } return $this->render('book/updateDescription.html.twig', [ 'book' => $book, 'form' => $form->createView() ]); } }
  40. 40. But there is more code in this example….
  41. 41. Refactoring • implement commands and command handlers (tactician) • implement middlewares (tactician) • move isolate transaction management to middlewares • move business logic into command handlers
  42. 42. Transaction middleware <?php /** * This code is available in Isolate Tactician Bridge */ class TransactionMiddleware implements Middleware { public function execute($command, callable $next) { $context = $this->isolate->getContext(); $transaction = $context->openTransaction(); try { $returnValue = $next($command); $context->closeTransaction(); return $returnValue; } catch (Exception $e) { $transaction->rollback(); throw $e; } } }
  43. 43. Update book command handler <?php class UpdateBookHandler { public function handle(UpdateBookCommand $command) { $book = $this->bookshelf->findBy(new Title($command->getTitle())); if (empty($book)) { throw new BookNotFoundException(); } $book->updateDescription($command->getDescription()); } }
  44. 44. Update book controller <?php class BookController { public function updateBookAction(Request $request, $title) { $form = $this->createForm(new FormType()); $form->handle($request); if ($form->isValid()) { $command = new UpdateBookCommand($title, $form->get('description')->getData()); $this->get('command_bus')->handle($command); return $this->redirect($this->generateUrl('homepage'); } return $this->render('book/updateDescription.html.twig', [ 'book' => $book, 'form' => $form->createView() ]); } }
  45. 45. Isolate extensions • Tactician Bridge
 https://github.com/isolate-org/tactician-bridge • Doctrine Bridge
 https://github.com/isolate-org/doctrine-bridge • Symfony Bundle
 https://github.com/isolate-org/symfony-bundle
 

  46. 46. Advantages • application totally decoupled from storage • possibility to switch storage for tests • possibility to replace ORM with ODM, webservice, filesystem or anything else • possibility to delay a decision about storage type • possibility to increase performance by replacing auto- generated sql queries with custom, optimized queries • clean architecture not impacted by persistence layer
  47. 47. Disadvantages • higher entry point for junior developers • require better understanding of how your storage works
  48. 48. Questions?

×