Rich domain model with symfony 2.5 and doctrine 2.5

Leonardo Proietti
Leonardo ProiettiSoftware Engineer at LoveCrafts
Rich Domain Model with 
Symfony2 and Doctrine2 
Leonardo Proietti 
@_leopro_ 
Symfony Day Italy 
Milan, 10 October 2014
twitter.com/_leopro_ 
github.com/leopro 
linkedin.com/in/leonardoproietti
1. Domain Modeling
domain = problem space 
domain model = solution space
Domain 
“A Domain [...] is what an organization 
does and the world it does in.” 
(Vaughn Vernon, “Implementing Domain-Driven Design”)
Domain Model 
“A model is a simplification. It is an 
interpretation of reality that abstracts the 
aspects relevant to solving problem at hand 
and ignores extraneous detail.” 
(Eric Evans, "Domain Driven Design")
A model
Domain Model 
“In DDD, domain model refers to a 
class.” 
(Julie Lerman, http://msdn.microsoft.com/en-us/magazine/dn385704.aspx)
Domain Model 
“An object model of the domain that 
incorporates both behavior and data.” 
(Martin Fowler, http://martinfowler.com/eaaCatalog/domainModel.html)
Domain Model 
“An object model of the domain that 
incorporates both behavior and data.” 
(Martin Fowler, http://martinfowler.com/eaaCatalog/domainModel.html)
Why Rich?
First, because it’s not anemic 
Martin Fowler 
http://martinfowler.com/bliki/AnemicDomainModel.html
<?php 
class Anemic 
{ 
private $shouldNotChangeAfterCreation; 
private $shouldBeChangedOnlyOnEdit; 
private $couldBeChangedAnytime; 
public function setCouldBeChangedAnytime($couldBeChangedAnytime) {} 
public function setShouldBeChangedOnlyOnEdit($shouldBeChangedOnlyOnEdit) {} 
public function setShouldNotChangeAfterCreation($shouldNotChangeAfterCreation {} 
}
<?php 
class SomeService 
{ 
public function doStuffOnCreation() 
{ 
$anemic = new Anemic(); 
$anemic->setCouldBeChangedAnytime('abc'); 
$anemic->setShouldNotChangeAfterCreation('def'); 
$unitOfWork->persist($anemic); 
$unitOfWork->flush(); 
} 
}
<?php 
class SomeService 
{ 
public function doStuffOnEdit() 
{ 
$anemic = new Anemic(); 
$anemic->setCouldBeChangedAnytime('abc'); 
$anemic->setShouldBeChangedOnlyOnEdit(‘123’); 
$unitOfWork->persist($anemic); 
$unitOfWork->flush(); 
} 
}
<?php 
class SomeService 
{ 
public function doStuffOnEdit() 
{ 
Loss of memory 
$anemic = new Anemic(); 
$anemic->setCouldBeChangedAnytime('abc'); 
$anemic->setShouldBeChangedOnlyOnEdit(‘123’); 
$unitOfWork->persist($anemic); 
$unitOfWork->flush(); 
} 
}
<?php 
class SomeService 
{ 
public function doStuffOnEdit() 
{ 
$anemic = new Anemic(); 
$anemic->setCouldBeChangedAnytime('abc'); 
$anemic->setShouldBeChangedOnlyOnEdit(‘123’); 
$anemic->setShouldNotChangeAfterCreation('def'); 
$unitOfWork->persist($anemic); 
$unitOfWork->flush(); 
} 
}
<?php 
class BehaviouralClass 
{ 
private $shouldNotChangeAfterCreation; 
private $shouldBeChangedOnlyOnEdit; 
private $couldBeChangedAnytime; 
public function __construct($shouldNotChangeAfterCreation, $couldBeChangedAnytime) 
{ 
$this->shouldNotChangeAfterCreation = $shouldNotChangeAfterCreation; 
$this->couldBeChangedAnytime = $couldBeChangedAnytime; 
} 
public function modify($shouldBeChangedOnlyOnEdit, $couldBeChangedAnytime = null) 
{ 
$this->shouldBeChangedOnlyOnEdit = $shouldBeChangedOnlyOnEdit; 
$this->couldBeChangedAnytime = $couldBeChangedAnytime; 
} 
}
<?php 
class BehaviouralClass 
{ 
private $shouldNotChangeAfterCreation; 
private $shouldBeChangedOnlyOnEdit; 
private $couldBeChangedAnytime; 
It's not still rich, lacks of ... 
public function __construct($shouldNotChangeAfterCreation, $couldBeChangedAnytime) 
{ 
$this->shouldNotChangeAfterCreation = $shouldNotChangeAfterCreation; 
$this->couldBeChangedAnytime = $couldBeChangedAnytime; 
} 
public function modify($shouldBeChangedOnlyOnEdit, $couldBeChangedAnytime = null) 
{ 
$this->shouldBeChangedOnlyOnEdit = $shouldBeChangedOnlyOnEdit; 
$this->couldBeChangedAnytime = $couldBeChangedAnytime; 
} 
}
Ubiquitous Language 
“the domain model can provide the backbone 
for that common language [...]. The vocabulary 
of that ubiquitius language includes the names 
of classes and prominent operations” 
(Eric Evans, "Domain Driven Design")
What does “coffee” mean? 
Alberto Brandolini AKA @ziobrando
A bit of strategy
Our domain is an online game that simulate the soccer’s world.
Bounded Contexts
What does “player” mean 
in our domain?
The meaning of “player” 
Within game engine context 
a model of a real soccer player, 
modelled with behaviours to fit the 
requirements of the game engine.
The meaning of “player” 
Within data import context 
a model of a real soccer player, but 
modelled for a simple CRUD.
The meaning of “player” 
Within user profile context 
a model of the user of the website, 
who plays the game.
League Team 
Calendar Player 
Lineup 
Coach 
Core context
League Team 
Calendar Player 
Lineup 
Coach 
(the player in “user 
context”) 
Core context
Player 
Game context User context 
Uuid 
Name 
Roles 
Uuid 
Email 
Password
Data and Behaviours
<?php 
class League 
{ 
private $id; 
private $name; 
private $teams; 
public function __construct(Uuid $uuid, $name) 
{ 
$this->id = $uuid; 
$this->name = $name; 
$this->teams = new ArrayCollection(); 
} 
public function registerTeam(Team $team) 
{ 
$this->teams->add($team); 
} 
}
<?php 
class League 
{ 
private $id; 
private $name; 
private $teams; 
A team must do a registration 
public function __construct({ 
to the league 
Uuid $uuid, $name) 
$this->id = $uuid; 
$this->name = $name; 
$this->teams = new ArrayCollection(); 
} 
public function registerTeam(Team $team) 
{ 
$this->teams->add($team); 
} 
}
<?php 
class League 
{ 
private $id; 
private $genericInfo; 
private $teams; 
public function __construct(Uuid $uuid, 
LeagueGenericInfo $leagueGenericInfo) 
{ 
$this->id = $uuid; 
$this->genericInfo = $leagueGenericInfo; 
$this->teams = new ArrayCollection(); 
} 
// ...}
<?php 
class LeagueGenericInfo 
{ 
private $name; 
private $description; 
private $country; 
public function __construct($country, $description, $name) 
{ 
$this->country = $country; 
$this->description = $description; 
$this->name = $name; 
} 
// … getters and behaviours}
<?php 
class LeagueGenericInfo 
{ 
private $name; 
private $description; 
private $country; 
Value object 
public function __construct($country, $description, $name) 
{ 
$this->country = $country; 
$this->description = $description; 
$this->name = $name; 
} 
// … getters and behaviours}
Validation: invariants and input
<?php 
class League 
{ 
// ... 
public function registerTeam(Team $team) 
{ 
if (!$this->canLeagueAcceptAnotherRegistration()) { 
throw new DomainException('Not more places available'); 
} 
$this->teams->add($team); 
} 
private function canLeagueAcceptAnotherRegistration() 
{ 
if ($this->teams->count() == 8) { 
return false; 
} 
return true; 
} 
}
<?php 
class League 
{ 
// ... 
public function registerTeam(Team $team) 
{ 
if (!$this->canLeagueAcceptAnotherRegistration()) { 
League protects its invariants 
throw new DomainException('Not more places available'); 
} 
$this->teams->add($team); 
} 
private function canLeagueAcceptAnotherRegistration() 
{ 
if ($this->teams->count() == 8) { 
return false; 
} 
return true; 
} 
}
<?php 
class League 
{ 
// ... 
public function getTeams() 
{ 
return $this->teams; 
} 
}
<?php 
class League 
{ 
// ... 
private function getTeams() 
{ 
return $this->teams; 
} 
}
<?php 
class LeagueGenericInfo 
{ 
private $name; 
private $description; 
private $country; 
private static $countries; 
public function __construct($country, $description, $name) 
{ 
if(!isset(static::$countries)) { 
static::$countries = require __DIR__.'/countries.php'; 
} 
if (!array_key_exists($name, static::$countries)) { 
throw new UnknownCountryException($country); 
} 
$this->country = $country; 
// .. thanks to Mathias Verraes for “Money” ;-) 
}}
<?php 
class LeagueGenericInfo 
{ 
private $name; 
private $description; 
private $country; 
Input validation 
private static $countries; 
public function __construct($country, $description, $name) 
{ 
if(!isset(static::$countries)) { 
static::$countries = require __DIR__.'/countries.php'; 
} 
if (!array_key_exists($name, static::$countries)) { 
throw new UnknownCountryException($country); 
} 
$this->country = $country; 
// .. thanks to Mathias Verraes for “Money” ;-) 
}}
<?php 
class LeagueGenericInfo 
{ 
private $name; 
private $description; 
private $country; 
Could be private static also $countries; 
placed in commands 
public function __construct($country, $description, $name) 
{ 
if(!isset(static::$countries)) { 
static::$countries = require __DIR__.'/countries.php'; 
} 
if (!array_key_exists($name, static::$countries)) { 
throw new UnknownCountryException($country); 
} 
$this->country = $country; 
// .. thanks to Mathias Verraes for “Money” ;-) 
}}
<?php 
class League 
{ 
public function render() 
{ 
$properties = [ 
'id' => $this->id, 
'name' => $this->genericInfo->getName(), 
'description' => $this->genericInfo->getDescription(), 
'country' => $this->genericInfo->getCountry(), 
]; 
$teams = new ArrayCollection(); 
foreach ($this->teams as $team) { 
$teams->add($team->render()); 
} 
$properties['teams'] = $teams; 
return new ArrayCollection($properties); 
} 
}
<?php 
class League 
{ 
public function render() 
{ 
$properties = [ 
'id' => $this->id, 
'name' => $this->genericInfo->getName(), 
'description' => $this->genericInfo->getDescription(), 
'country' => $this->genericInfo->getCountry(), 
]; 
Return a read-only object 
$teams = new ArrayCollection(); 
foreach ($this->teams as $team) { 
$teams->add($team->render()); 
} 
$properties['teams'] = $teams; 
return new ArrayCollection($properties); 
} 
}
<?php 
class Team 
{ 
private $id; 
private $players; 
public function __construct(Uuid $uuid) 
{ 
$this->id = $uuid; 
$this->players = new ArrayCollection(); 
} 
public function firePlayer($id) 
{ 
foreach ($this->players as $key => $player) { 
if ($player->getId() == $id) { 
$this->players->remove($key); 
} 
} 
} 
}
<?php 
class Team 
{ 
private $id; 
private $players; 
Traverse the collections 
public function __construct(Uuid $uuid) 
{ 
$this->id = $uuid; 
$this->players = new ArrayCollection(); 
} 
public function firePlayer(Player $playerToFire) 
{ 
foreach ($this->players as $key => $player) { 
if ($player->getId() == $playerToFire->getId()) { 
$this->players->remove($key); 
} 
} 
} 
}
The Player should have a 
relation towards the Team?
Be iterative using TDD/BDD
<?php 
class LeagueTest extends PHPUnit_Framework_TestCase 
{ 
/** 
* @test 
* @expectedException 
*/ 
public function leagueMustHaveMaximumEightTeams() 
{ 
// … 
$genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); 
$league = new League($uuid, $genericInfo); 
$team = $this->getMockBuilder('Team') // … 
for ($x=0; $x<=8; $x++) { 
$league->registerTeam($team); 
} 
} 
}
<?php 
class LeagueTest extends PHPUnit_Framework_TestCase 
{ 
/** 
* @test 
* @expectedException 
*/ 
public function leagueMustHaveMaximumEightTeams() 
{ 
// … 
$genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); 
$league = new League($uuid, $genericInfo); 
$team = $this->getMockBuilder('Team') // … 
for ($x=0; $x<=8; $x++) { 
$league->registerTeam($team); 
} 
} 
}
<?php 
class LeagueTest extends PHPUnit_Framework_TestCase 
{ 
/** 
* @test 
* @expectedException 
*/ 
public function leagueMustHaveMaximumEightTeams() 
{ 
// … 
The same Team 
can do more than one registration 
to the League?!? 
$genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); 
$league = new League($uuid, $genericInfo); 
$team = $this->getMockBuilder('Team') // … 
for ($x=0; $x<=8; $x++) { 
$league->registerTeam($team); 
} 
} 
}
<?php 
class League 
{ 
//.. 
public function registerTeam(Team $team) 
{ 
$this->canLeagueAcceptRegistrationOf($team); 
$this->teams->add($team); 
} 
private function canLeagueAcceptRegistrationOf(Team $applicantTeam) 
{ 
if (!$this->canLeagueAcceptAnotherRegistration()) { 
throw new DomainException('Not more places available'); 
} 
foreach ($this->teams as $key => $team) { 
if ($team->getId() == $applicantTeam->getId()) { 
throw new DomainException('Team already registered'); 
} 
} 
} 
}
<?php 
class League 
{ 
//.. 
public function registerTeam(Team $team) 
{ 
$this->canLeagueAcceptRegistrationOf($team); 
$this->teams->add($team); 
} 
And so on ... 
private function canLeagueAcceptRegistrationOf(Team $applicantTeam) 
{ 
if (!$this->canLeagueAcceptAnotherRegistration()) { 
throw new DomainException('Not more places available'); 
} 
foreach ($this->teams as $key => $team) { 
if ($team->getId() == $applicantTeam->getId()) { 
throw new DomainException('Team already registered'); 
} 
} 
} 
}
2. Doctrine (v2.5)
Awareness
We are using the entities of 
the Persistence Model as 
entities of our Domain 
Awareness 
Model
League.orm.yml 
League: 
type: entity 
table: league 
embedded: 
id: 
class: ValueObjectUuid 
genericInfo: 
class: ValueObjectLeagueGenericInfo 
oneToMany: 
contratti: 
targetEntity: Team 
mappedBy: league 
fetch: EXTRA_LAZY
League.orm.yml 
League: 
type: entity 
table: league 
embedded: 
id: 
class: ValueObjectUuid 
genericInfo: 
class: ValueObjectLeagueGenericInfo 
oneToMany: 
contratti: 
targetEntity: Team 
mappedBy: league 
fetch: EXTRA_LAZY
League.orm.yml 
League: 
type: entity 
table: league 
embedded: 
id: 
class: ValueObjectUuid 
genericInfo: 
class: ValueObjectLeagueGenericInfo 
oneToMany: 
contratti: 
targetEntity: Team 
mappedBy: league 
fetch: EXTRA_LAZY 
Uuid.orm.yml 
Uuid: 
type: embeddable 
id: 
uuid: 
type: string 
length: 36
League.orm.yml 
League: 
type: entity 
table: league 
embedded: 
id: 
class: ValueObjectUuid 
genericInfo: 
class: ValueObjectLeagueGenericInfo 
oneToMany: 
contratti: 
targetEntity: Team 
mappedBy: league 
fetch: EXTRA_LAZY 
Uuid.orm.yml 
Uuid: 
type: embeddable 
id: 
uuid: 
type: string 
length: 36
<?php 
class TeamRepository implements TeamRepositoryInterface 
{ 
private $em; 
public function __construct(EntityManager $em) 
{ 
$this->em = $em; 
} 
}
Persisting entities 
<?php 
class TeamRepository implements TeamRepositoryInterface 
{ 
public function add(Team $team) 
{ 
$this->em->persist($team); 
$this->em->flush(); 
} 
}
Avoid collection hydration 
(foreach, toArray) 
<?php 
class TeamRepository implements TeamRepositoryInterface 
{ 
public function getWithoutPlayers($id) 
{ 
$qb = $this->em->createQueryBuilder(); 
$qb 
->select('t', 'p') 
->from("Team", 't') 
->leftJoin('t.players', 'p', Join::WITH, $qb->expr()->andX( 
$qb->expr()->eq('p.id.uuid', ':pid') 
)) 
->where('c.id.uuid = :id') 
->setMaxResults(1); 
$qb->setParameter('id', $id); 
$qb->setParameter('pid', null); 
return $qb->getQuery()->getOneOrNullResult(); 
} 
}
Retrieve an object joined with empty collection 
<?php 
class TeamRepository implements TeamRepositoryInterface 
{ 
public function getWithPlayers($id) 
{ 
$qb = $this->em->createQueryBuilder(); 
$qb 
->select('t', 'p') 
->from("Team", 't') 
->leftJoin(t.players', 'p', Join::WITH, $qb->expr()->andX( 
$qb->expr()->eq('p.status', ':status') 
)) 
->where('t.id.uuid = :id'); 
$qb->setParameter('status', 'on_the_market'); 
$qb->setParameter('id', $id); 
return $qb->getQuery()->getOneOrNullResult(); 
} 
}
Get paginated list of Teams with Player joined 
<?php 
use DoctrineORMToolsPaginationPaginator; 
class TeamRepository implements TeamRepositoryInterface 
{ 
public function paginate($first, $max) 
{ 
$qb = $this->em->createQueryBuilder(); 
$qb 
->select('t', 'p') 
->from("Team", 't') 
->leftJoin('t.players', 'p') 
->setFirstResult($first) 
->setMaxResults($max); 
$paginator = new Paginator($qb->getQuery()); 
return $paginator->getIterator(); 
} 
}
3. Symfony (v2.5)
<?php 
class FirePlayerCommand implements Command 
{ 
public $teamId; 
public $playerId; 
public function getRequest() 
{ 
return new Request( 
[ 
'teamId' => $this->teamId, 
'playerId' => $this->playerId 
] 
); 
} 
}
<?php 
class Request extends ArrayCollection implements RequestInterface 
{ 
public function __construct(array $values) 
{ 
parent::__construct($values); 
} 
public function get($key, $default = null) 
{ 
if (!parent::containsKey($key)) { 
throw new DomainException(); 
} 
$value = parent::get($key); 
if (!$value && $default) { 
return $default; 
} 
return $value; 
} 
}
<?php 
class CommandHandler 
{ 
private $dispatcher; 
private $useCases; 
public function __construct(EventDispatcherInterface $dispatcher) 
{ 
$this->dispatcher = $dispatcher; 
} 
public function registerUseCases($useCases) 
{ 
foreach ($useCases as $useCase) { 
if ($useCase instanceof UseCase) { 
$this->useCases[$useCase->getManagedCommand()] = $useCase; 
} else { 
throw new LogicException(''); 
} 
} 
} 
// ...}
<?php 
class CommandHandler 
{ 
// ... 
public function execute(Command $command) 
{ 
try { 
$this->dispatcher 
->dispatch(Events::PRE_COMMAND, new CommandEvent($command)); 
$this->useCases[get_class($command)]->run($command); 
$response = new Response(); 
$this->dispatcher 
->dispatch(Events::POST_COMMAND, new PostCommandEvent($command, $response)); 
return $response; 
} catch (DomainException $e) { 
$this->dispatcher 
->dispatch(Events::EXCEPTION, new ExceptionEvent($command, $e)); 
return new Response($e->getMessage(), Response::STATUS_KO); 
} 
} 
}
<?php 
class CommandHandler 
{ 
// ... 
public function execute(Command $command) 
{ 
try { 
$this->dispatcher 
->dispatch(Events::PRE_COMMAND, new CommandEvent($command)); 
$this->useCases[get_class($command)]->run($command); 
$response = new Response(); 
$this->dispatcher 
->dispatch(Events::POST_COMMAND, new PostCommandEvent($command, $response)); 
return $response; 
} catch (DomainException $e) { 
$this->dispatcher 
->dispatch(Events::EXCEPTION, new ExceptionEvent($command, $e)); 
return new Response($e->getMessage(), Response::STATUS_KO); 
} 
} 
}
<?php 
class FirePlayerUseCase implements UseCase 
{ 
private $repository; 
public function __construct(TeamRepositoryInterface $repository) 
{ 
$this->repository = $repository; 
} 
public function run(Command $command) 
{ 
$request = $command->getRequest(); 
$team = $this->repository->get( 
$request->get('teamId') 
); 
$team->firePlayer( 
$request->get('playerId') 
); 
$this->repository->add($team); 
} 
}
Commands and Use Cases could be used standalone
<?php 
class CommandHandlerCompilerPass implements CompilerPassInterface 
{ 
public function process(ContainerBuilder $container) 
{ 
if (!$container->hasDefinition('command_handler')) { 
return; 
} 
$definition = $container->getDefinition('command_handler'); 
$taggedServices = $container->findTaggedServiceIds('use_case'); 
$useCases = array(); 
foreach ($taggedServices as $id => $attributes) { 
$useCases[] = new Reference($id); 
} 
$definition->addMethodCall( 
'registerUseCases', 
array($useCases) 
); 
} 
}
<service id="use_case.fire_player" public="false" class="UseCaseFirePlayerUseCase"> 
<argument type="service" id="repository_team"/> 
<tag name="use_case" /> 
</service>
<?php 
class MyController extends Controller 
{ 
public function modifyLeagueAction(Request $request, $id) 
{ 
$reader = $this->get('reader'); 
$league = $reader->getLeague($id); 
$command = ModifyLeagueCommand::fromArray($league); 
$form = $this->createForm(new ModifyLeagueType(), $command); 
$form->handleRequest($request); 
if ($form->isValid()) { 
$commandHandler = $this->get('command_handler'); 
$response = $commandHandler->execute($command); 
if ($response->isOk()) { 
//... 
} 
} 
return array( 
'form' => $form->createView() 
); 
} 
}
<?php 
class MyController extends Controller 
{ 
public function modifyLeagueAction(Request $request, $id) 
{ 
$reader = $this->get('reader'); 
$league = $reader->getLeague($id); 
$command = ModifyLeagueCommand::fromArray($league); 
$form = $this->createForm(new ModifyLeagueType(), $command); 
$form->handleRequest($request); 
if ($form->isValid()) { 
$commandHandler = $this->get('command_handler'); 
$response = $commandHandler->execute($command); 
if ($response->isOk()) { 
//... 
} 
} 
return array( 
'form' => $form->createView() 
); 
} 
}
<?php 
class MyController extends Controller 
{ 
public function modifyLeagueAction(Request $request, $id) 
{ 
$reader = $this->get('reader'); 
Consider using a service for 
$league = $reader->getLeague($id); 
reading $command = ModifyLeagueCommand::operations, fromArray($league); 
instead 
$form = $this->createForm(new ModifyLeagueType(), $command); 
use $form->the handleRequest($repository request); 
directly 
if ($form->isValid()) { 
$commandHandler = $this->get('command_handler'); 
$response = $commandHandler->execute($command); 
if ($response->isOk()) { 
//... 
} 
} 
return array( 
'form' => $form->createView() 
); 
} 
}
<?php 
class ModifyLeagueType extends CreateNewsType 
{ 
public function buildForm(FormBuilderInterface $builder, array $options) 
{ 
$builder 
->add('leagueId’, ‘’hidden') 
->add('name') 
->add('save', 'submit') 
; 
} 
public function setDefaultOptions(OptionsResolverInterface $resolver) 
{ 
$resolver->setDefaults(array( 
'data_class' => ModifyLeagueCommand::CLASS, 
)); 
} 
}
<?php 
class ModifyLeagueCommand implements Command 
{ 
public $leagueId; 
public $name; 
public function getRequest() 
{ 
return new Request( 
[ 
leagueId => $this->leagueId, 
name => $this->name 
] 
); 
} 
}
Command validation 
ModifyLeagueCommand: 
properties: 
leagueId: 
- NotBlank: ~ 
name: 
- NotBlank: ~
Using CQRS and Event Sourcing things change deeply
Rich domain model with symfony 2.5 and doctrine 2.5
?
https://joind.in/talk/view/12214
Credits 
A special thanks to @_orso_ the first who told me about rich models 
- Eric Evans - "Domain Driven Design" 
- Vaughn Vernon - “Implementing Domain-Driven Design” 
- http://www.slideshare.net/ziobrando/gestire-la-complessit-con-domain-driven-design 
- http://verraes.net/2013/12/related-entities-vs-child-entities/ 
- http://www.whitewashing.de/2012/08/22/building_an_object_model__no_setters_allowed.html 
- http://www.infoq.com/articles/ddd-contextmapping 
- http://nicolopignatelli.me/valueobjects-a-php-immutable-class-library/ 
- http://welcometothebundle.com/domain-driven-design-and-symfony-for-simple-app/ 
- http://www.slideshare.net/perprogramming/application-layer-33335917 
- http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/ 
- http://www.slideshare.net/thinkddd/practical-domain-driven-design-cqrs-and-messaging-architectures 
- http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/ 
- http://gojko.net/2009/09/30/ddd-and-relational-databases-the-value-object-dilemma/
Credits 
- http://verraes.net/2013/06/unbreakable-domain-models/ 
- http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model 
- http://martinfowler.com/bliki/BoundedContext.html 
- http://www.substanceofcode.com/2007/01/17/from-anemic-to-rich-domain-model/ 
- http://gorodinski.com/blog/2012/04/25/read-models-as-a-tactical-pattern-in-domain-driven-design-ddd/ 
- http://www.sapiensworks.com/blog/post/2013/05/01/DDD-Persisting-Aggregate-Roots-In-A-Unit-Of-Work.aspx 
- http://simon-says-architecture.com/2011/09/06/ddd-by-the-book/ 
- http://scaledagileframework.com/domain-modeling/ 
- http://www.codeproject.com/Articles/555855/Introduction-to-CQRS
1 of 90

Recommended

Symfony2, creare bundle e valore per il cliente by
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
1.3K views73 slides
The IoC Hydra - Dutch PHP Conference 2016 by
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
1.2K views127 slides
Doctrine fixtures by
Doctrine fixturesDoctrine fixtures
Doctrine fixturesBill Chang
5K views12 slides
Forget about index.php and build you applications around HTTP! by
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Kacper Gunia
5.7K views128 slides
The IoC Hydra by
The IoC HydraThe IoC Hydra
The IoC HydraKacper Gunia
3.3K views124 slides
Design how your objects talk through mocking by
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mockingKonstantin Kudryashov
6.2K views103 slides

More Related Content

What's hot

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need by
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needKacper Gunia
50.4K views140 slides
Decoupling the Ulabox.com monolith. From CRUD to DDD by
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
1.8K views83 slides
Silex meets SOAP & REST by
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
14.8K views62 slides
The Zen of Lithium by
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
2.6K views118 slides
PHP 5.3 and Lithium: the most rad php framework by
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkG Woo
5.9K views58 slides
The History of PHPersistence by
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistenceHugo Hamon
2.3K views75 slides

What's hot(20)

Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need by Kacper Gunia
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Kacper Gunia50.4K views
Decoupling the Ulabox.com monolith. From CRUD to DDD by Aleix Vergés
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
Aleix Vergés1.8K views
Silex meets SOAP & REST by Hugo Hamon
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon14.8K views
The Zen of Lithium by Nate Abele
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
Nate Abele2.6K views
PHP 5.3 and Lithium: the most rad php framework by G Woo
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
G Woo5.9K views
The History of PHPersistence by Hugo Hamon
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo Hamon2.3K views
Crafting beautiful software by Jorn Oomen
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen2K views
Models and Service Layers, Hemoglobin and Hobgoblins by Ross Tuck
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck48.3K views
Database Design Patterns by Hugo Hamon
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon11.4K views
Lithium: The Framework for People Who Hate Frameworks by Nate Abele
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
Nate Abele12.2K views
Doctrine For Beginners by Jonathan Wage
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage1.5K views
CQRS and Event Sourcing in a Symfony application by Samuel ROZE
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE12.7K views
Rich Model And Layered Architecture in SF2 Application by Kirill Chebunin
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin3.7K views
The Origin of Lithium by Nate Abele
The Origin of LithiumThe Origin of Lithium
The Origin of Lithium
Nate Abele8.4K views
Hacking Your Way To Better Security - Dutch PHP Conference 2016 by Colin O'Dell
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Colin O'Dell863 views
Speed up your developments with Symfony2 by Hugo Hamon
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon4.5K views
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition by Nate Abele
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
Nate Abele1.6K views
When cqrs meets event sourcing by Manel Sellés
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
Manel Sellés4.3K views

Viewers also liked

Clean architecture with ddd layering in php by
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in phpLeonardo Proietti
38.9K views177 slides
Effective Doctrine2: Performance Tips for Symfony2 Developers by
Effective Doctrine2: Performance Tips for Symfony2 DevelopersEffective Doctrine2: Performance Tips for Symfony2 Developers
Effective Doctrine2: Performance Tips for Symfony2 DevelopersMarcin Chwedziak
36.6K views66 slides
Symfony tips and tricks by
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricksJavier Eguiluz
54.3K views197 slides
Enterprise PHP: mappers, models and services by
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and servicesAaron Saray
12.1K views37 slides
Symfony in microservice architecture by
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architectureDaniele D'Angeli
29.9K views90 slides
Living documentation mini-workshop by
Living documentation mini-workshopLiving documentation mini-workshop
Living documentation mini-workshopMatthias Noback
685 views15 slides

Viewers also liked(20)

Clean architecture with ddd layering in php by Leonardo Proietti
Clean architecture with ddd layering in phpClean architecture with ddd layering in php
Clean architecture with ddd layering in php
Leonardo Proietti38.9K views
Effective Doctrine2: Performance Tips for Symfony2 Developers by Marcin Chwedziak
Effective Doctrine2: Performance Tips for Symfony2 DevelopersEffective Doctrine2: Performance Tips for Symfony2 Developers
Effective Doctrine2: Performance Tips for Symfony2 Developers
Marcin Chwedziak36.6K views
Symfony tips and tricks by Javier Eguiluz
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
Javier Eguiluz54.3K views
Enterprise PHP: mappers, models and services by Aaron Saray
Enterprise PHP: mappers, models and servicesEnterprise PHP: mappers, models and services
Enterprise PHP: mappers, models and services
Aaron Saray12.1K views
Symfony in microservice architecture by Daniele D'Angeli
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architecture
Daniele D'Angeli29.9K views
Living documentation mini-workshop by Matthias Noback
Living documentation mini-workshopLiving documentation mini-workshop
Living documentation mini-workshop
Matthias Noback685 views
Performance tips for Symfony2 & PHP by Max Romanovsky
Performance tips for Symfony2 & PHPPerformance tips for Symfony2 & PHP
Performance tips for Symfony2 & PHP
Max Romanovsky1.6K views
Increase your performance and code quality by Dusko Vesin
Increase your performance and code qualityIncrease your performance and code quality
Increase your performance and code quality
Dusko Vesin733 views
Держим руку на пульсе проекта. Мониторинг PHP-приложений by Max Romanovsky
Держим руку на пульсе проекта. Мониторинг PHP-приложенийДержим руку на пульсе проекта. Мониторинг PHP-приложений
Держим руку на пульсе проекта. Мониторинг PHP-приложений
Max Romanovsky1.6K views
Things I Believe Now That I'm Old by Ross Tuck
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
Ross Tuck6.6K views
An Architectural Model for Adapting Domain-Specific AOM Applications by eduardomg23
An Architectural Model for Adapting Domain-Specific AOM ApplicationsAn Architectural Model for Adapting Domain-Specific AOM Applications
An Architectural Model for Adapting Domain-Specific AOM Applications
eduardomg23790 views
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015) by Matthias Noback
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
Hexagonal architecture - message-oriented software design (PHP Barcelona 2015)
Matthias Noback2.4K views
What mom never told you about bundle configurations - Symfony Live Paris 2012 by D
What mom never told you about bundle configurations - Symfony Live Paris 2012What mom never told you about bundle configurations - Symfony Live Paris 2012
What mom never told you about bundle configurations - Symfony Live Paris 2012
D 18.1K views
Autenticazione delle api con jwt e symfony (Italian) by Marco Albarelli
Autenticazione delle api con jwt e symfony (Italian)Autenticazione delle api con jwt e symfony (Italian)
Autenticazione delle api con jwt e symfony (Italian)
Marco Albarelli821 views
Building a documented RESTful API in just a few hours with Symfony by olrandir
Building a documented RESTful API in just a few hours with SymfonyBuilding a documented RESTful API in just a few hours with Symfony
Building a documented RESTful API in just a few hours with Symfony
olrandir1K views
Service approach for development REST API in Symfony2 by Sumy PHP User Grpoup
Service approach for development REST API in Symfony2Service approach for development REST API in Symfony2
Service approach for development REST API in Symfony2
Domain Model by dzenanr
Domain ModelDomain Model
Domain Model
dzenanr1.8K views

Similar to Rich domain model with symfony 2.5 and doctrine 2.5

Why is crud a bad idea - focus on real scenarios by
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosDivante
743 views27 slides
Lithium Best by
Lithium Best Lithium Best
Lithium Best Richard McIntyre
1.8K views129 slides
Adding Dependency Injection to Legacy Applications by
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
1.8K views77 slides
Dependency Injection by
Dependency InjectionDependency Injection
Dependency InjectionRifat Nabi
2.8K views34 slides
How Kris Writes Symfony Apps by
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
17.1K views116 slides
Bag Of Tricks From Iusethis by
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From IusethisMarcus Ramberg
6.4K views88 slides

Similar to Rich domain model with symfony 2.5 and doctrine 2.5(20)

Why is crud a bad idea - focus on real scenarios by Divante
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
Divante743 views
Adding Dependency Injection to Legacy Applications by Sam Hennessy
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy1.8K views
Dependency Injection by Rifat Nabi
Dependency InjectionDependency Injection
Dependency Injection
Rifat Nabi2.8K views
How Kris Writes Symfony Apps by Kris Wallsmith
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith17.1K views
Bag Of Tricks From Iusethis by Marcus Ramberg
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
Marcus Ramberg6.4K views
関西PHP勉強会 php5.4つまみぐい by Hisateru Tanaka
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
Hisateru Tanaka2.6K views
Can't Miss Features of PHP 5.3 and 5.4 by Jeff Carouth
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
Jeff Carouth3.1K views
Zend Framework Study@Tokyo #2 by Shinya Ohyanagi
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
Shinya Ohyanagi1.3K views
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 by Alessandro Nadalin
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Alessandro Nadalin9.4K views
Apostrophe by tompunk
ApostropheApostrophe
Apostrophe
tompunk5.4K views
Как получить чёрный пояс по WordPress? by Yevhen Kotelnytskyi
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?
Yevhen Kotelnytskyi1.6K views
PHPCon 2016: PHP7 by Witek Adamus / XSolve by XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve1K views
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance by Ivan Chepurnyi
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Ivan Chepurnyi25.7K views

Recently uploaded

Uni Systems for Power Platform.pptx by
Uni Systems for Power Platform.pptxUni Systems for Power Platform.pptx
Uni Systems for Power Platform.pptxUni Systems S.M.S.A.
58 views21 slides
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue by
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueShapeBlue
96 views20 slides
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...ShapeBlue
77 views12 slides
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti... by
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...ShapeBlue
46 views29 slides
Ransomware is Knocking your Door_Final.pdf by
Ransomware is Knocking your Door_Final.pdfRansomware is Knocking your Door_Final.pdf
Ransomware is Knocking your Door_Final.pdfSecurity Bootcamp
76 views46 slides
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda... by
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...ShapeBlue
63 views13 slides

Recently uploaded(20)

Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue by ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
ShapeBlue96 views
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by ShapeBlue
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
ShapeBlue77 views
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti... by ShapeBlue
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
ShapeBlue46 views
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda... by ShapeBlue
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...
ShapeBlue63 views
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ... by ShapeBlue
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...
ShapeBlue65 views
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T by ShapeBlue
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
ShapeBlue56 views
DRBD Deep Dive - Philipp Reisner - LINBIT by ShapeBlue
DRBD Deep Dive - Philipp Reisner - LINBITDRBD Deep Dive - Philipp Reisner - LINBIT
DRBD Deep Dive - Philipp Reisner - LINBIT
ShapeBlue62 views
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue by ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlueCloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
ShapeBlue46 views
Business Analyst Series 2023 - Week 3 Session 5 by DianaGray10
Business Analyst Series 2023 -  Week 3 Session 5Business Analyst Series 2023 -  Week 3 Session 5
Business Analyst Series 2023 - Week 3 Session 5
DianaGray10369 views
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT by ShapeBlue
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBITUpdates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
ShapeBlue91 views
Keynote Talk: Open Source is Not Dead - Charles Schulz - Vates by ShapeBlue
Keynote Talk: Open Source is Not Dead - Charles Schulz - VatesKeynote Talk: Open Source is Not Dead - Charles Schulz - Vates
Keynote Talk: Open Source is Not Dead - Charles Schulz - Vates
ShapeBlue119 views
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava... by ShapeBlue
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...
Centralized Logging Feature in CloudStack using ELK and Grafana - Kiran Chava...
ShapeBlue48 views
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院 by IttrainingIttraining
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院
Data Integrity for Banking and Financial Services by Precisely
Data Integrity for Banking and Financial ServicesData Integrity for Banking and Financial Services
Data Integrity for Banking and Financial Services
Precisely56 views
PharoJS - Zürich Smalltalk Group Meetup November 2023 by Noury Bouraqadi
PharoJS - Zürich Smalltalk Group Meetup November 2023PharoJS - Zürich Smalltalk Group Meetup November 2023
PharoJS - Zürich Smalltalk Group Meetup November 2023
Noury Bouraqadi141 views

Rich domain model with symfony 2.5 and doctrine 2.5

  • 1. Rich Domain Model with Symfony2 and Doctrine2 Leonardo Proietti @_leopro_ Symfony Day Italy Milan, 10 October 2014
  • 4. domain = problem space domain model = solution space
  • 5. Domain “A Domain [...] is what an organization does and the world it does in.” (Vaughn Vernon, “Implementing Domain-Driven Design”)
  • 6. Domain Model “A model is a simplification. It is an interpretation of reality that abstracts the aspects relevant to solving problem at hand and ignores extraneous detail.” (Eric Evans, "Domain Driven Design")
  • 8. Domain Model “In DDD, domain model refers to a class.” (Julie Lerman, http://msdn.microsoft.com/en-us/magazine/dn385704.aspx)
  • 9. Domain Model “An object model of the domain that incorporates both behavior and data.” (Martin Fowler, http://martinfowler.com/eaaCatalog/domainModel.html)
  • 10. Domain Model “An object model of the domain that incorporates both behavior and data.” (Martin Fowler, http://martinfowler.com/eaaCatalog/domainModel.html)
  • 12. First, because it’s not anemic Martin Fowler http://martinfowler.com/bliki/AnemicDomainModel.html
  • 13. <?php class Anemic { private $shouldNotChangeAfterCreation; private $shouldBeChangedOnlyOnEdit; private $couldBeChangedAnytime; public function setCouldBeChangedAnytime($couldBeChangedAnytime) {} public function setShouldBeChangedOnlyOnEdit($shouldBeChangedOnlyOnEdit) {} public function setShouldNotChangeAfterCreation($shouldNotChangeAfterCreation {} }
  • 14. <?php class SomeService { public function doStuffOnCreation() { $anemic = new Anemic(); $anemic->setCouldBeChangedAnytime('abc'); $anemic->setShouldNotChangeAfterCreation('def'); $unitOfWork->persist($anemic); $unitOfWork->flush(); } }
  • 15. <?php class SomeService { public function doStuffOnEdit() { $anemic = new Anemic(); $anemic->setCouldBeChangedAnytime('abc'); $anemic->setShouldBeChangedOnlyOnEdit(‘123’); $unitOfWork->persist($anemic); $unitOfWork->flush(); } }
  • 16. <?php class SomeService { public function doStuffOnEdit() { Loss of memory $anemic = new Anemic(); $anemic->setCouldBeChangedAnytime('abc'); $anemic->setShouldBeChangedOnlyOnEdit(‘123’); $unitOfWork->persist($anemic); $unitOfWork->flush(); } }
  • 17. <?php class SomeService { public function doStuffOnEdit() { $anemic = new Anemic(); $anemic->setCouldBeChangedAnytime('abc'); $anemic->setShouldBeChangedOnlyOnEdit(‘123’); $anemic->setShouldNotChangeAfterCreation('def'); $unitOfWork->persist($anemic); $unitOfWork->flush(); } }
  • 18. <?php class BehaviouralClass { private $shouldNotChangeAfterCreation; private $shouldBeChangedOnlyOnEdit; private $couldBeChangedAnytime; public function __construct($shouldNotChangeAfterCreation, $couldBeChangedAnytime) { $this->shouldNotChangeAfterCreation = $shouldNotChangeAfterCreation; $this->couldBeChangedAnytime = $couldBeChangedAnytime; } public function modify($shouldBeChangedOnlyOnEdit, $couldBeChangedAnytime = null) { $this->shouldBeChangedOnlyOnEdit = $shouldBeChangedOnlyOnEdit; $this->couldBeChangedAnytime = $couldBeChangedAnytime; } }
  • 19. <?php class BehaviouralClass { private $shouldNotChangeAfterCreation; private $shouldBeChangedOnlyOnEdit; private $couldBeChangedAnytime; It's not still rich, lacks of ... public function __construct($shouldNotChangeAfterCreation, $couldBeChangedAnytime) { $this->shouldNotChangeAfterCreation = $shouldNotChangeAfterCreation; $this->couldBeChangedAnytime = $couldBeChangedAnytime; } public function modify($shouldBeChangedOnlyOnEdit, $couldBeChangedAnytime = null) { $this->shouldBeChangedOnlyOnEdit = $shouldBeChangedOnlyOnEdit; $this->couldBeChangedAnytime = $couldBeChangedAnytime; } }
  • 20. Ubiquitous Language “the domain model can provide the backbone for that common language [...]. The vocabulary of that ubiquitius language includes the names of classes and prominent operations” (Eric Evans, "Domain Driven Design")
  • 21. What does “coffee” mean? Alberto Brandolini AKA @ziobrando
  • 22. A bit of strategy
  • 23. Our domain is an online game that simulate the soccer’s world.
  • 25. What does “player” mean in our domain?
  • 26. The meaning of “player” Within game engine context a model of a real soccer player, modelled with behaviours to fit the requirements of the game engine.
  • 27. The meaning of “player” Within data import context a model of a real soccer player, but modelled for a simple CRUD.
  • 28. The meaning of “player” Within user profile context a model of the user of the website, who plays the game.
  • 29. League Team Calendar Player Lineup Coach Core context
  • 30. League Team Calendar Player Lineup Coach (the player in “user context”) Core context
  • 31. Player Game context User context Uuid Name Roles Uuid Email Password
  • 33. <?php class League { private $id; private $name; private $teams; public function __construct(Uuid $uuid, $name) { $this->id = $uuid; $this->name = $name; $this->teams = new ArrayCollection(); } public function registerTeam(Team $team) { $this->teams->add($team); } }
  • 34. <?php class League { private $id; private $name; private $teams; A team must do a registration public function __construct({ to the league Uuid $uuid, $name) $this->id = $uuid; $this->name = $name; $this->teams = new ArrayCollection(); } public function registerTeam(Team $team) { $this->teams->add($team); } }
  • 35. <?php class League { private $id; private $genericInfo; private $teams; public function __construct(Uuid $uuid, LeagueGenericInfo $leagueGenericInfo) { $this->id = $uuid; $this->genericInfo = $leagueGenericInfo; $this->teams = new ArrayCollection(); } // ...}
  • 36. <?php class LeagueGenericInfo { private $name; private $description; private $country; public function __construct($country, $description, $name) { $this->country = $country; $this->description = $description; $this->name = $name; } // … getters and behaviours}
  • 37. <?php class LeagueGenericInfo { private $name; private $description; private $country; Value object public function __construct($country, $description, $name) { $this->country = $country; $this->description = $description; $this->name = $name; } // … getters and behaviours}
  • 39. <?php class League { // ... public function registerTeam(Team $team) { if (!$this->canLeagueAcceptAnotherRegistration()) { throw new DomainException('Not more places available'); } $this->teams->add($team); } private function canLeagueAcceptAnotherRegistration() { if ($this->teams->count() == 8) { return false; } return true; } }
  • 40. <?php class League { // ... public function registerTeam(Team $team) { if (!$this->canLeagueAcceptAnotherRegistration()) { League protects its invariants throw new DomainException('Not more places available'); } $this->teams->add($team); } private function canLeagueAcceptAnotherRegistration() { if ($this->teams->count() == 8) { return false; } return true; } }
  • 41. <?php class League { // ... public function getTeams() { return $this->teams; } }
  • 42. <?php class League { // ... private function getTeams() { return $this->teams; } }
  • 43. <?php class LeagueGenericInfo { private $name; private $description; private $country; private static $countries; public function __construct($country, $description, $name) { if(!isset(static::$countries)) { static::$countries = require __DIR__.'/countries.php'; } if (!array_key_exists($name, static::$countries)) { throw new UnknownCountryException($country); } $this->country = $country; // .. thanks to Mathias Verraes for “Money” ;-) }}
  • 44. <?php class LeagueGenericInfo { private $name; private $description; private $country; Input validation private static $countries; public function __construct($country, $description, $name) { if(!isset(static::$countries)) { static::$countries = require __DIR__.'/countries.php'; } if (!array_key_exists($name, static::$countries)) { throw new UnknownCountryException($country); } $this->country = $country; // .. thanks to Mathias Verraes for “Money” ;-) }}
  • 45. <?php class LeagueGenericInfo { private $name; private $description; private $country; Could be private static also $countries; placed in commands public function __construct($country, $description, $name) { if(!isset(static::$countries)) { static::$countries = require __DIR__.'/countries.php'; } if (!array_key_exists($name, static::$countries)) { throw new UnknownCountryException($country); } $this->country = $country; // .. thanks to Mathias Verraes for “Money” ;-) }}
  • 46. <?php class League { public function render() { $properties = [ 'id' => $this->id, 'name' => $this->genericInfo->getName(), 'description' => $this->genericInfo->getDescription(), 'country' => $this->genericInfo->getCountry(), ]; $teams = new ArrayCollection(); foreach ($this->teams as $team) { $teams->add($team->render()); } $properties['teams'] = $teams; return new ArrayCollection($properties); } }
  • 47. <?php class League { public function render() { $properties = [ 'id' => $this->id, 'name' => $this->genericInfo->getName(), 'description' => $this->genericInfo->getDescription(), 'country' => $this->genericInfo->getCountry(), ]; Return a read-only object $teams = new ArrayCollection(); foreach ($this->teams as $team) { $teams->add($team->render()); } $properties['teams'] = $teams; return new ArrayCollection($properties); } }
  • 48. <?php class Team { private $id; private $players; public function __construct(Uuid $uuid) { $this->id = $uuid; $this->players = new ArrayCollection(); } public function firePlayer($id) { foreach ($this->players as $key => $player) { if ($player->getId() == $id) { $this->players->remove($key); } } } }
  • 49. <?php class Team { private $id; private $players; Traverse the collections public function __construct(Uuid $uuid) { $this->id = $uuid; $this->players = new ArrayCollection(); } public function firePlayer(Player $playerToFire) { foreach ($this->players as $key => $player) { if ($player->getId() == $playerToFire->getId()) { $this->players->remove($key); } } } }
  • 50. The Player should have a relation towards the Team?
  • 52. <?php class LeagueTest extends PHPUnit_Framework_TestCase { /** * @test * @expectedException */ public function leagueMustHaveMaximumEightTeams() { // … $genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); $league = new League($uuid, $genericInfo); $team = $this->getMockBuilder('Team') // … for ($x=0; $x<=8; $x++) { $league->registerTeam($team); } } }
  • 53. <?php class LeagueTest extends PHPUnit_Framework_TestCase { /** * @test * @expectedException */ public function leagueMustHaveMaximumEightTeams() { // … $genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); $league = new League($uuid, $genericInfo); $team = $this->getMockBuilder('Team') // … for ($x=0; $x<=8; $x++) { $league->registerTeam($team); } } }
  • 54. <?php class LeagueTest extends PHPUnit_Framework_TestCase { /** * @test * @expectedException */ public function leagueMustHaveMaximumEightTeams() { // … The same Team can do more than one registration to the League?!? $genericInfo = new LeagueGenericInfo('it', 'my league', 'awesome league'); $league = new League($uuid, $genericInfo); $team = $this->getMockBuilder('Team') // … for ($x=0; $x<=8; $x++) { $league->registerTeam($team); } } }
  • 55. <?php class League { //.. public function registerTeam(Team $team) { $this->canLeagueAcceptRegistrationOf($team); $this->teams->add($team); } private function canLeagueAcceptRegistrationOf(Team $applicantTeam) { if (!$this->canLeagueAcceptAnotherRegistration()) { throw new DomainException('Not more places available'); } foreach ($this->teams as $key => $team) { if ($team->getId() == $applicantTeam->getId()) { throw new DomainException('Team already registered'); } } } }
  • 56. <?php class League { //.. public function registerTeam(Team $team) { $this->canLeagueAcceptRegistrationOf($team); $this->teams->add($team); } And so on ... private function canLeagueAcceptRegistrationOf(Team $applicantTeam) { if (!$this->canLeagueAcceptAnotherRegistration()) { throw new DomainException('Not more places available'); } foreach ($this->teams as $key => $team) { if ($team->getId() == $applicantTeam->getId()) { throw new DomainException('Team already registered'); } } } }
  • 59. We are using the entities of the Persistence Model as entities of our Domain Awareness Model
  • 60. League.orm.yml League: type: entity table: league embedded: id: class: ValueObjectUuid genericInfo: class: ValueObjectLeagueGenericInfo oneToMany: contratti: targetEntity: Team mappedBy: league fetch: EXTRA_LAZY
  • 61. League.orm.yml League: type: entity table: league embedded: id: class: ValueObjectUuid genericInfo: class: ValueObjectLeagueGenericInfo oneToMany: contratti: targetEntity: Team mappedBy: league fetch: EXTRA_LAZY
  • 62. League.orm.yml League: type: entity table: league embedded: id: class: ValueObjectUuid genericInfo: class: ValueObjectLeagueGenericInfo oneToMany: contratti: targetEntity: Team mappedBy: league fetch: EXTRA_LAZY Uuid.orm.yml Uuid: type: embeddable id: uuid: type: string length: 36
  • 63. League.orm.yml League: type: entity table: league embedded: id: class: ValueObjectUuid genericInfo: class: ValueObjectLeagueGenericInfo oneToMany: contratti: targetEntity: Team mappedBy: league fetch: EXTRA_LAZY Uuid.orm.yml Uuid: type: embeddable id: uuid: type: string length: 36
  • 64. <?php class TeamRepository implements TeamRepositoryInterface { private $em; public function __construct(EntityManager $em) { $this->em = $em; } }
  • 65. Persisting entities <?php class TeamRepository implements TeamRepositoryInterface { public function add(Team $team) { $this->em->persist($team); $this->em->flush(); } }
  • 66. Avoid collection hydration (foreach, toArray) <?php class TeamRepository implements TeamRepositoryInterface { public function getWithoutPlayers($id) { $qb = $this->em->createQueryBuilder(); $qb ->select('t', 'p') ->from("Team", 't') ->leftJoin('t.players', 'p', Join::WITH, $qb->expr()->andX( $qb->expr()->eq('p.id.uuid', ':pid') )) ->where('c.id.uuid = :id') ->setMaxResults(1); $qb->setParameter('id', $id); $qb->setParameter('pid', null); return $qb->getQuery()->getOneOrNullResult(); } }
  • 67. Retrieve an object joined with empty collection <?php class TeamRepository implements TeamRepositoryInterface { public function getWithPlayers($id) { $qb = $this->em->createQueryBuilder(); $qb ->select('t', 'p') ->from("Team", 't') ->leftJoin(t.players', 'p', Join::WITH, $qb->expr()->andX( $qb->expr()->eq('p.status', ':status') )) ->where('t.id.uuid = :id'); $qb->setParameter('status', 'on_the_market'); $qb->setParameter('id', $id); return $qb->getQuery()->getOneOrNullResult(); } }
  • 68. Get paginated list of Teams with Player joined <?php use DoctrineORMToolsPaginationPaginator; class TeamRepository implements TeamRepositoryInterface { public function paginate($first, $max) { $qb = $this->em->createQueryBuilder(); $qb ->select('t', 'p') ->from("Team", 't') ->leftJoin('t.players', 'p') ->setFirstResult($first) ->setMaxResults($max); $paginator = new Paginator($qb->getQuery()); return $paginator->getIterator(); } }
  • 70. <?php class FirePlayerCommand implements Command { public $teamId; public $playerId; public function getRequest() { return new Request( [ 'teamId' => $this->teamId, 'playerId' => $this->playerId ] ); } }
  • 71. <?php class Request extends ArrayCollection implements RequestInterface { public function __construct(array $values) { parent::__construct($values); } public function get($key, $default = null) { if (!parent::containsKey($key)) { throw new DomainException(); } $value = parent::get($key); if (!$value && $default) { return $default; } return $value; } }
  • 72. <?php class CommandHandler { private $dispatcher; private $useCases; public function __construct(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } public function registerUseCases($useCases) { foreach ($useCases as $useCase) { if ($useCase instanceof UseCase) { $this->useCases[$useCase->getManagedCommand()] = $useCase; } else { throw new LogicException(''); } } } // ...}
  • 73. <?php class CommandHandler { // ... public function execute(Command $command) { try { $this->dispatcher ->dispatch(Events::PRE_COMMAND, new CommandEvent($command)); $this->useCases[get_class($command)]->run($command); $response = new Response(); $this->dispatcher ->dispatch(Events::POST_COMMAND, new PostCommandEvent($command, $response)); return $response; } catch (DomainException $e) { $this->dispatcher ->dispatch(Events::EXCEPTION, new ExceptionEvent($command, $e)); return new Response($e->getMessage(), Response::STATUS_KO); } } }
  • 74. <?php class CommandHandler { // ... public function execute(Command $command) { try { $this->dispatcher ->dispatch(Events::PRE_COMMAND, new CommandEvent($command)); $this->useCases[get_class($command)]->run($command); $response = new Response(); $this->dispatcher ->dispatch(Events::POST_COMMAND, new PostCommandEvent($command, $response)); return $response; } catch (DomainException $e) { $this->dispatcher ->dispatch(Events::EXCEPTION, new ExceptionEvent($command, $e)); return new Response($e->getMessage(), Response::STATUS_KO); } } }
  • 75. <?php class FirePlayerUseCase implements UseCase { private $repository; public function __construct(TeamRepositoryInterface $repository) { $this->repository = $repository; } public function run(Command $command) { $request = $command->getRequest(); $team = $this->repository->get( $request->get('teamId') ); $team->firePlayer( $request->get('playerId') ); $this->repository->add($team); } }
  • 76. Commands and Use Cases could be used standalone
  • 77. <?php class CommandHandlerCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition('command_handler')) { return; } $definition = $container->getDefinition('command_handler'); $taggedServices = $container->findTaggedServiceIds('use_case'); $useCases = array(); foreach ($taggedServices as $id => $attributes) { $useCases[] = new Reference($id); } $definition->addMethodCall( 'registerUseCases', array($useCases) ); } }
  • 78. <service id="use_case.fire_player" public="false" class="UseCaseFirePlayerUseCase"> <argument type="service" id="repository_team"/> <tag name="use_case" /> </service>
  • 79. <?php class MyController extends Controller { public function modifyLeagueAction(Request $request, $id) { $reader = $this->get('reader'); $league = $reader->getLeague($id); $command = ModifyLeagueCommand::fromArray($league); $form = $this->createForm(new ModifyLeagueType(), $command); $form->handleRequest($request); if ($form->isValid()) { $commandHandler = $this->get('command_handler'); $response = $commandHandler->execute($command); if ($response->isOk()) { //... } } return array( 'form' => $form->createView() ); } }
  • 80. <?php class MyController extends Controller { public function modifyLeagueAction(Request $request, $id) { $reader = $this->get('reader'); $league = $reader->getLeague($id); $command = ModifyLeagueCommand::fromArray($league); $form = $this->createForm(new ModifyLeagueType(), $command); $form->handleRequest($request); if ($form->isValid()) { $commandHandler = $this->get('command_handler'); $response = $commandHandler->execute($command); if ($response->isOk()) { //... } } return array( 'form' => $form->createView() ); } }
  • 81. <?php class MyController extends Controller { public function modifyLeagueAction(Request $request, $id) { $reader = $this->get('reader'); Consider using a service for $league = $reader->getLeague($id); reading $command = ModifyLeagueCommand::operations, fromArray($league); instead $form = $this->createForm(new ModifyLeagueType(), $command); use $form->the handleRequest($repository request); directly if ($form->isValid()) { $commandHandler = $this->get('command_handler'); $response = $commandHandler->execute($command); if ($response->isOk()) { //... } } return array( 'form' => $form->createView() ); } }
  • 82. <?php class ModifyLeagueType extends CreateNewsType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('leagueId’, ‘’hidden') ->add('name') ->add('save', 'submit') ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => ModifyLeagueCommand::CLASS, )); } }
  • 83. <?php class ModifyLeagueCommand implements Command { public $leagueId; public $name; public function getRequest() { return new Request( [ leagueId => $this->leagueId, name => $this->name ] ); } }
  • 84. Command validation ModifyLeagueCommand: properties: leagueId: - NotBlank: ~ name: - NotBlank: ~
  • 85. Using CQRS and Event Sourcing things change deeply
  • 87. ?
  • 89. Credits A special thanks to @_orso_ the first who told me about rich models - Eric Evans - "Domain Driven Design" - Vaughn Vernon - “Implementing Domain-Driven Design” - http://www.slideshare.net/ziobrando/gestire-la-complessit-con-domain-driven-design - http://verraes.net/2013/12/related-entities-vs-child-entities/ - http://www.whitewashing.de/2012/08/22/building_an_object_model__no_setters_allowed.html - http://www.infoq.com/articles/ddd-contextmapping - http://nicolopignatelli.me/valueobjects-a-php-immutable-class-library/ - http://welcometothebundle.com/domain-driven-design-and-symfony-for-simple-app/ - http://www.slideshare.net/perprogramming/application-layer-33335917 - http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/ - http://www.slideshare.net/thinkddd/practical-domain-driven-design-cqrs-and-messaging-architectures - http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/ - http://gojko.net/2009/09/30/ddd-and-relational-databases-the-value-object-dilemma/
  • 90. Credits - http://verraes.net/2013/06/unbreakable-domain-models/ - http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model - http://martinfowler.com/bliki/BoundedContext.html - http://www.substanceofcode.com/2007/01/17/from-anemic-to-rich-domain-model/ - http://gorodinski.com/blog/2012/04/25/read-models-as-a-tactical-pattern-in-domain-driven-design-ddd/ - http://www.sapiensworks.com/blog/post/2013/05/01/DDD-Persisting-Aggregate-Roots-In-A-Unit-Of-Work.aspx - http://simon-says-architecture.com/2011/09/06/ddd-by-the-book/ - http://scaledagileframework.com/domain-modeling/ - http://www.codeproject.com/Articles/555855/Introduction-to-CQRS

Editor's Notes

  1. A model is a simplification. It is an interpretation of reality that abstracts the aspects relevant to solving problem at hand and ignores extraneous detail.
  2. il matching uno a uno tra sottodominio e bounded context è una condizione desiderabile, non un vincolo
  3. il modello cambia spesso, tanto quanto la conoscenza che acquisiamo e che ci porta a comprendere come risolvere i problemi; TDD si sposa bene
  4. non accoppiare con le annotations
  5. non accoppiare con le annotations
  6. non accoppiare con le annotations
  7. non accoppiare con le annotations
  8. non accoppiare con le annotations
  9. non accoppiare con le annotations
  10. non accoppiare con le annotations
  11. non accoppiare con le annotations
  12. non accoppiare con le annotations
  13. non accoppiare con le annotations
  14. non accoppiare con le annotations
  15. non accoppiare con le annotations
  16. non accoppiare con le annotations
  17. non accoppiare con le annotations
  18. non accoppiare con le annotations
  19. non accoppiare con le annotations
  20. non accoppiare con le annotations
  21. non accoppiare con le annotations
  22. non accoppiare con le annotations
  23. non accoppiare con le annotations