SlideShare a Scribd company logo
1 of 64
Download to read offline
Database? Meh,
implementation detail
Maciej Malarz (@malarzm)
@malarzm
Maciej Malarz (@malarzm)
Maciej Malarz (@malarzm)
Maciej Malarz (@malarzm)
Entity
/** @ORMEntity */
class Submission
{
/** @ORMOneToOne(targetEntity="User") */
private $acceptedBy;
/** @ORMColumn(type="datetime") */
private $acceptedOn;
/** @ORMColumn(type="string") */
private $applicant;
/** @ORMOneToOne(targetEntity="User") */
private $rejectedBy;
/** @ORMColumn(type="datetime") */
private $rejectedOn;
/** @ORMColumn(type="string") */
private $title;
}
Maciej Malarz (@malarzm)
List action
/**
* @Route("/", name="homepage")
*/
public function listAction()
{
$submissions = $this->getDoctrine()->getManager()
->getRepository(Submission::class)
->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
return [ 'submissions' => $submissions ];
}
Maciej Malarz (@malarzm)
View action
/**
* @Route("/{id}", name="submission")
* @Template()
*/
public function viewAction(Request $request)
{
$s = $this->getDoctrine()->getManager()->find(Submission::class, $request->get('id'));
if ($s === null) {
throw $this->createNotFoundException();
}
return [ 'submission' => $s ];
}
/**
* @ParamConverter("submission", class="AppBundleEntitySubmission")
* @Route("/{id}", name="submission")
* @Template
*/
public function viewAction(Submission $submission)
{
return [ 'submission' => $submission ];
}
Maciej Malarz (@malarzm)
Accept & reject actions
/**
* @ParamConverter("submission", class="AppBundleEntitySubmission")
* @Route("/accept/{id}", name="accept")
*/
public function acceptAction(Submission $submission)
{
$submission->setAcceptedBy($this->getUser());
$submission->setAcceptedOn(new DateTime());
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('homepage');
}
/**
* @ParamConverter("submission", class="AppBundleEntitySubmission")
* @Route("/accept/{id}", name="reject")
*/
public function rejectAction(Submission $submission)
{
$submission->setRejectedBy($this->getUser());
$submission->setRejectedOn(new DateTime());
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('homepage');
}
Maciej Malarz (@malarzm)
et voilà!
Maciej Malarz (@malarzm)
Forgot the slug
class SubmissionsSlugger
{
public function prePersist(LifecycleEventArgs $event)
{
/** @var Submission $entity */
$entity = $event->getEntity();
if ( ! ($entity instanceof Submission)) {
return;
}
$entity->setSlug($this->slugify($entity->getTitle()));
}
private function slugify($string)
{
return preg_replace('/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string))));
}
}
Maciej Malarz (@malarzm)
New features
Maciej Malarz (@malarzm)
Oh hey, project was
a success!
Maciej Malarz (@malarzm)
1. Value Objects
Maciej Malarz (@malarzm)
VO candidate
/** @ORMEntity */
class Submission
{
/** @ORMOneToOne(targetEntity="User") */
private $acceptedBy;
/** @ORMColumn(type="datetime") */
private $acceptedOn;
/** @ORMColumn(type="string") */
private $applicant;
/** @ORMOneToOne(targetEntity="User") */
private $rejectedBy;
/** @ORMColumn(type="datetime") */
private $rejectedOn;
/** @ORMColumn(type="string") */
private $title;
}
Maciej Malarz (@malarzm)
Introducing VO
/** @ORMEmbeddable */
abstract class AbstractStatus
{
/** @ORMOneToOne(targetEntity="User") */
private $by;
/** @ORMColumn(type="datetime") */
private $on;
/** @ORMColumn(type="string") */
private $message;
public function __construct(User $by, $message)
{
$this->by = $by;
$this->on = new DateTime();
$this->message = $message;
}
}
class StatusAccepted extends AbstractStatus { }
class StatusCancelled extends AbstractStatus { }
class StatusRejected extends AbstractStatus { }
Maciej Malarz (@malarzm)
Mapping
class Submission
{
/** @ORMColumn(type="string") */
private $applicant;
/** @ORMColumn(type="string") */
private $slug;
/** @ORMEmbedded(class="???") */
private $status;
/** @ORMColumn(type="string") */
private $title;
}
Maciej Malarz (@malarzm)
Actually you can but with
MongoDB ODM ;)
Maciej Malarz (@malarzm)
/** @ORMEmbeddable */
class Status
{
const ACCEPTED = 0;
const CANCELLED = 1;
const REJECTED = 2;
/** @ORMOneToOne(targetEntity="User") */
private $by;
/** @ORMColumn(type="datetime") */
private $on;
/** @ORMColumn(type="string") */
private $message;
/** @ORMColumn(type="integer") */
private $type;
public function __construct($type, User $by, $message)
{
$this->by = $by;
$this->on = new DateTime();
$this->message = $message;
$this->type = $type;
}
}
Not very good on its own...
Maciej Malarz (@malarzm)
A bit better...
/** @ORMEntity */
class Submission
{
/**
* @ORMEmbedded(class="Status")
* @var Status
*/
private $status;
public function getStatus()
{
switch ($this->status->getType()) {
case Status::ACCEPTED:
return new StatusAccepted(/* ... */);
/* ... */
}
}
public function setStatus(Status $status)
{
switch (get_class($status)) {
case StatusAccepted::class:
$this->status = new Status(/* ... */);
break;
/* ... */
}
}
Maciej Malarz (@malarzm)
We can do better
Just don't use DB mapped entities as domain entities
Maciej Malarz (@malarzm)
Recap: Value Objects
A small simple object, like money or a
date range, whose equality isn't based on
identity.
Maciej Malarz (@malarzm)
2. Object Managers
Maciej Malarz (@malarzm)
Remember these ones?
/**
* @Route("{id}", name="submission")
*/
public function viewAction(Request $request)
{
$s = $this->getDoctrine()->getManager()->find(Submission::class, $request->get('id'));
if ($s === null) {
throw $this->createNotFoundException();
}
return [ 'submission' => $s ];
}
/**
* @ParamConverter("submission", class="AppBundleEntitySubmission")
* @Route("/accept/{id}", name="reject")
*/
public function rejectAction(Submission $submission)
{
$submission->setRejectedBy($this->getUser());
$submission->setRejectedOn(new DateTime());
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('homepage');
}
Maciej Malarz (@malarzm)
Your very own manager!
class SubmissionManager
{
/** @var ObjectManager */
private $objectManager;
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
public function find($id)
{
return $this->objectManager->find(Submission::class, $id);
}
public function save(Submission $submission)
{
$this->objectManager->persist($submission);
$this->objectManager->flush($submission);
}
}
Maciej Malarz (@malarzm)
Look ma! Using only my own stuff
/**
* @Route("/{id}", name="submission")
* @Template
*/
public function viewAction(Request $request)
{
$s = $this->get('submission_manager')->find($request->get('id'));
if ($s === null) {
throw $this->createNotFoundException();
}
return [ 'submission' => $s ];
}
/**
* @ParamConverter("submission", class="AppBundleEntitySubmission")
* @Route("/accept/{id}", name="accept")
*/
public function acceptAction(Submission $submission)
{
$submission->setAcceptedBy($this->getUser());
$submission->setAcceptedOn(new DateTime());
$this->get('submission_manager')->save($submission);
return $this->redirectToRoute('homepage');
}
Maciej Malarz (@malarzm)
Own events
class SubmissionManager
{
/** @var ObjectManager */
private $objectManager;
/** @var EventDispatcherInterface */
private $eventDispatcher;
public function __construct(ObjectManager $objectManager, EventDispatcherInterface $eventDispatcher)
{
$this->objectManager = $objectManager;
$this->eventDispatcher = $eventDispatcher;
}
public function save(Submission $submission)
{
if ($this->objectManager->contains($submission)) {
$this->eventDispatcher->dispatch('submission.update', new SubmissionEvent($submission));
} else {
$this->objectManager->persist($submission);
$this->eventDispatcher->dispatch('submission.create', new SubmissionEvent($submission));
}
$this->objectManager->flush($submission);
}
}
Maciej Malarz (@malarzm)
Recap: Object Managers
Manages lifecycle and instances of entities.
Maciej Malarz (@malarzm)
3. Repositories
Maciej Malarz (@malarzm)
We had this action:
public function listAction()
{
$submissions = $this->getDoctrine()->getManager()
->getRepository(Submission::class)
->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
return [ 'submissions' => $submissions ];
}
Maciej Malarz (@malarzm)
Custom repositories
class SubmissionRepository extends EntityRepository
{
public function findPending()
{
return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
}
}
class SubmissionManager
{
public function getRepository()
{
return $this->objectManager->getRepository(Submission::class);
}
}
public function listAction()
{
$submissions = $this->get('submission_repository')->findPending();
return [ 'submissions' => $submissions ];
}
Maciej Malarz (@malarzm)
Moar customization
class SubmissionRepository extends EntityRepository
{
/** @var SubmissionContext */
private $context;
public function __construct(EntityManager $em, ClassMetadata $class, SubmissionContext $context)
{
parent::__construct($em, $class);
$this->context = $context;
}
public function findAccepted()
{
$qb = $this->createQueryBuilder('s');
$accepted = $qb->where($qb->expr()->isNotNull('s.accepted'))
->getQuery()->getArrayResult();
return $this->finalizeCollection($accepted);
}
public function findPending()
{
return $this->finalizeCollection($this->findBy(['acceptedOn' => null, 'rejectedOn' => null]));
}
private function finalizeCollection($submissions)
{
return array_filter($submissions, array($this->context, 'applyRules'));
}
}
Maciej Malarz (@malarzm)
Verbs matter
class SubmissionRepository
{
public function find($id)
{
return $this->objectManager->find(Submission::class, $id);
}
public function get($id)
{
$s = $this->find($id);
if ($s === null) {
throw new NoResultException();
}
return $s;
}
}
Maciej Malarz (@malarzm)
Separate queries
class DoctrineSubmissionQuery
{
/** @var SubmissionContext */
private $context;
/** @var SubmissionRepository */
private $repository;
public function __construct(SubmissionRepository $repository, SubmissionContext $context)
{
$this->context = $context;
$this->repository = $repository;
}
public function __invoke()
{
// ...
}
}
Maciej Malarz (@malarzm)
Wanna cache? No problem!
class CacheSubmissionQuery
{
/** @var Cache */
private $cache;
/** @var SubmissionContext */
private $context;
public function __construct(Cache $cache, SubmissionContext $context)
{
$this->cache = $cache;
$this->context = $context;
}
public function __invoke()
{
// Get data from check based on some context hash
}
}
Maciej Malarz (@malarzm)
Recap: Repositories
Repositories contains business-specific
methods for locating entities.
Maciej Malarz (@malarzm)
4. Entities
Maciej Malarz (@malarzm)
Stay valid after __construct
$payment = new Payment();
$payment->setCurrency('USD');
$payment->setAmount(-69);
Stay valid after __construct
class Payment
{
private $amount;
private $currency;
public function __construct($amount, $currency)
{
if ((int) $amount <= 0) {
throw new InvalidArgumentException('Payment amount must be greater than 0');
}
if ( ! in_array($currency, ['USD', 'PLN'])) {
throw new InvalidArgumentException($currency . ' currency is not allowed');
}
$this->amount = $amount;
$this->currency = $currency;
}
}
Maciej Malarz (@malarzm)
Stay valid after __construct
Maciej Malarz (@malarzm)
You may need DTO
class PaymentDTO
{
/**
* @AssertGreaterThan(0)
*/
public $amount;
public $currency;
public function toPayment()
{
return new Payment($this->amount, $this->currency);
}
}
Maciej Malarz (@malarzm)
Recap: Entities
A thing with unique and independent
existence. Since it does exist it shall be
always valid.
Maciej Malarz (@malarzm)
Recap: All ze stuff
Value Objects
Maciej Malarz (@malarzm)
Object Managers
Repositories Entities
5. Holy Grails
Maciej Malarz (@malarzm)
5.1 Tests
Maciej Malarz (@malarzm)
Not very testable
/**
* @Route("/", name="homepage")
*/
public function listAction()
{
$submissions = $this->getDoctrine()->getManager()
->getRepository(Submission::class)
->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
return [ 'submissions' => $submissions ];
}
Maciej Malarz (@malarzm)
Better but...
class SubmissionRepository extends EntityRepository
{
// public function __construct(EntityManager $em, ClassMetadata $class)
// {
// parent::__construct($em, $class);
// }
public function findPending()
{
return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
}
}
Maciej Malarz (@malarzm)
Pull out an interface
interface SubmissionRepository
{
public function findPending();
}
class DoctrineSubmissionRepository extends EntityRepository implements SubmissionRepository
{
public function findPending()
{
return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]);
}
}
class InMemorySubmissionRepository implements SubmissionRepository
{
private $data = array();
public function findPending()
{
return array_filter($this->data, function(Submission $s) {
return $s->getAcceptedOn() === null && $s->getRejectedOn() === null;
});
}
}
Maciej Malarz (@malarzm)
Need stub?
class InMemorySubmissionRepository implements SubmissionRepository
{
private $data = array();
public function __construct(array $data = array())
{
$this->data = $data;
}
public function findPending()
{
return array_filter($this->data, function(Submission $s) {
return $s->getAcceptedOn() === null && $s->getRejectedOn() === null;
});
}
}
Maciej Malarz (@malarzm)
Or full flow?
interface SubmissionManager
{
public function find($id);
public function save(Submission $submission);
}
class InMemorySubmissionManager implements SubmissionManager
{
/** @var InMemorySubmissionRepository */
private $repository;
public function __construct(InMemorySubmissionRepository $repository)
{
$this->repository = $repository;
}
public function find($id)
{
return $this->repository->find($id);
}
public function save(Submission $submission)
{
$this->repository->store($submission);
}
}
Maciej Malarz (@malarzm)
Applies everywhere!
interface TaxCalculator
{
public function calculate(Income $income);
}
class Some3rdPartTaxCalculator implements TaxCalculator
{
public function calculate(Income $income)
{
// Some expensive API call
}
}
class StubbedTaxCalculator implements TaxCalculator
{
public function calculate(Income $income)
{
return $income->getMoney() * 0.19;
}
}
Maciej Malarz (@malarzm)
5.2 One-off storages
Maciej Malarz (@malarzm)
Hey, let's integrate with UniSubmission8.0
class UniSubmissionManager implements SubmissionManager
{
/** @var UniSubmissionApi */
private $api;
public function __construct(UniSubmissionApi $api)
{
$this->api = $api;
}
public function find($id)
{
$this->api->find($id);
}
public function save(Submission $submission)
{
$this->api->push($submission);
}
}
class UniSubmissionRepository implements SubmissionRepository
{
// ...
}
Maciej Malarz (@malarzm)
5.3 Changing ORM
Maciej Malarz (@malarzm)
Doctrine not cool anymore
class SuperDuperORMSubmissionManager implements SubmissionManager
{
/** @var SuperDuperORM */
private $orm;
/** @var EventDispatcherInterface */
private $eventDispatcher;
public function __construct(SuperDuperORM $orm, EventDispatcherInterface $eventDispatcher)
{
$this->orm = $orm;
$this->eventDispatcher = $eventDispatcher;
}
public function find($id)
{
$this->orm->gimme(Submission::class, $id);
}
public function save(Submission $submission)
{
if ($this->orm->haz($submission)) {
$this->eventDispatcher->dispatch('submission.update', new SubmissionEvent($submission));
} else {
$this->orm->register($submission);
$this->eventDispatcher->dispatch('submission.create', new SubmissionEvent($submission));
}
$this->orm->push($submission);
}
}
Maciej Malarz (@malarzm)
But for this you
should really have
own persistence
interface and write
adapters
Maciej Malarz (@malarzm)
5.4 Integrity
Maciej Malarz (@malarzm)
Saw (actually written, I regret) this
class Menu
{
private $tree;
private $flattened;
public function getFlattened()
{
if ($this->flattened !== null) {
return $this->flattened;
}
return $this->flattened = $this->doFlattenTree();
}
public function removeNode(Node $node)
{
$this->tree->remove($node);
// "Meh, after removing node the page is refreshing, no need to refresh flattened structure"
}
}
Maciej Malarz (@malarzm)
6. Thank you!
Maciej Malarz (@malarzm)
7. Questions?
Maciej Malarz (@malarzm)
8. Thank you!
Maciej Malarz (@malarzm)
www.slideshare.net/MaciejMalarz/database-meh-implementation-detail

