Save Repository From Save
-
Upload
norbert-orzechowicz -
Category
Software
-
view
4.228 -
download
2
Transcript of Save Repository From Save
Save repository from save
Norbert Orzechowicz @norzechowicz
Repository Here I should add some complicated
description of repository pattern. But instead of that…
Keep calm and
think about… bookshelf
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
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
<?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); }
Conclusions
• bookshelf acts as a collection
• bookshelf does not handle book changes
• bookshelf implements repository pattern
Repository Mediates between the domain and data mapping layers using a collection-like
interface for accessing domain objects.
Martin Fowler
From pure data to entity
Database+----+-------------------+------------+----------------------------------+ | id | title | author | description | +----+-------------------+------------+----------------------------------+ | 1 | 50 shades of grey | E.L. James | Fifty Shades of Grey is a... | +----+-------------------+------------+----------------------------------+
But first let me introduce you few building blocks
Data Access Object<?php
class BookDataAccessObject { public function getByTitle($title); public function saveNew(array $data); }
Hydrator <?php
class BookHydrator { /** * @param array $data * @return Book */ public function hydrateBook($data = []); }
Converter <?php
class BookConverter { /** * @param Book $book * @return array */ public function toArray(Book $book); }
Do you already know where to assemble them?
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; } }
Book Repository<?php
class Bookshelf { /** * @param Book $book */ public function add(Book $book) { $data = $this->converter->toArray($book); $this->dao->saveNew($data); } }
What about changes?
Well if you don’t ask your bookshelf to handle changes why would you
like to ask repository for that?
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); }
Persistence repositories?
Excuses…
So how to handle changes?
Unit of WorkMaintains a list of objects affected by
a business transaction and coordinates the writing out of changes and the resolution
of concurrency problems.
Martin Fowler
Unit of Work<?php
class UnitOfWork { public function watch($entity);
public function remove($entity);
public function commit();
public function rollback(); }
UoW expected extension points
• Entity Created
• Entity Updated
• Entity Removed
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; } }
Commit?(save changes, create new entities, delete entities)
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() ]); } }
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() ]); } }
Rollback?
In most web applications there is no need for rollback because objects become useless when
response is created.
Still wanna see more? (more of abstraction of course)
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
IsolateThink about it as a registry of persistence contexts
Persistence ContextIt’s responsible for opening and closing transactions
TransactionIt’s an abstraction over the unit of work (more or less)
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; } }
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() ]); } }
But there is more code in this example….
Refactoring• implement commands and command handlers
(tactician)
• implement middlewares (tactician)
• move isolate transaction management to middlewares
• move business logic into command handlers
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; } } }
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()); } }
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() ]); } }
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
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
Disadvantages
• higher entry point for junior developers
• require better understanding of how your storage works
Questions?