SlideShare ist ein Scribd-Unternehmen logo
1 von 177
Downloaden Sie, um offline zu lesen
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/

Weitere ähnliche Inhalte

Was ist angesagt?

Spring Boot Interview Questions | Edureka
Spring Boot Interview Questions | EdurekaSpring Boot Interview Questions | Edureka
Spring Boot Interview Questions | EdurekaEdureka!
 
DDD on example of Symfony (SfCampUA14)
DDD on example of Symfony (SfCampUA14)DDD on example of Symfony (SfCampUA14)
DDD on example of Symfony (SfCampUA14)Oleg Zinchenko
 
Enterprise Software Architecture styles
Enterprise Software Architecture stylesEnterprise Software Architecture styles
Enterprise Software Architecture stylesAraf Karsh Hamid
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven DesignRyan Riley
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationSamuel ROZE
 
Clean Architecture
Clean ArchitectureClean Architecture
Clean ArchitectureBadoo
 
Clean architecture
Clean architectureClean architecture
Clean architecture.NET Crowd
 
Solid principles
Solid principlesSolid principles
Solid principlesToan Nguyen
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Edureka!
 
Solid design principles
Solid design principlesSolid design principles
Solid design principlesMahmoud Asadi
 
Jpa 잘 (하는 척) 하기
Jpa 잘 (하는 척) 하기Jpa 잘 (하는 척) 하기
Jpa 잘 (하는 척) 하기경원 이
 
Domain Driven Design: Zero to Hero
Domain Driven Design: Zero to HeroDomain Driven Design: Zero to Hero
Domain Driven Design: Zero to HeroFabrício Rissetto
 
SOLID Design Principles
SOLID Design PrinciplesSOLID Design Principles
SOLID Design PrinciplesSamuel Breed
 

Was ist angesagt? (20)

Spring Boot Interview Questions | Edureka
Spring Boot Interview Questions | EdurekaSpring Boot Interview Questions | Edureka
Spring Boot Interview Questions | Edureka
 
DDD on example of Symfony (SfCampUA14)
DDD on example of Symfony (SfCampUA14)DDD on example of Symfony (SfCampUA14)
DDD on example of Symfony (SfCampUA14)
 
Enterprise Software Architecture styles
Enterprise Software Architecture stylesEnterprise Software Architecture styles
Enterprise Software Architecture styles
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
 
Clean Architecture
Clean ArchitectureClean Architecture
Clean Architecture
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
Domain Driven Design 101
Domain Driven Design 101Domain Driven Design 101
Domain Driven Design 101
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Solid principles
Solid principlesSolid principles
Solid principles
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
 
Solid design principles
Solid design principlesSolid design principles
Solid design principles
 
Laravel ppt
Laravel pptLaravel ppt
Laravel ppt
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Jpa 잘 (하는 척) 하기
Jpa 잘 (하는 척) 하기Jpa 잘 (하는 척) 하기
Jpa 잘 (하는 척) 하기
 
Domain Driven Design: Zero to Hero
Domain Driven Design: Zero to HeroDomain Driven Design: Zero to Hero
Domain Driven Design: Zero to Hero
 
Angular
AngularAngular
Angular
 
Express js
Express jsExpress js
Express js
 
Clean Architecture
Clean ArchitectureClean Architecture
Clean Architecture
 
SOLID Design Principles
SOLID Design PrinciplesSOLID Design Principles
SOLID Design Principles
 

Ähnlich wie Clean architecture with ddd layering in php

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 ...Sébastien Portebois
 
"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 RichardsFwdays
 
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
 
Android application development for TresmaxAsia
Android application development for TresmaxAsiaAndroid application development for TresmaxAsia
Android application development for TresmaxAsiaMichael Angelo Rivera
 
Angular mobile angular_u
Angular mobile angular_uAngular mobile angular_u
Angular mobile angular_uDoris Chen
 
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 EdwardsHakka Labs
 
Android Workshop_1
Android Workshop_1Android Workshop_1
Android Workshop_1Purvik Rana
 
Microservices: How loose is loosely coupled?
Microservices: How loose is loosely coupled?Microservices: How loose is loosely coupled?
Microservices: How loose is loosely coupled?John Rofrano
 