More Related Content

What's hot

Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonFokke Zandbergen
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium AppsNate Abele
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionNate Abele
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex DevsAaronius
 
Simplified Android Development with Simple-Stack
Simplified Android Development with Simple-StackSimplified Android Development with Simple-Stack
Simplified Android Development with Simple-StackGabor Varadi
 
JavaStates Simple Tutorial
JavaStates Simple TutorialJavaStates Simple Tutorial
JavaStates Simple TutorialLaurent Henocque
 
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf Conference
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsJquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsEPAM Systems
 
Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019julien pauli
 
Drupal Step-by-Step: How We Built Our Training Site, Part 1
Drupal Step-by-Step: How We Built Our Training Site, Part 1Drupal Step-by-Step: How We Built Our Training Site, Part 1
Drupal Step-by-Step: How We Built Our Training Site, Part 1Acquia
 
Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3Vladyslav Riabchenko
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksNate Abele
 
Modularity and Layered Data Model
Modularity and Layered Data ModelModularity and Layered Data Model
Modularity and Layered Data ModelAttila Jenei
 

What's hot (20)

Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLon
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex Devs
 
Simplified Android Development with Simple-Stack
Simplified Android Development with Simple-StackSimplified Android Development with Simple-Stack
Simplified Android Development with Simple-Stack
 
