save repository from save

Post on 14-Feb-2017

4.234 Views

Category:

Software

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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?

top related