Introduction to Android Development.pptx
Introduction to Android Development.pptxIntroduction to Android Development.pptx
Introduction to Android Development.pptxasmeerana605
 
Hexagonal architecture - message-oriented software design
Hexagonal architecture  - message-oriented software designHexagonal architecture  - message-oriented software design
Hexagonal architecture - message-oriented software designMatthias Noback
 
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 2013DuckMa
 
StackEngine Problem Space Demo
StackEngine Problem Space DemoStackEngine Problem Space Demo
StackEngine Problem Space DemoBoyd Hemphill
 
Domain driven design and model driven development
Domain driven design and model driven developmentDomain driven design and model driven development
Domain driven design and model driven developmentDmitry Geyzersky
 
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 APIsMarcus Kohlberg
 
ASAS 2014 - Simon Brown
ASAS 2014 - Simon BrownASAS 2014 - Simon Brown
ASAS 2014 - Simon BrownAvisi B.V.
 
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 SlidesDuraSpace
 
Android application development
Android application developmentAndroid application development
Android application developmentLinh Vi Tường
 

Ähnlich wie 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
 
Hexagonal architecture - message-oriented software design
Hexagonal architecture  - message-oriented software designHexagonal architecture  - message-oriented software design
Hexagonal architecture - message-oriented software design
 
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
 
Domain driven design and model driven development
Domain driven design and model driven developmentDomain driven design and model driven development
Domain driven design and model driven development
 
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
 

Kürzlich hochgeladen

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 GIRLimonikaupta
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Servicesexy call girls service in goa
 
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call GirlVIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girladitipandeya
 
𓀤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...Neha Pandey
 
Russian Call girls in Dubai +971563133746 Dubai Call girls
Russian  Call girls in Dubai +971563133746 Dubai  Call girlsRussian  Call girls in Dubai +971563133746 Dubai  Call girls
Russian Call girls in Dubai +971563133746 Dubai Call girlsstephieert
 
AlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsAlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsThierry TROUIN ☁
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...SofiyaSharma5
 
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...Call Girls in Nagpur High Profile
 
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night StandHot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Standkumarajju5765
 
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.soniya singh
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGAPNIC
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
SEO Growth Program-Digital optimization Specialist
SEO Growth Program-Digital optimization SpecialistSEO Growth Program-Digital optimization Specialist
SEO Growth Program-Digital optimization SpecialistKHM Anwar
 
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort ServiceEnjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort ServiceDelhi Call girls
 
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779Best VIP Call Girls Noida Sector 75 Call Me: 8448380779
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779Delhi Call girls
 
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girlsstephieert
 
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts servicesonalikaur4
 

Kürzlich hochgeladen (20)

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
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
 
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call GirlVIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
 
𓀤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...
 
Russian Call girls in Dubai +971563133746 Dubai Call girls
Russian  Call girls in Dubai +971563133746 Dubai  Call girlsRussian  Call girls in Dubai +971563133746 Dubai  Call girls
Russian Call girls in Dubai +971563133746 Dubai Call girls
 
AlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsAlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with Flows
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
 
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No AdvanceRohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
 
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...Top Rated  Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
Top Rated Pune Call Girls Daund ⟟ 6297143586 ⟟ Call Me For Genuine Sex Servi...
 
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night StandHot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
 
Rohini Sector 22 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 22 Call Girls Delhi 9999965857 @Sabina Saikh No AdvanceRohini Sector 22 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 22 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
 
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.
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOG
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
 
SEO Growth Program-Digital optimization Specialist
SEO Growth Program-Digital optimization SpecialistSEO Growth Program-Digital optimization Specialist
SEO Growth Program-Digital optimization Specialist
 
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort ServiceEnjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
Enjoy Night⚡Call Girls Dlf City Phase 3 Gurgaon >༒8448380779 Escort Service
 
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779Best VIP Call Girls Noida Sector 75 Call Me: 8448380779
Best VIP Call Girls Noida Sector 75 Call Me: 8448380779
 
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Saket Delhi 💯Call Us 🔝8264348440🔝
 
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
 
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
 

Clean architecture with ddd layering in php