50 jquery
50 jquery50 jquery
50 jquery
 
JavaStates Simple Tutorial
JavaStates Simple TutorialJavaStates Simple Tutorial
JavaStates Simple Tutorial
 
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
ZFConf 2010: Zend Framework & MVC, Model Implementation (Part 2, Dependency I...
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsJquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
 
Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019
 
Drupal Step-by-Step: How We Built Our Training Site, Part 1
Drupal Step-by-Step: How We Built Our Training Site, Part 1Drupal Step-by-Step: How We Built Our Training Site, Part 1
Drupal Step-by-Step: How We Built Our Training Site, Part 1
 
Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
 
Modularity and Layered Data Model
Modularity and Layered Data ModelModularity and Layered Data Model
Modularity and Layered Data Model
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
 
jQuery Presentasion
jQuery PresentasionjQuery Presentasion
jQuery Presentasion
 
JavaScript JQUERY AJAX
JavaScript JQUERY AJAXJavaScript JQUERY AJAX
JavaScript JQUERY AJAX
 
Framework Project Portfolio
Framework Project PortfolioFramework Project Portfolio
Framework Project Portfolio
 

Viewers also liked

ฉันเหมือนใคร
ฉันเหมือนใครฉันเหมือนใคร
ฉันเหมือนใครstang6050
 
TEN overview and key features
TEN overview and key featuresTEN overview and key features
TEN overview and key featuresJol Rose
 
Initial Ideas
Initial IdeasInitial Ideas
Initial Ideas2ampro
 
microéconomie
microéconomiemicroéconomie
microéconomieMed Gas
 
Estudio sobre las solicitudes de préstamos bancarios
Estudio sobre las solicitudes de préstamos bancariosEstudio sobre las solicitudes de préstamos bancarios
Estudio sobre las solicitudes de préstamos bancariosCrediMarket
 
¿Cómo valoran los clientes a los bancos?
¿Cómo valoran los clientes a los bancos?¿Cómo valoran los clientes a los bancos?
¿Cómo valoran los clientes a los bancos?CrediMarket
 
dtmf based mobile control robot
dtmf based mobile control robotdtmf based mobile control robot
dtmf based mobile control robotPankaj Rai
 
Soldering procedures/ orthodontic assistant training
Soldering procedures/ orthodontic assistant trainingSoldering procedures/ orthodontic assistant training
Soldering procedures/ orthodontic assistant trainingIndian dental academy
 
Economic Indicators and Monthly Overview October 2015
Economic Indicators and Monthly Overview October 2015Economic Indicators and Monthly Overview October 2015
Economic Indicators and Monthly Overview October 2015SappiHouston
 
¿Cómo valoran los clientes las comisiones que les cobran los bancos?
¿Cómo valoran los clientes las comisiones que les cobran los bancos?¿Cómo valoran los clientes las comisiones que les cobran los bancos?
¿Cómo valoran los clientes las comisiones que les cobran los bancos?CrediMarket
 

Viewers also liked (16)

ฉันเหมือนใคร
ฉันเหมือนใครฉันเหมือนใคร
ฉันเหมือนใคร
 
La empresa
La empresaLa empresa
La empresa
 
iVault Home Protection
iVault Home Protection iVault Home Protection
iVault Home Protection
 
Historia de las tic
Historia de las ticHistoria de las tic
Historia de las tic
 
TEN overview and key features
TEN overview and key featuresTEN overview and key features
TEN overview and key features
 
Initial Ideas
Initial IdeasInitial Ideas
Initial Ideas
 
Karton verpackung
Karton verpackungKarton verpackung
Karton verpackung
 
microéconomie
microéconomiemicroéconomie
microéconomie
 
Estudio sobre las solicitudes de préstamos bancarios
Estudio sobre las solicitudes de préstamos bancariosEstudio sobre las solicitudes de préstamos bancarios
Estudio sobre las solicitudes de préstamos bancarios
 
¿Cómo valoran los clientes a los bancos?
¿Cómo valoran los clientes a los bancos?¿Cómo valoran los clientes a los bancos?
¿Cómo valoran los clientes a los bancos?
 
Pankaj Rai
Pankaj RaiPankaj Rai
Pankaj Rai
 
dtmf based mobile control robot
dtmf based mobile control robotdtmf based mobile control robot
dtmf based mobile control robot
 
Codes and conventions of a thriller
Codes and conventions of a thrillerCodes and conventions of a thriller
Codes and conventions of a thriller
 
Soldering procedures/ orthodontic assistant training
Soldering procedures/ orthodontic assistant trainingSoldering procedures/ orthodontic assistant training
Soldering procedures/ orthodontic assistant training
 
Economic Indicators and Monthly Overview October 2015
Economic Indicators and Monthly Overview October 2015Economic Indicators and Monthly Overview October 2015
Economic Indicators and Monthly Overview October 2015
 
¿Cómo valoran los clientes las comisiones que les cobran los bancos?
¿Cómo valoran los clientes las comisiones que les cobran los bancos?¿Cómo valoran los clientes las comisiones que les cobran los bancos?
¿Cómo valoran los clientes las comisiones que les cobran los bancos?
 

Similar to Maciej Malarz (Codete) - Database? Meh, implementation detail

Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Arnaud Langlade
 
Cross platform Objective-C Strategy
Cross platform Objective-C StrategyCross platform Objective-C Strategy
Cross platform Objective-C StrategyGraham Lee
 
Data20161007
Data20161007Data20161007
Data20161007capegmail
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Julien Vinber
 
ORM - tuningujemy podejście do mapowania
ORM - tuningujemy podejście do mapowaniaORM - tuningujemy podejście do mapowania
ORM - tuningujemy podejście do mapowania3camp
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Let's write secure Drupal code! - Drupal Camp Poland 2019
Let's write secure Drupal code! - Drupal Camp Poland 2019Let's write secure Drupal code! - Drupal Camp Poland 2019
Let's write secure Drupal code! - Drupal Camp Poland 2019Balázs Tatár
 
Rest in practice con Symfony2
Rest in practice con Symfony2Rest in practice con Symfony2
Rest in practice con Symfony2Daniel Londero
 
Let's write secure Drupal code! - DrupalCamp Belarus 2019
Let's write secure Drupal code! - DrupalCamp Belarus 2019Let's write secure Drupal code! - DrupalCamp Belarus 2019
Let's write secure Drupal code! - DrupalCamp Belarus 2019Balázs Tatár
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologyDaniel Knell
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmFabio Collini
 
... now write an interpreter (PHPem 2016)
... now write an interpreter (PHPem 2016)... now write an interpreter (PHPem 2016)
... now write an interpreter (PHPem 2016)James Titcumb
 

Similar to Maciej Malarz (Codete) - Database? Meh, implementation detail (20)

Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)
 
