SlideShare a Scribd company logo
1 of 177
Download to read offline
Clean Architecture using
DDD layering in PHP
Leonardo Proietti
@_leopro_
1. Clean Architecture
Definition of Clean Architecture
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Definition of Clean Architecture
Independent of Frameworks
Testable
Independent of UI
Independent of Database
Independent of any external agency
Hey bro, I respect your opinion but ...
It isn't just my opinion
Do you know "Uncle Bob", isn't it?
I’m just another dwarf.
The Clean Architecture
The Dependency Rule
“This rule says that code dependencies can
only point inwards. Nothing in an inner circle
can know anything at all about something in an
outer circle.”
(http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)
The Dependency Rule
“This rule says that code dependencies can
only point inwards. Nothing in an inner circle
can know anything at all about something in an
outer circle.”
(http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html)
2. Domain Driven Design
What is Domain Driven Design?
What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
What is Domain Driven Design?
“it is a way of thinking and a set of priorities,
aimed at accelerating software projects that
have to deal with complicated domains”
(Eric Evans, "Domain Driven Design")
What is Domain Driven Design?
“is a collection of principles and patterns that
help developers craft elegant object systems”
(http://msdn.microsoft.com/en-us/magazine/dd419654.aspx)
What is Domain Driven Design?
“is a collection of principles and patterns that
help developers craft elegant object systems”
(http://msdn.microsoft.com/en-us/magazine/dd419654.aspx)
What is Domain Driven Design?
“is an approach to software development for
complex needs by connecting the
implementation to an evolving model”
(http://en.wikipedia.org/wiki/Domain-driven_design)
What is Domain Driven Design?
“is an approach to software development for
complex needs by connecting the
implementation to an evolving model”
(http://en.wikipedia.org/wiki/Domain-driven_design)
Mmhh interesting … but what does it mean?
Make yourself comfortable
3. DDD Core
Domain
“Every software program relates to some
activity or interest of its user. That subject area
to which the user applies the program is the
domain of the software”
(Eric Evans, "Domain Driven Design")
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")
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")
Sounds familiar?
Model
“A domain model [...] is not just the knowledge
in a domain expert’s head; it is a rigorously
organized and selective abstraction of that
knowledge.”
(Eric Evans, "Domain Driven Design")
Model
“A domain model [...] is not just the knowledge
in a domain expert’s head; it is a rigorously
organized and selective abstraction of that
knowledge.”
(Eric Evans, "Domain Driven Design")
Next it’s maybe the most important thing in DDD
Ubiquitous Language
“the domain model can provide the backbone
for that common language [...]. The vocabulary
of that UBIQUITOUS LANGUAGE includes the
names of classes and prominent operations”
(Eric Evans, "Domain Driven Design")
Ubiquitous Language
It’s a shared jargon between domain experts
and developers, based on Domain Model
Take care of Ubiquitous Language
What does “coffee” mean?
Alberto Brandolini AKA ziobrando
Ubiquitous Language
“changes to the language will be recognized as
changes in the domain model”
(Eric Evans, "Domain Driven Design")
Context
“The setting in which a word or statement
appears that determines its meaning.”
4. DDD Building Blocks
Entity
An object with “clear identity and a life-cycle
with state transitions that we care about.”
(http://dddsample.sourceforge.net/characterization.html)
Are these entities?
It depends.
It depends.
“We don't assign seats on our flights,
so feel free to sit in any available seat”
Value Object
“An object that contains attributes but has no
conceptual identity. They should be treated as
immutable.”
(http://en.wikipedia.org/wiki/Domain-driven_design)
Value Object
“A small simple object, like money or a date
range, whose equality isn't based on identity.”
(http://martinfowler.com/eaaCatalog/valueObject.html)
Are these value objects?
In most of the contexts, but ...
Beware about Anemic Domain Model
Beware about Anemic Domain Model
Both Entity and Value Object
should have data and behaviours.
(http://www.martinfowler.com/bliki/AnemicDomainModel.html)
Few other concepts
Repository
Aggregate
Domain Event
Repository
“A REPOSITORY represents all objects of a
certain type as a conceptual set. It acts like a
collection, except with more elaborate querying
capability”
(Eric Evans, "Domain Driven Design")
Repository
“All repositories provide methods that allow
client to request objects matching some
criteria”
(Eric Evans, "Domain Driven Design")
Repository
“Although most queries return an object or a
collection of objects, it also fits within the
concept to return some types of summary
calculation”
(Eric Evans, "Domain Driven Design")
Aggregate
“A DDD aggregate is a cluster of domain
objects that can be treated as a single unit.”
(http://martinfowler.com/bliki/DDD_Aggregate.html)
Aggregate
“DDD aggregates are domain concepts (order,
clinic visit, playlist), while collections are
generic.”
(http://martinfowler.com/bliki/DDD_Aggregate.html)
Domain Event
“Captures the memory of something interesting
which affects the domain”
(http://martinfowler.com/eaaDev/DomainEvent.html)
How long does it take?
5. DDD Layering
Layering
“We need to decouple the domain objects from
other functions of the system, so we can avoid
confusing the domain concepts wiht other
concepts”
(Eric Evans, "Domain Driven Design")
Layering
“We need to decouple the domain objects from
other functions of the system, so we can avoid
confusing the domain concepts wiht other
concepts”
(Eric Evans, "Domain Driven Design")
Layering
(http://guptavikas.wordpress.com/2009/12/01/domain-driven-design-an-introduction/)
Layering
(http://dddsample.sourceforge.net/architecture.html)
Domain
“The domain layer is the heart of the software,
and this is where the interesting stuff happens.”
(http://dddsample.sourceforge.net/architecture.html)
Application
“The application layer is responsible for driving
the workflow of the application, matching the
use cases at hand”
(http://dddsample.sourceforge.net/architecture.html)
Interface
“This layer holds everything that interacts with
other systems”
(http://dddsample.sourceforge.net/architecture.html)
Interface
“This layer holds everything that interacts with
other systems”
(http://dddsample.sourceforge.net/architecture.html)
Controller Form
View API
Infrastructure
“In simple terms, the infrastructure consists of everything
that exists independently of our application: external
libraries, database engine, application server, messaging
backend and so on.”
(http://dddsample.sourceforge.net/architecture.html)
Separation of concerns
Services
Services
“Sometimes, it just isn’t a thing.”
(Eric Evans, "Domain Driven Design")
Services
Domain Services
Application Services
Infrastructural Services
Domain Services
“If a SERVICE were devised to make
appropriate debits and credits for a found
transfer, that capability would belong in the
domain layer”
(Eric Evans, "Domain Driven Design")
Application Services
“if the banking application can convert and
export our transactions into a spreadsheet file
[...] that export is an application SERVICE”
(Eric Evans, "Domain Driven Design")
Infrastructural Services
“a bank might have an application that sends
an e-mail [...]. The interface that encapsulates
the email system, [...] is a SERVICE in the
infrastructure layer”
(Eric Evans, "Domain Driven Design")
6. Code First
Persistence Ignorance
“In DDD, we don't consider any databases.
DDD is all about the domain, not about the
database, and Persistence Ignorance (PI) is a
very important aspect of DDD”
(http://williamdurand.fr/2013/08/20/ddd-with-symfony2-making-things-clear/)
YAGNI
You aren't gonna need it.
You don’t need a Database or a Framework to
modelling the Domain
YAGNI
You aren't gonna need it.
You don’t need a Database or a Framework to
modelling the Domain
Where should I start then?!?
Understanding the Domain
Talking with domain experts
DDD is Agile, we should be iterative
(http://dddsample.sourceforge.net/architecture.html)
7. Let’s code
Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
Code
Here, a complete sample code:
https://github.com/leopro/trip-planner
You can follow the building steps, starting from the
first commit.
Code
Let’s focus on some steps
composer.json
{
"name": "my trip planner",
"autoload": {
"psr-0": { "": "src/" }
},
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
},
"require-dev": {
"phpunit/phpunit": "4.0.*"
},
"config": {
"bin-dir": "bin"
}
}
composer.json
{
"name": "my trip planner",
"autoload": {
"psr-0": { "": "src/" }
},
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "bin"
}
}
I don't need anything
more to start
composer.json
{
"name": "my trip planner",
"autoload": {
"psr-0": { "": "src/" }
},
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "bin"
}
}
Ok, I have a dependency on
doctrine/collections ...
composer.json
{
"name": "my trip planner",
"autoload": {
"psr-0": { "": "src/" }
},
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "bin"
}
}
… the missing (SPL)
Collection/Array/OrderedMap interface
composer.json
{
"name": "my trip planner",
"autoload": {
"psr-0": { "": "src/" }
},
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "bin"
}
}
Anyway, you can put a boundary
<?php
namespace LeoproTripPlannerDomainContract;
use DoctrineCommonCollectionsCollection as DoctrineCollection;
interface Collection extends DoctrineCollection {}
<?php
namespace LeoproTripPlannerDomainAdapter;
use DoctrineCommonCollectionsArrayCollection as DoctrineArrayCollection;
use LeoproTripPlannerDomainContractCollection;
class ArrayCollection extends DoctrineArrayCollection implements Collection {}
Domain
Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
<?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainEntityTrip;
class TripTest extends PHPUnit_Framework_TestCase
{
public function testCreateTripReturnATripWithFirstRoute()
{
$trip = Trip::create('my first planning');
$this->assertInstanceOf('LeoproTripPlannerDomainEntityTrip', $trip);
$this->assertEquals(1, $trip->getRoutes()->count());
}
}
<?php
namespace LeoproTripPlannerDomainEntity;
use LeoproTripPlannerDomainAdapterArrayCollection;
class Trip
{
private $name,
private $routes;
private function __construct($name, Route $route)
{
$this->name = $name;
$this->routes = new ArrayCollection(array($route));
}
public function create($name)
{
return new self($name, new Route);
}
public function getRoutes()
{
return $this->routes;
}
}
<?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
}
Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
<?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainEntityRoute;
class RouteTest extends PHPUnit_Framework_TestCase
{
public function testCreateRouteAddingALeg()
{
$route = Route::create('my first trip');
$route->addLeg('06-06-2014');
$this->assertEquals(1, $route->getLegs()->count());
}
<?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
private $name;
private $legs;
private function __construct($name)
{
$this->name = $name;
$this->legs = new ArrayCollection();
}
public static function create($tripName)
{
return new self('first route for trip: ' . $tripName);
}
public function addLeg($date)
{
$leg = Leg::create($date);
$this->legs->add($leg);
}
//...
<?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
private $name;
private $legs;
private function __construct($name)
{
$this->name = $name;
$this->legs = new ArrayCollection();
}
public static function create($tripName)
{
return new self('first route for trip: ' . $tripName);
}
public function addLeg($date)
{
$leg = Leg::create($date);
$this->legs->add($leg);
}
//...
Wait, we really want two legs
with the same date?
The model is changing
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg
and two leg with the same date for the
same route are not allowed .
A leg has one date and one location.
/**
* @expectedException ...DateAlreadyUsedException
*/
public function testNoDuplicationDateForTheSameRoute()
{
$route = Route::create('my first trip');
$route->addLeg('06-06-2014');
$route->addLeg('06-06-2014');
}
<?php
namespace LeoproTripPlannerDomainEntity;
class Route
{
//...
public function addLeg($date)
{
$leg = Leg::create($date);
$dateAlreadyUsed = function($key, $element) use($leg) {
return $element->getDate() == $leg->getDate();
};
if ($this->legs->exists($dateAlreadyUsed)) {
throw new DateAlreadyUsedException($date . ' already used');
}
$this->legs->add($leg);
}
//...
Our starting domain
I need a tool to plan my trips.
Every trip must have at least one route
and every route has one or more leg.
A leg has one date and one location.
<?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainEntityLeg;
class LegTest extends PHPUnit_Framework_TestCase
{
public function testCreateLegReturnsALegWithDateAndLocation()
{
$leg = Leg::create('01/01/2014', 'd/m/Y', -3.386665, 36.736908);
$this->assertInstanceOf('LeoproTripPlannerDomainEntityLeg', $leg);
$location = $leg->getLocation();
$this->assertInstanceOf('LeoproTripPlannerDomainEntityLocation', $location);
$point = $location->getPoint();
$this->assertInstanceOf('LeoproTripPlannerDomainValueObjectPoint', $point);
$this->assertEquals(-3.386665, $point->getLatitude());
$this->assertEquals(36.736908, $point->getLongitude());
}
}
<?php
namespace LeoproTripPlannerDomainEntity;
use LeoproTripPlannerDomainValueObjectDate;
class Leg
{
private $date;
private $location;
private function __construct(Date $date, Location $location)
{
$this->date = $date;
$this->location = $location;
}
public static function create($date, $dateFormat, $latitude, $longitude)
{
$date = new Date($date, $dateFormat);
return new self(
$date,
Location::create($date->getFormattedDate(), $latitude, $longitude)
);
}
//..
<?php
namespace LeoproTripPlannerDomainEntity;
use LeoproTripPlannerDomainValueObjectPoint;
class Location
{
private $name;
private $point;
private function __construct($name, Point $point)
{
$this->name = $name;
$this->point = $point;
}
public static function create($name, $latitude, $longitude)
{
return new self($name, new Point($latitude, $longitude)
);
}
public function getPoint()
{
return $this->point;
}
}
<?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainValueObjectPoint;
class PointTest extends PHPUnit_Framework_TestCase
{
public function testDistance()
{
$firstPoint = new Point(-3.386665, 36.736908);
$secondPoint = new Point(-3.428112, 35.932846);
$this->assertEquals(89, $firstPoint->getCartographicDistance($secondPoint));
$this->assertEquals(98, $firstPoint->getApproximateRoadDistance($secondPoint));
}
}
<?php
namespace LeoproTripPlannerDomainTests;
use LeoproTripPlannerDomainValueObjectPoint;
class PointTest extends PHPUnit_Framework_TestCase
{
public function testDistance()
{
$firstPoint = new Point(-3.386665, 36.736908);
$secondPoint = new Point(-3.428112, 35.932846);
$this->assertEquals(89, $firstPoint->getCartographicDistance($secondPoint));
$this->assertEquals(98, $firstPoint->getApproximateRoadDistance($secondPoint));
}
}
Value Object
getCartographicDistance()
getApproximateRoadDistance()
<?php
namespace LeoproTripPlannerDomainValueObject;
class Point
{
private $latitude;
private $longitude;
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}
//..
public function getApproximateRoadDistance(Point $point, $degreeApproximation = 10)
{
$distance = $this->getCartographicDistance($point);
return round($distance + $distance * ($degreeApproximation / 100));
}
public function getCartographicDistance(Point $point)
{
$earthRadius = 3958.75;
$dLat = deg2rad($point->getLatitude() - $this->latitude);
$dLng = deg2rad($point->getLongitude() - $this->longitude);
$a = sin($dLat / 2) * sin($dLat / 2) +
cos(deg2rad($this->latitude)) * cos(deg2rad($point->getLatitude())) *
sin($dLng / 2) * sin($dLng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$dist = $earthRadius * $c;
$meterConversion = 1.609344;
$geopointDistance = $dist * $meterConversion;
return round($geopointDistance, 0);
}
public function getCartographicDistance(Point $point)
{
$earthRadius = 3958.75;
$dLat = deg2rad($point->getLatitude() - $this->latitude);
$dLng = deg2rad($point->getLongitude() - $this->longitude);
$a = sin($dLat / 2) * sin($dLat / 2) +
cos(deg2rad($this->latitude)) * cos(deg2rad($point->getLatitude())) *
sin($dLng / 2) * sin($dLng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$dist = $earthRadius * $c;
$meterConversion = 1.609344;
$geopointDistance = $dist * $meterConversion;
return round($geopointDistance, 0);
}
Got the point?
Application
<?php
namespace LeoproTripPlannerApplicationCommand;
use LeoproTripPlannerApplicationUseCaseUseCaseInterface;
class CommandHandler
{
private $useCases;
public function registerCommands(array $useCases)
{
foreach ($useCases as $useCase) {
if ($useCase instanceof UseCaseInterface) {
$this->useCases[$useCase->getManagedCommand()] = $useCase;
} else {
throw new LogicException(‘...');
}
}
}
//...
<?php
namespace LeoproTripPlannerApplicationCommand;
use LeoproTripPlannerApplicationUseCaseUseCaseInterface;
class CommandHandler
{
//...
public function execute($command)
{
try {
$commandClass = get_class($command);
if (!array_key_exists($commandClass, $this->useCases)) {
throw new LogicException($commandClass . ' is not a managed command');
}
$this->useCases[get_class($command)]->run($command);
} catch (Exception $e) {
throw $e;
}
}
}
<?php
namespace LeoproTripPlannerApplicationCommand;
use LeoproTripPlannerApplicationUseCaseUseCaseInterface;
class CommandHandler
{
//...
public function execute($command)
{
try {
$commandClass = get_class($command);
if (!array_key_exists($commandClass, $this->useCases)) {
throw new LogicException($commandClass . ' is not a managed command');
}
$this->useCases[get_class($command)]->run($command);
} catch (Exception $e) {
throw $e;
}
}
}
You can move the state of the
domain, through commands
<?php
namespace LeoproTripPlannerApplicationCommand;
use LeoproTripPlannerApplicationContractCommandInterface;
use LeoproTripPlannerDomainAdapterArrayCollection;
class CreateTripCommand implements CommandInterface
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getRequest()
{
return new ArrayCollection(
array(
'name' => $this->name
)
);
}
}
<?php
namespace LeoproTripPlannerApplicationUseCase;
class CreateTripUseCase extends AbstractUseCase implements UseCaseInterface
{
private $tripRepository;
public function __construct(TripRepository $tripRepository)
{
$this->tripRepository = $tripRepository;
}
public function run(CommandInterface $command)
{
$this->exceptionIfCommandNotManaged($command);
$request = $command->getRequest();
$trip = Trip::createWithFirstRoute(new TripIdentity(uniqid()), $request->get('name'));
$this->tripRepository->add($trip);
return $trip;
}
}
<?php
namespace LeoproTripPlannerApplicationUseCase;
class CreateTripUseCase extends AbstractUseCase implements UseCaseInterface
{
private $tripRepository;
public function __construct(TripRepository $tripRepository)
{
$this->tripRepository = $tripRepository;
}
public function run(CommandInterface $command)
{
$this->exceptionIfCommandNotManaged($command);
$request = $command->getRequest();
$trip = Trip::createWithFirstRoute(new TripIdentity(uniqid()), $request->get('name'));
$this->tripRepository->add($trip);
return $trip;
}
}
Defining a TripRepository
interface ...
<?php
namespace LeoproTripPlannerDomainContract;
use LeoproTripPlannerDomainEntityTrip;
use LeoproTripPlannerDomainValueObjectTripIdentity;
interface TripRepository
{
/**
* @param TripIdentity $identity
* @return LeoproTripPlannerDomainEntityTrip
*/
public function get(TripIdentity $identity);
/**
* @param Trip $trip
* @return void
*/
public function add(Trip $trip);
}
<?php
namespace LeoproTripPlannerDomainContract;
use LeoproTripPlannerDomainEntityTrip;
use LeoproTripPlannerDomainValueObjectTripIdentity;
interface TripRepository
{
/**
* @param TripIdentity $identity
* @return LeoproTripPlannerDomainEntityTrip
*/
public function get(TripIdentity $identity);
/**
* @param Trip $trip
* @return void
*/
public function add(Trip $trip);
}
… and interfaces for Validator
and Event Dispatcher
<?php
namespace LeoproTripPlannerApplicationContract;
interface Validator
{
/**
* @param $value
* @return LeoproTripPlannerDomainContractCollection
*/
public function validate($value);
}
interface EventDispatcher
{
/**
* @param array $listeners
* @return EventListener[]
*/
public function registerListeners(array $listeners);
/**
* @param $event
*/
public function notify($name, $event);
}
About validation
In DDD, entities should be always
valid.
About validation
But if you ask
“where do I put validation?”
you'll get different answers.
About validation
If you are using commands,
validate the command itself, is a
good trade-off.
Infrastructure
Framework's revenge
composer.json
"require": {
"php": ">=5.3.3",
"doctrine/collections": "v1.2",
"symfony/symfony": "~2.4",
"doctrine/dbal": "dev-master",
"doctrine/orm": "dev-master",
"doctrine/doctrine-bundle": "dev-master",
"twig/extensions": "~1.0",
"symfony/assetic-bundle": "~2.3",
"symfony/swiftmailer-bundle": "~2.3",
"symfony/monolog-bundle": "~2.4",
"sensio/distribution-bundle": "~2.3",
"sensio/framework-extra-bundle": "~3.0",
"sensio/generator-bundle": "~2.3",
"incenteev/composer-parameter-handler": "~2.0",
"doctrine/data-fixtures": "dev-master",
"doctrine/migrations": "dev-master",
"doctrine/doctrine-migrations-bundle": "dev-master",
"doctrine/doctrine-fixtures-bundle": "dev-master"
},
Mapping entities
app/config/config.yml
orm:
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: false
mappings:
TripPlannerDomain:
type: yml
prefix: LeoproTripPlannerDomainEntity
dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/entity
is_bundle: false
TripPlannerDomainValueObjects:
type: yml
prefix: LeoproTripPlannerDomainValueObject
dir: %kernel.root_dir%/../src/Leopro/TripPlanner/InfrastructureBundle/Resources/config/doctrine/value_object
is_bundle: false
InfrastructureBundle/Resources/config/entity/Route.orm.yml
LeoproTripPlannerDomainEntityTrip:
type: entity
table: trip
embedded:
identity:
class: LeoproTripPlannerDomainValueObjectTripIdentity
fields:
name:
type: string
length: 250
manyToMany:
routes:
targetEntity: LeoproTripPlannerDomainEntityRoute
joinTable:
name: trip_routes
joinColumns:
link_id:
referencedColumnName: identity_id
inverseJoinColumns:
report_id:
referencedColumnName: internalIdentity
cascade: ["persist"]
Validate commands
InfrastructureBundle/Resources/config/validation.yml
LeoproTripPlannerApplicationCommandCreateTripCommand:
properties:
name:
- NotBlank: ~
LeoproTripPlannerApplicationCommandAddLegToRouteCommand:
properties:
tripIdentity:
- NotBlank: ~
routeIdentity:
- NotBlank: ~
date:
- NotBlank: ~
dateFormat:
- NotBlank: ~
latitude:
- NotBlank: ~
longitude:
- NotBlank: ~
Configuring services
src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml
<services>
<!-- Exposed Services -->
<service id="trip_repository" alias="trip_repository.doctrine"></service>
<service id="command_handler" class="%application.command_handler.class%">
<argument type="service" id="infrastructure.validator"/>
<argument type="service" id="application.event_dispatcher"/>
</service>
</services>
src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml
<services>
<!-- Not Exposed Services -->
<service id="application.event_dispatcher" public="false" class="%application.event_dispatcher.class%">
</service>
<service id="use_case.create_trip" public="false" class="...CreateTripUseCase">
<argument type="service" id="trip_repository"/>
<tag name="use_case"/>
</service>
<service id="use_case.add_leg_to_route" public="false"
class="LeoproTripPlannerApplicationUseCaseAddLegToRouteUseCase">
<argument type="service" id="trip_repository"/>
<tag name="use_case"/>
</service>
<service id="use_case.update_location" public="false"
class="LeoproTripPlannerApplicationUseCaseUpdateLocationUseCase">
<argument type="service" id="trip_repository"/>
<tag name="use_case"/>
</service>
</services>
src/LeoPro/TripPlanner/InfrastructureBundle/Resources/config/services.xml
<services>
<!-- Adapter -->
<service id="infrastructure.validator" public="false" class="%infrastructure.validator.class%">
<argument type="service" id="validator"/>
</service>
<service id="infrastructure.event_dispatcher_adapter" public="false"
class="%infrastructure.event_dispatcher_adapter.class%">
<argument type="service" id="event_dispatcher"/>
<tag name="event_dispatcher_listener"/>
</service>
<!-- Concrete Implementations -->
<service id="trip_repository.doctrine" public="false" class="%infrastructure.trip_repository.doctrine.class%">
<argument type="service" id="doctrine.orm.entity_manager"/>
</service>
</services>
Adapter
Ops … some parts of the
frameworks do not fit our
interfaces.
<?php
namespace LeoproTripPlannerInfrastructureBundleAdapter;
use LeoproTripPlannerApplicationContractValidator as ApplicationValidatorInterface;
use LeoproTripPlannerDomainAdapterArrayCollection;
use SymfonyComponentValidatorValidatorValidatorInterface;
class Validator implements ApplicationValidatorInterface
{
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function validate($value)
{
$applicationErrors = new ArrayCollection();
$errors = $this->validator->validate($value);
foreach ($errors as $error) {
$applicationErrors->set($error->getPropertyPath(), $error->getMessage());
}
return $applicationErrors;
}
}
Repository
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use DoctrineORMEntityManager;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function get(TripIdentity $identity)
{
$qb = $this->em->createQueryBuilder()
->select('t')
->from("TripPlannerDomain:Trip", 't')
->where('t.identity.id = :identity');
$qb->setParameter('identity', $identity);
return $qb->getQuery()->getOneOrNullResult();
}
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use DoctrineORMEntityManager;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
public function add(Trip $trip)
{
$this->em->persist($trip);
$this->em->flush();
}
}
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use DoctrineORMEntityManager;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
public function add(Trip $trip)
{
$this->em->persist($trip);
$this->em->flush();
}
}
Then it’s like a Doctrine
repository?!?
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use DoctrineORMEntityManager;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
public function add(Trip $trip)
{
$this->em->persist($trip);
$this->em->flush();
}
}
No, it’s quite different
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
private $myThirdPartApiClient;
public function __construct(ApiClient $myThirdPartApiClient)
{
$this->myThirdPartApiClient = $myThirdPartApiClient;
}
public function get(TripIdentity $identity)
{
$this->myThirdPartApiClient->get($identity);
}
public function add(Trip $trip)
{
$this->myThirdPartApiClient->store($trip);
}
<?php
namespace LeoproTripPlannerInfrastructureBundleRepository;
use LeoproTripPlannerDomainContractTripRepository as TripRepositoryInterface;
class TripRepository implements TripRepositoryInterface
{
private $myThirdPartApiClient;
public function __construct(ApiClient $myThirdPartApiClient)
{
$this->myThirdPartApiClient = $myThirdPartApiClient;
}
public function get(TripIdentity $identity)
{
$this->myThirdPartApiClient->get($identity);
}
public function add(Trip $trip)
{
$this->myThirdPartApiClient->store($trip);
}
It’s another possible Repository
implementation
Presentation
<?php
namespace LeoproTripPlannerPresentationBundleController;
use LeoproTripPlannerPresentationBundleFormTypeCreateTripType;
class ApiController extends Controller
{
/**
* @Route("/", name="create_trip")
* @Template
*/
public function createTripAction(Request $request)
{
$form = $this->createForm(new CreateTripType());
$form->handleRequest($request);
if ($form->isValid()) {
$trip = $this->get('command_handler')->execute($form->getData());
return new Response('ok');
}
return array(
'form' => $form->createView(),
);
}
}
<?php
namespace LeoproTripPlannerPresentationBundleFormType;
use LeoproTripPlannerApplicationCommandCreateTripCommand;
class CreateTripType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('save', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'LeoproTripPlannerApplicationCommandCreateTripCommand',
'empty_data' => function (FormInterface $form) {
$command = new CreateTripCommand(
$form->get('name')->getData()
);
return $command;
},
));
}
}
One step to the finish line
What have I learned?
A clean architecture helps in avoiding the Big Ball of Mud.
Also starting with a very simple domain
Iteration by iteration
Complexity could grow
If our system is tightly coupled
and the domain is scattered
we are losing the chance of responding to changes.
Talking about testing ...
… independence from frameworks, database, UI ...
… means talking about business.
Let the code speak the language of the business
First, taking care of the model
Then choosing the right tool
...
We reached the finish line, well done.
Thank you :-)
Credits
● Eric Evans, - "Domain Driven Design"
● “Uncle Bob” - http://blog.8thlight.com/uncle-bob/archive.html and books
● http://williamdurand.fr/2013/08/20/ddd-with-symfony2-making-things-clear/
● http://williamdurand.fr/2013/08/07/ddd-with-symfony2-folder-structure-and-
code-first/
● http://verraes.net/2013/04/decoupling-symfony2-forms-from-entities/
● http://www.whitewashing.
de/2012/08/22/building_an_object_model__no_setters_allowed.html
● http://nicolopignatelli.me/valueobjects-a-php-immutable-class-library/
● http://welcometothebundle.com/domain-driven-design-and-symfony-for-
simple-app/
● http://www.slideshare.net/SteveRhoades2/implementing-ddd-concepts-in-
php
Credits
● http://devlicio.us/blogs/casey/archive/2009/02/16/ddd-aggregates-and-
aggregate-roots.aspx
● http://www.slideshare.net/perprogramming/application-layer-33335917
● http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-
design/
● http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-
ddd/
● http://www.slideshare.net/jeppec/agile-ddd-cqrs
● http://www.slideshare.net/thinkddd/practical-domain-driven-design-cqrs-
and-messaging-architectures
● http://www.codeproject.com/Articles/339725/Domain-Driven-Design-Clear-
Your-Concepts-Before-Yo
● http://lostechies.com/jimmybogard/2008/05/21/entities-value-objects-
aggregates-and-roots/
Credits
● http://www.slideshare.net/ziobrando/gestire-la-complessit-con-domain-
driven-design
● http://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/
● http://lostechies.com/jimmybogard/2009/09/03/ddd-repository-
implementation-patterns/
● http://www.sapiensworks.com/blog/post/2012/04/18/DDD-Aggregates-
And-Aggregates-Root-Explained.aspx
● http://www.udidahan.com/2009/06/29/dont-create-aggregate-roots/
● http://jblewitt.com/blog/?p=241
● http://www.sapiensworks.com/blog/post/2013/10/18/Modelling-Aggregate-
Roots-Relationships.aspx
● http://www.sapiensworks.com/blog/post/2013/01/15/Domain-Driven-
Design-Aggregate-Root-Modelling-Fallacy.aspx
Credits
● http://lostechies.com/jamesgregory/2009/05/09/entity-interface-anti-pattern
● http://www.slideshare.net/piotrpelczar/cqrs-28299581
● http://richarddingwall.name/2009/10/13/life-inside-an-aggregate-root-part-
1/
● http://lostechies.com/jimmybogard/2010/02/24/strengthening-your-domain-
aggregate-construction/
● http://guptavikas.wordpress.com/2009/12/21/domain-driven-design-
creating-domain-objects/
● http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-
ddd/
● http://verraes.net/2013/12/related-entities-vs-child-entities/
● http://devlicio.us/blogs/casey/archive/2009/02/20/ddd-the-repository-
pattern.aspx
● http://gojko.net/2009/09/30/ddd-and-relational-databases-the-value-object-
dilemma/

More Related Content

What's hot

Clean architecture
Clean architectureClean architecture
Clean architecture
andbed
 
A Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidA Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on Android
Outware Mobile
 

What's hot (20)

Clean architecture with asp.net core
Clean architecture with asp.net coreClean architecture with asp.net core
Clean architecture with asp.net core
 
Introducing Clean Architecture
Introducing Clean ArchitectureIntroducing Clean Architecture
Introducing Clean Architecture
 
Domain Driven Design(DDD) Presentation
Domain Driven Design(DDD) PresentationDomain Driven Design(DDD) Presentation
Domain Driven Design(DDD) Presentation
 
Clean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software CraftsmanshipClean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software Craftsmanship
 
Refactoring for Domain Driven Design
Refactoring for Domain Driven DesignRefactoring for Domain Driven Design
Refactoring for Domain Driven Design
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
A Practical Guide to Domain Driven Design: Presentation Slides
A Practical Guide to Domain Driven Design: Presentation SlidesA Practical Guide to Domain Driven Design: Presentation Slides
A Practical Guide to Domain Driven Design: Presentation Slides
 
Real Life Clean Architecture
Real Life Clean ArchitectureReal Life Clean Architecture
Real Life Clean Architecture
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
Implementing DDD with C#
Implementing DDD with C#Implementing DDD with C#
Implementing DDD with C#
 
Hexagonal architecture with Spring Boot
Hexagonal architecture with Spring BootHexagonal architecture with Spring Boot
Hexagonal architecture with Spring Boot
 
CQRS: Command/Query Responsibility Segregation
CQRS: Command/Query Responsibility SegregationCQRS: Command/Query Responsibility Segregation
CQRS: Command/Query Responsibility Segregation
 
Hexagonal architecture for java applications
Hexagonal architecture for java applicationsHexagonal architecture for java applications
Hexagonal architecture for java applications
 
Introducing Domain Driven Design - codemash
Introducing Domain Driven Design - codemashIntroducing Domain Driven Design - codemash
Introducing Domain Driven Design - codemash
 
Domain Driven Design: Zero to Hero
Domain Driven Design: Zero to HeroDomain Driven Design: Zero to Hero
Domain Driven Design: Zero to Hero
 
Hexagonal architecture in PHP
Hexagonal architecture in PHPHexagonal architecture in PHP
Hexagonal architecture in PHP
 
Domain Driven Design Quickly
Domain Driven Design QuicklyDomain Driven Design Quickly
Domain Driven Design Quickly
 
A Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidA Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on Android
 
Domain Driven Design Introduction
Domain Driven Design IntroductionDomain Driven Design Introduction
Domain Driven Design Introduction
 
CQRS and Event Sourcing
CQRS and Event Sourcing CQRS and Event Sourcing
CQRS and Event Sourcing
 

Similar to Clean architecture with ddd layering in php

Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014 Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014
John Willis
 
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Karen Thompson
 
Domain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron EdwardsDomain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron Edwards
Hakka Labs
 
Android application development
Android application developmentAndroid application development
Android application development
Linh Vi Tường
 
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
ICSM 2010
 

Similar to Clean architecture with ddd layering in php (20)

Microservices Architecture
Microservices ArchitectureMicroservices Architecture
Microservices Architecture
 
Let's talk about... Microservices
Let's talk about... MicroservicesLet's talk about... Microservices
Let's talk about... Microservices
 
The challenge of application distribution - Introduction to Docker (2014 dec ...
The challenge of application distribution - Introduction to Docker (2014 dec ...The challenge of application distribution - Introduction to Docker (2014 dec ...
The challenge of application distribution - Introduction to Docker (2014 dec ...
 
"The Intersection of architecture and implementation", Mark Richards
"The Intersection of architecture and implementation", Mark Richards"The Intersection of architecture and implementation", Mark Richards
"The Intersection of architecture and implementation", Mark Richards
 
Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014 Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014
 
Android application development for TresmaxAsia
Android application development for TresmaxAsiaAndroid application development for TresmaxAsia
Android application development for TresmaxAsia
 
Angular mobile angular_u
Angular mobile angular_uAngular mobile angular_u
Angular mobile angular_u
 
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
 
Domain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron EdwardsDomain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron Edwards
 
Android Workshop_1
Android Workshop_1Android Workshop_1
Android Workshop_1
 
Microservices: How loose is loosely coupled?
Microservices: How loose is loosely coupled?Microservices: How loose is loosely coupled?
Microservices: How loose is loosely coupled?
 
Introduction to Android Development.pptx
Introduction to Android Development.pptxIntroduction to Android Development.pptx
Introduction to Android Development.pptx
 
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
 
StackEngine Problem Space Demo
StackEngine Problem Space DemoStackEngine Problem Space Demo
StackEngine Problem Space Demo
 
Go Best Practices – Interfaces, Packages and APIs
Go Best Practices – Interfaces, Packages and APIsGo Best Practices – Interfaces, Packages and APIs
Go Best Practices – Interfaces, Packages and APIs
 
ASAS 2014 - Simon Brown
ASAS 2014 - Simon BrownASAS 2014 - Simon Brown
ASAS 2014 - Simon Brown
 
2.28.17 Introducing DSpace 7 Webinar Slides
2.28.17 Introducing DSpace 7 Webinar Slides2.28.17 Introducing DSpace 7 Webinar Slides
2.28.17 Introducing DSpace 7 Webinar Slides
 
Android application development
Android application developmentAndroid application development
Android application development
 
Up to speed in domain driven design
Up to speed in domain driven designUp to speed in domain driven design
Up to speed in domain driven design
 
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
2D and 3D Visualizations In Wikidev2.0 M. Fokaefs, D. Serrano, B. Tansey and ...
 

Recently uploaded

➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men 🔝mehsana🔝 Escorts...
➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men  🔝mehsana🔝   Escorts...➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men  🔝mehsana🔝   Escorts...
➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men 🔝mehsana🔝 Escorts...
nirzagarg
 
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRLLucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
imonikaupta
 
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
@Chandigarh #call #Girls 9053900678 @Call #Girls in @Punjab 9053900678
 
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
nirzagarg
 
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
@Chandigarh #call #Girls 9053900678 @Call #Girls in @Punjab 9053900678
 

Recently uploaded (20)

(+971568250507 ))# Young Call Girls in Ajman By Pakistani Call Girls in ...
(+971568250507  ))#  Young Call Girls  in Ajman  By Pakistani Call Girls  in ...(+971568250507  ))#  Young Call Girls  in Ajman  By Pakistani Call Girls  in ...
(+971568250507 ))# Young Call Girls in Ajman By Pakistani Call Girls in ...
 
➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men 🔝mehsana🔝 Escorts...
➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men  🔝mehsana🔝   Escorts...➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men  🔝mehsana🔝   Escorts...
➥🔝 7737669865 🔝▻ mehsana Call-girls in Women Seeking Men 🔝mehsana🔝 Escorts...
 
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRLLucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
 
Busty Desi⚡Call Girls in Vasundhara Ghaziabad >༒8448380779 Escort Service
Busty Desi⚡Call Girls in Vasundhara Ghaziabad >༒8448380779 Escort ServiceBusty Desi⚡Call Girls in Vasundhara Ghaziabad >༒8448380779 Escort Service
Busty Desi⚡Call Girls in Vasundhara Ghaziabad >༒8448380779 Escort Service
 
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
 
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
 
Pirangut | Call Girls Pune Phone No 8005736733 Elite Escort Service Available...
Pirangut | Call Girls Pune Phone No 8005736733 Elite Escort Service Available...Pirangut | Call Girls Pune Phone No 8005736733 Elite Escort Service Available...
Pirangut | Call Girls Pune Phone No 8005736733 Elite Escort Service Available...
 
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
20240509 QFM015 Engineering Leadership Reading List April 2024.pdf
 
VVIP Pune Call Girls Sinhagad WhatSapp Number 8005736733 With Elite Staff And...
VVIP Pune Call Girls Sinhagad WhatSapp Number 8005736733 With Elite Staff And...VVIP Pune Call Girls Sinhagad WhatSapp Number 8005736733 With Elite Staff And...
VVIP Pune Call Girls Sinhagad WhatSapp Number 8005736733 With Elite Staff And...
 
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
💚😋 Bilaspur Escort Service Call Girls, 9352852248 ₹5000 To 25K With AC💚😋
 
Real Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirtReal Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirt
 
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
 
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
📱Dehradun Call Girls Service 📱☎️ +91'905,3900,678 ☎️📱 Call Girls In Dehradun 📱
 
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency""Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
 
APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53APNIC Updates presented by Paul Wilson at ARIN 53
APNIC Updates presented by Paul Wilson at ARIN 53
 
Russian Call Girls in %(+971524965298 )# Call Girls in Dubai
Russian Call Girls in %(+971524965298  )#  Call Girls in DubaiRussian Call Girls in %(+971524965298  )#  Call Girls in Dubai
Russian Call Girls in %(+971524965298 )# Call Girls in Dubai
 
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
20240510 QFM016 Irresponsible AI Reading List April 2024.pdf
 
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
 
Ganeshkhind ! Call Girls Pune - 450+ Call Girl Cash Payment 8005736733 Neha T...
Ganeshkhind ! Call Girls Pune - 450+ Call Girl Cash Payment 8005736733 Neha T...Ganeshkhind ! Call Girls Pune - 450+ Call Girl Cash Payment 8005736733 Neha T...
Ganeshkhind ! Call Girls Pune - 450+ Call Girl Cash Payment 8005736733 Neha T...
 

Clean architecture with ddd layering in php