Cross platform Objective-C Strategy
Cross platform Objective-C StrategyCross platform Objective-C Strategy
Cross platform Objective-C Strategy
 
Data20161007
Data20161007Data20161007
Data20161007
 
Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?
 
Code me a HR
Code me a HRCode me a HR
Code me a HR
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
 
ORM - tuningujemy podejście do mapowania
ORM - tuningujemy podejście do mapowaniaORM - tuningujemy podejście do mapowania
ORM - tuningujemy podejście do mapowania
 
Orm bad-habits
Orm bad-habitsOrm bad-habits
Orm bad-habits
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Doctrine in FLOW3
Doctrine in FLOW3Doctrine in FLOW3
Doctrine in FLOW3
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Let's write secure Drupal code! - Drupal Camp Poland 2019
Let's write secure Drupal code! - Drupal Camp Poland 2019Let's write secure Drupal code! - Drupal Camp Poland 2019
Let's write secure Drupal code! - Drupal Camp Poland 2019
 
Rest in practice con Symfony2
Rest in practice con Symfony2Rest in practice con Symfony2
Rest in practice con Symfony2
 
Let's write secure Drupal code! - DrupalCamp Belarus 2019
Let's write secure Drupal code! - DrupalCamp Belarus 2019Let's write secure Drupal code! - DrupalCamp Belarus 2019
Let's write secure Drupal code! - DrupalCamp Belarus 2019
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
 
Twitter codeigniter library
Twitter codeigniter libraryTwitter codeigniter library
Twitter codeigniter library
 
... now write an interpreter (PHPem 2016)
... now write an interpreter (PHPem 2016)... now write an interpreter (PHPem 2016)
... now write an interpreter (PHPem 2016)
 

More from Business Link Krakow

Bartosz Grzybowski - Continuous integration, czyli code quality matters
Bartosz Grzybowski - Continuous integration, czyli code quality mattersBartosz Grzybowski - Continuous integration, czyli code quality matters
Bartosz Grzybowski - Continuous integration, czyli code quality mattersBusiness Link Krakow
 
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego kodu
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego koduGrzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego kodu
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego koduBusiness Link Krakow
 
Konrad Kwiatkowski - Type of components in React/Redux
Konrad Kwiatkowski - Type of components in React/ReduxKonrad Kwiatkowski - Type of components in React/Redux
Konrad Kwiatkowski - Type of components in React/ReduxBusiness Link Krakow
 
Oferta miejsca pracy Business Link Kraków
Oferta miejsca pracy Business Link KrakówOferta miejsca pracy Business Link Kraków
Oferta miejsca pracy Business Link KrakówBusiness Link Krakow
 
Paweł Kowalczyk (Codete) - Continuous integration for iOS
Paweł Kowalczyk (Codete) - Continuous integration for iOSPaweł Kowalczyk (Codete) - Continuous integration for iOS
Paweł Kowalczyk (Codete) - Continuous integration for iOSBusiness Link Krakow
 
Mateusz Zając (Codete) - Swift in Production
Mateusz Zając (Codete)  - Swift in ProductionMateusz Zając (Codete)  - Swift in Production
Mateusz Zając (Codete) - Swift in ProductionBusiness Link Krakow
 
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case Study
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case StudyJakub Mrowiec (Grand Parade Poland) - Monumentum Case Study
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case StudyBusiness Link Krakow
 
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for Dummies
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for DummiesBartosz Zaczyński (Grand Parade Poland) - WebSocket for Dummies
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for DummiesBusiness Link Krakow
 
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...Business Link Krakow
 
Paweł Dyrek (Codete) - Product Delivery
Paweł Dyrek (Codete) - Product DeliveryPaweł Dyrek (Codete) - Product Delivery
Paweł Dyrek (Codete) - Product DeliveryBusiness Link Krakow
 
Mateusz Chłodnicki - Case study: Shuttout.com
Mateusz Chłodnicki - Case study: Shuttout.comMateusz Chłodnicki - Case study: Shuttout.com
Mateusz Chłodnicki - Case study: Shuttout.comBusiness Link Krakow
 
Tomasz Chołast - Case study: zrzutka.pl
Tomasz Chołast - Case study: zrzutka.plTomasz Chołast - Case study: zrzutka.pl
Tomasz Chołast - Case study: zrzutka.plBusiness Link Krakow
 
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?Business Link Krakow
 
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...Business Link Krakow
 
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...Business Link Krakow
 
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol Król
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol KrólCrowdinvesting - inwestycyjne modele crowdfundingu - Karol Król
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol KrólBusiness Link Krakow
 
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020Business Link Krakow
 

More from Business Link Krakow (20)

Bartosz Grzybowski - Continuous integration, czyli code quality matters
Bartosz Grzybowski - Continuous integration, czyli code quality mattersBartosz Grzybowski - Continuous integration, czyli code quality matters
Bartosz Grzybowski - Continuous integration, czyli code quality matters
 
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego kodu
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego koduGrzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego kodu
Grzegorz Sikorskie - "Gorsze jest lepsze", czyli o dobrych stronach złego kodu
 
Konrad Kwiatkowski - Type of components in React/Redux
Konrad Kwiatkowski - Type of components in React/ReduxKonrad Kwiatkowski - Type of components in React/Redux
Konrad Kwiatkowski - Type of components in React/Redux
 
Oferta sale
Oferta saleOferta sale
Oferta sale
 
Oferta miejsca pracy Business Link Kraków
Oferta miejsca pracy Business Link KrakówOferta miejsca pracy Business Link Kraków
Oferta miejsca pracy Business Link Kraków
 
Paweł Kowalczyk (Codete) - Continuous integration for iOS
Paweł Kowalczyk (Codete) - Continuous integration for iOSPaweł Kowalczyk (Codete) - Continuous integration for iOS
Paweł Kowalczyk (Codete) - Continuous integration for iOS
 
Mateusz Zając (Codete) - Swift in Production
Mateusz Zając (Codete)  - Swift in ProductionMateusz Zając (Codete)  - Swift in Production
Mateusz Zając (Codete) - Swift in Production
 
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case Study
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case StudyJakub Mrowiec (Grand Parade Poland) - Monumentum Case Study
Jakub Mrowiec (Grand Parade Poland) - Monumentum Case Study
 
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for Dummies
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for DummiesBartosz Zaczyński (Grand Parade Poland) - WebSocket for Dummies
Bartosz Zaczyński (Grand Parade Poland) - WebSocket for Dummies
 
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...
Piotr Grabski-Gradziński (VML) - To jak zrobimy ten projekt? Czyli o doborze ...
 
Paweł Dyrek (Codete) - Product Delivery
Paweł Dyrek (Codete) - Product DeliveryPaweł Dyrek (Codete) - Product Delivery
Paweł Dyrek (Codete) - Product Delivery
 
Mateusz Chłodnicki - Case study: Shuttout.com
Mateusz Chłodnicki - Case study: Shuttout.comMateusz Chłodnicki - Case study: Shuttout.com
Mateusz Chłodnicki - Case study: Shuttout.com
 
Tomasz Chołast - Case study: zrzutka.pl
Tomasz Chołast - Case study: zrzutka.plTomasz Chołast - Case study: zrzutka.pl
Tomasz Chołast - Case study: zrzutka.pl
 
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?
Mateusz Hauschild - Jak crowdfunding zmienił rynek gier planszowych?
 
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...
Filip Karkosz & Dominik Szloński - Jak z zaangażowania konsumenta uczynić źró...
 
Marek Cieśla - Kickstarter.com
Marek Cieśla - Kickstarter.comMarek Cieśla - Kickstarter.com
Marek Cieśla - Kickstarter.com
 
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...
Bartosz Filip Malinowski - Crowdsourcing dla Starbucksa, małych startupów i o...
 
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol Król
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol KrólCrowdinvesting - inwestycyjne modele crowdfundingu - Karol Król
Crowdinvesting - inwestycyjne modele crowdfundingu - Karol Król
 
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020
Fundusze UE - nowe szanse, nowe możliwości dla start-up na lata 2014-2020
 
Pułapki podatkowe
Pułapki podatkowe Pułapki podatkowe
Pułapki podatkowe
 

Recently uploaded

%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in sowetomasabamasaba
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension AidPhilip Schwarz
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyviewmasabamasaba
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfonteinmasabamasaba
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park masabamasaba
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplatePresentation.STUDIO
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...Jittipong Loespradit
 
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024VictoriaMetrics
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is insideshinachiaurasa2
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfonteinmasabamasaba
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2
 

Recently uploaded (20)

%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 

Maciej Malarz (Codete) - Database? Meh, implementation detail

  • 5. Entity /** @ORMEntity */ class Submission { /** @ORMOneToOne(targetEntity="User") */ private $acceptedBy; /** @ORMColumn(type="datetime") */ private $acceptedOn; /** @ORMColumn(type="string") */ private $applicant; /** @ORMOneToOne(targetEntity="User") */ private $rejectedBy; /** @ORMColumn(type="datetime") */ private $rejectedOn; /** @ORMColumn(type="string") */ private $title; } Maciej Malarz (@malarzm)
  • 6. List action /** * @Route("/", name="homepage") */ public function listAction() { $submissions = $this->getDoctrine()->getManager() ->getRepository(Submission::class) ->findBy(['acceptedOn' => null, 'rejectedOn' => null]); return [ 'submissions' => $submissions ]; } Maciej Malarz (@malarzm)
  • 7. View action /** * @Route("/{id}", name="submission") * @Template() */ public function viewAction(Request $request) { $s = $this->getDoctrine()->getManager()->find(Submission::class, $request->get('id')); if ($s === null) { throw $this->createNotFoundException(); } return [ 'submission' => $s ]; } /** * @ParamConverter("submission", class="AppBundleEntitySubmission") * @Route("/{id}", name="submission") * @Template */ public function viewAction(Submission $submission) { return [ 'submission' => $submission ]; } Maciej Malarz (@malarzm)
  • 8. Accept & reject actions /** * @ParamConverter("submission", class="AppBundleEntitySubmission") * @Route("/accept/{id}", name="accept") */ public function acceptAction(Submission $submission) { $submission->setAcceptedBy($this->getUser()); $submission->setAcceptedOn(new DateTime()); $this->getDoctrine()->getManager()->flush(); return $this->redirectToRoute('homepage'); } /** * @ParamConverter("submission", class="AppBundleEntitySubmission") * @Route("/accept/{id}", name="reject") */ public function rejectAction(Submission $submission) { $submission->setRejectedBy($this->getUser()); $submission->setRejectedOn(new DateTime()); $this->getDoctrine()->getManager()->flush(); return $this->redirectToRoute('homepage'); } Maciej Malarz (@malarzm)
  • 10.
  • 11. Forgot the slug class SubmissionsSlugger { public function prePersist(LifecycleEventArgs $event) { /** @var Submission $entity */ $entity = $event->getEntity(); if ( ! ($entity instanceof Submission)) { return; } $entity->setSlug($this->slugify($entity->getTitle())); } private function slugify($string) { return preg_replace('/[^a-z0-9]/', '-', strtolower(trim(strip_tags($string)))); } } Maciej Malarz (@malarzm)
  • 12.
  • 14. Oh hey, project was a success! Maciej Malarz (@malarzm)
  • 15.
  • 16.
  • 17. 1. Value Objects Maciej Malarz (@malarzm)
  • 18. VO candidate /** @ORMEntity */ class Submission { /** @ORMOneToOne(targetEntity="User") */ private $acceptedBy; /** @ORMColumn(type="datetime") */ private $acceptedOn; /** @ORMColumn(type="string") */ private $applicant; /** @ORMOneToOne(targetEntity="User") */ private $rejectedBy; /** @ORMColumn(type="datetime") */ private $rejectedOn; /** @ORMColumn(type="string") */ private $title; } Maciej Malarz (@malarzm)
  • 19. Introducing VO /** @ORMEmbeddable */ abstract class AbstractStatus { /** @ORMOneToOne(targetEntity="User") */ private $by; /** @ORMColumn(type="datetime") */ private $on; /** @ORMColumn(type="string") */ private $message; public function __construct(User $by, $message) { $this->by = $by; $this->on = new DateTime(); $this->message = $message; } } class StatusAccepted extends AbstractStatus { } class StatusCancelled extends AbstractStatus { } class StatusRejected extends AbstractStatus { } Maciej Malarz (@malarzm)
  • 20. Mapping class Submission { /** @ORMColumn(type="string") */ private $applicant; /** @ORMColumn(type="string") */ private $slug; /** @ORMEmbedded(class="???") */ private $status; /** @ORMColumn(type="string") */ private $title; } Maciej Malarz (@malarzm)
  • 21. Actually you can but with MongoDB ODM ;) Maciej Malarz (@malarzm)
  • 22. /** @ORMEmbeddable */ class Status { const ACCEPTED = 0; const CANCELLED = 1; const REJECTED = 2; /** @ORMOneToOne(targetEntity="User") */ private $by; /** @ORMColumn(type="datetime") */ private $on; /** @ORMColumn(type="string") */ private $message; /** @ORMColumn(type="integer") */ private $type; public function __construct($type, User $by, $message) { $this->by = $by; $this->on = new DateTime(); $this->message = $message; $this->type = $type; } } Not very good on its own... Maciej Malarz (@malarzm)
  • 23. A bit better... /** @ORMEntity */ class Submission { /** * @ORMEmbedded(class="Status") * @var Status */ private $status; public function getStatus() { switch ($this->status->getType()) { case Status::ACCEPTED: return new StatusAccepted(/* ... */); /* ... */ } } public function setStatus(Status $status) { switch (get_class($status)) { case StatusAccepted::class: $this->status = new Status(/* ... */); break; /* ... */ } } Maciej Malarz (@malarzm)
  • 24. We can do better Just don't use DB mapped entities as domain entities Maciej Malarz (@malarzm)
  • 25. Recap: Value Objects A small simple object, like money or a date range, whose equality isn't based on identity. Maciej Malarz (@malarzm)
  • 26. 2. Object Managers Maciej Malarz (@malarzm)
  • 27. Remember these ones? /** * @Route("{id}", name="submission") */ public function viewAction(Request $request) { $s = $this->getDoctrine()->getManager()->find(Submission::class, $request->get('id')); if ($s === null) { throw $this->createNotFoundException(); } return [ 'submission' => $s ]; } /** * @ParamConverter("submission", class="AppBundleEntitySubmission") * @Route("/accept/{id}", name="reject") */ public function rejectAction(Submission $submission) { $submission->setRejectedBy($this->getUser()); $submission->setRejectedOn(new DateTime()); $this->getDoctrine()->getManager()->flush(); return $this->redirectToRoute('homepage'); } Maciej Malarz (@malarzm)
  • 28. Your very own manager! class SubmissionManager { /** @var ObjectManager */ private $objectManager; public function __construct(ObjectManager $objectManager) { $this->objectManager = $objectManager; } public function find($id) { return $this->objectManager->find(Submission::class, $id); } public function save(Submission $submission) { $this->objectManager->persist($submission); $this->objectManager->flush($submission); } } Maciej Malarz (@malarzm)
  • 29. Look ma! Using only my own stuff /** * @Route("/{id}", name="submission") * @Template */ public function viewAction(Request $request) { $s = $this->get('submission_manager')->find($request->get('id')); if ($s === null) { throw $this->createNotFoundException(); } return [ 'submission' => $s ]; } /** * @ParamConverter("submission", class="AppBundleEntitySubmission") * @Route("/accept/{id}", name="accept") */ public function acceptAction(Submission $submission) { $submission->setAcceptedBy($this->getUser()); $submission->setAcceptedOn(new DateTime()); $this->get('submission_manager')->save($submission); return $this->redirectToRoute('homepage'); } Maciej Malarz (@malarzm)
  • 30. Own events class SubmissionManager { /** @var ObjectManager */ private $objectManager; /** @var EventDispatcherInterface */ private $eventDispatcher; public function __construct(ObjectManager $objectManager, EventDispatcherInterface $eventDispatcher) { $this->objectManager = $objectManager; $this->eventDispatcher = $eventDispatcher; } public function save(Submission $submission) { if ($this->objectManager->contains($submission)) { $this->eventDispatcher->dispatch('submission.update', new SubmissionEvent($submission)); } else { $this->objectManager->persist($submission); $this->eventDispatcher->dispatch('submission.create', new SubmissionEvent($submission)); } $this->objectManager->flush($submission); } } Maciej Malarz (@malarzm)
  • 31. Recap: Object Managers Manages lifecycle and instances of entities. Maciej Malarz (@malarzm)
  • 33. We had this action: public function listAction() { $submissions = $this->getDoctrine()->getManager() ->getRepository(Submission::class) ->findBy(['acceptedOn' => null, 'rejectedOn' => null]); return [ 'submissions' => $submissions ]; } Maciej Malarz (@malarzm)
  • 34. Custom repositories class SubmissionRepository extends EntityRepository { public function findPending() { return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]); } } class SubmissionManager { public function getRepository() { return $this->objectManager->getRepository(Submission::class); } } public function listAction() { $submissions = $this->get('submission_repository')->findPending(); return [ 'submissions' => $submissions ]; } Maciej Malarz (@malarzm)
  • 35. Moar customization class SubmissionRepository extends EntityRepository { /** @var SubmissionContext */ private $context; public function __construct(EntityManager $em, ClassMetadata $class, SubmissionContext $context) { parent::__construct($em, $class); $this->context = $context; } public function findAccepted() { $qb = $this->createQueryBuilder('s'); $accepted = $qb->where($qb->expr()->isNotNull('s.accepted')) ->getQuery()->getArrayResult(); return $this->finalizeCollection($accepted); } public function findPending() { return $this->finalizeCollection($this->findBy(['acceptedOn' => null, 'rejectedOn' => null])); } private function finalizeCollection($submissions) { return array_filter($submissions, array($this->context, 'applyRules')); } } Maciej Malarz (@malarzm)
  • 36. Verbs matter class SubmissionRepository { public function find($id) { return $this->objectManager->find(Submission::class, $id); } public function get($id) { $s = $this->find($id); if ($s === null) { throw new NoResultException(); } return $s; } } Maciej Malarz (@malarzm)
  • 37. Separate queries class DoctrineSubmissionQuery { /** @var SubmissionContext */ private $context; /** @var SubmissionRepository */ private $repository; public function __construct(SubmissionRepository $repository, SubmissionContext $context) { $this->context = $context; $this->repository = $repository; } public function __invoke() { // ... } } Maciej Malarz (@malarzm)
  • 38. Wanna cache? No problem! class CacheSubmissionQuery { /** @var Cache */ private $cache; /** @var SubmissionContext */ private $context; public function __construct(Cache $cache, SubmissionContext $context) { $this->cache = $cache; $this->context = $context; } public function __invoke() { // Get data from check based on some context hash } } Maciej Malarz (@malarzm)
  • 39. Recap: Repositories Repositories contains business-specific methods for locating entities. Maciej Malarz (@malarzm)
  • 41. Stay valid after __construct $payment = new Payment(); $payment->setCurrency('USD'); $payment->setAmount(-69);
  • 42. Stay valid after __construct class Payment { private $amount; private $currency; public function __construct($amount, $currency) { if ((int) $amount <= 0) { throw new InvalidArgumentException('Payment amount must be greater than 0'); } if ( ! in_array($currency, ['USD', 'PLN'])) { throw new InvalidArgumentException($currency . ' currency is not allowed'); } $this->amount = $amount; $this->currency = $currency; } } Maciej Malarz (@malarzm)
  • 43. Stay valid after __construct Maciej Malarz (@malarzm)
  • 44. You may need DTO class PaymentDTO { /** * @AssertGreaterThan(0) */ public $amount; public $currency; public function toPayment() { return new Payment($this->amount, $this->currency); } } Maciej Malarz (@malarzm)
  • 45. Recap: Entities A thing with unique and independent existence. Since it does exist it shall be always valid. Maciej Malarz (@malarzm)
  • 46. Recap: All ze stuff Value Objects Maciej Malarz (@malarzm) Object Managers Repositories Entities
  • 47. 5. Holy Grails Maciej Malarz (@malarzm)
  • 49. Not very testable /** * @Route("/", name="homepage") */ public function listAction() { $submissions = $this->getDoctrine()->getManager() ->getRepository(Submission::class) ->findBy(['acceptedOn' => null, 'rejectedOn' => null]); return [ 'submissions' => $submissions ]; } Maciej Malarz (@malarzm)
  • 50. Better but... class SubmissionRepository extends EntityRepository { // public function __construct(EntityManager $em, ClassMetadata $class) // { // parent::__construct($em, $class); // } public function findPending() { return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]); } } Maciej Malarz (@malarzm)
  • 51. Pull out an interface interface SubmissionRepository { public function findPending(); } class DoctrineSubmissionRepository extends EntityRepository implements SubmissionRepository { public function findPending() { return $this->findBy(['acceptedOn' => null, 'rejectedOn' => null]); } } class InMemorySubmissionRepository implements SubmissionRepository { private $data = array(); public function findPending() { return array_filter($this->data, function(Submission $s) { return $s->getAcceptedOn() === null && $s->getRejectedOn() === null; }); } } Maciej Malarz (@malarzm)
  • 52. Need stub? class InMemorySubmissionRepository implements SubmissionRepository { private $data = array(); public function __construct(array $data = array()) { $this->data = $data; } public function findPending() { return array_filter($this->data, function(Submission $s) { return $s->getAcceptedOn() === null && $s->getRejectedOn() === null; }); } } Maciej Malarz (@malarzm)
  • 53. Or full flow? interface SubmissionManager { public function find($id); public function save(Submission $submission); } class InMemorySubmissionManager implements SubmissionManager { /** @var InMemorySubmissionRepository */ private $repository; public function __construct(InMemorySubmissionRepository $repository) { $this->repository = $repository; } public function find($id) { return $this->repository->find($id); } public function save(Submission $submission) { $this->repository->store($submission); } } Maciej Malarz (@malarzm)
  • 54. Applies everywhere! interface TaxCalculator { public function calculate(Income $income); } class Some3rdPartTaxCalculator implements TaxCalculator { public function calculate(Income $income) { // Some expensive API call } } class StubbedTaxCalculator implements TaxCalculator { public function calculate(Income $income) { return $income->getMoney() * 0.19; } } Maciej Malarz (@malarzm)
  • 55. 5.2 One-off storages Maciej Malarz (@malarzm)
  • 56. Hey, let's integrate with UniSubmission8.0 class UniSubmissionManager implements SubmissionManager { /** @var UniSubmissionApi */ private $api; public function __construct(UniSubmissionApi $api) { $this->api = $api; } public function find($id) { $this->api->find($id); } public function save(Submission $submission) { $this->api->push($submission); } } class UniSubmissionRepository implements SubmissionRepository { // ... } Maciej Malarz (@malarzm)
  • 57. 5.3 Changing ORM Maciej Malarz (@malarzm)
  • 58. Doctrine not cool anymore class SuperDuperORMSubmissionManager implements SubmissionManager { /** @var SuperDuperORM */ private $orm; /** @var EventDispatcherInterface */ private $eventDispatcher; public function __construct(SuperDuperORM $orm, EventDispatcherInterface $eventDispatcher) { $this->orm = $orm; $this->eventDispatcher = $eventDispatcher; } public function find($id) { $this->orm->gimme(Submission::class, $id); } public function save(Submission $submission) { if ($this->orm->haz($submission)) { $this->eventDispatcher->dispatch('submission.update', new SubmissionEvent($submission)); } else { $this->orm->register($submission); $this->eventDispatcher->dispatch('submission.create', new SubmissionEvent($submission)); } $this->orm->push($submission); } } Maciej Malarz (@malarzm)
  • 59. But for this you should really have own persistence interface and write adapters Maciej Malarz (@malarzm)
  • 61. Saw (actually written, I regret) this class Menu { private $tree; private $flattened; public function getFlattened() { if ($this->flattened !== null) { return $this->flattened; } return $this->flattened = $this->doFlattenTree(); } public function removeNode(Node $node) { $this->tree->remove($node); // "Meh, after removing node the page is refreshing, no need to refresh flattened structure" } } Maciej Malarz (@malarzm)
  • 62. 6. Thank you! Maciej Malarz (@malarzm)
  • 64. 8. Thank you! Maciej Malarz (@malarzm) www.slideshare.net/MaciejMalarz/database-meh-implementation-detail