SlideShare uma empresa Scribd logo
1 de 177
Baixar para ler 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/

Mais conteúdo relacionado

Mais procurados

From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvCodelyTV
 
Clean architecture
Clean architectureClean architecture
Clean architectureLieven Doclo
 
Clean architecture
Clean architectureClean architecture
Clean architectureandbed
 
Spring JDBCTemplate
Spring JDBCTemplateSpring JDBCTemplate
Spring JDBCTemplateGuo Albert
 
Clean Code I - Best Practices
Clean Code I - Best PracticesClean Code I - Best Practices
Clean Code I - Best PracticesTheo Jungeblut
 
Cours design pattern m youssfi partie 1 introduction et pattern strategy
Cours design pattern m youssfi partie 1 introduction et pattern strategyCours design pattern m youssfi partie 1 introduction et pattern strategy
Cours design pattern m youssfi partie 1 introduction et pattern strategyENSET, Université Hassan II Casablanca
 
Java database connectivity with MySql
Java database connectivity with MySqlJava database connectivity with MySql
Java database connectivity with MySqlDhyey Dattani
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring bootAntoine Rey
 
Spring boot
Spring bootSpring boot
Spring bootsdeeg
 
Presentation JEE et son écossystéme
Presentation JEE et son écossystémePresentation JEE et son écossystéme
Presentation JEE et son écossystémeAlgeria JUG
 
Spring Boot in Action
Spring Boot in Action Spring Boot in Action
Spring Boot in Action Alex Movila
 

Mais procurados (20)

Spring Security 5
Spring Security 5Spring Security 5
Spring Security 5
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
 
Support JEE Spring Inversion de Controle IOC et Spring MVC
Support JEE Spring Inversion de Controle IOC et Spring MVCSupport JEE Spring Inversion de Controle IOC et Spring MVC
Support JEE Spring Inversion de Controle IOC et Spring MVC
 
Spring JDBCTemplate
Spring JDBCTemplateSpring JDBCTemplate
Spring JDBCTemplate
 
Spring Core
Spring CoreSpring Core
Spring Core
 
Clean Code I - Best Practices
Clean Code I - Best PracticesClean Code I - Best Practices
Clean Code I - Best Practices
 
SOLID Principles
SOLID PrinciplesSOLID Principles
SOLID Principles
 
Introduction to spring boot
Introduction to spring bootIntroduction to spring boot
Introduction to spring boot
 
Introduction à JPA (Java Persistence API )
Introduction à JPA  (Java Persistence API )Introduction à JPA  (Java Persistence API )
Introduction à JPA (Java Persistence API )
 
Cours design pattern m youssfi partie 1 introduction et pattern strategy
Cours design pattern m youssfi partie 1 introduction et pattern strategyCours design pattern m youssfi partie 1 introduction et pattern strategy
Cours design pattern m youssfi partie 1 introduction et pattern strategy
 
Spring Boot
Spring BootSpring Boot
Spring Boot
 
Java database connectivity with MySql
Java database connectivity with MySqlJava database connectivity with MySql
Java database connectivity with MySql
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring boot
 
Spring boot
Spring bootSpring boot
Spring boot
 
Presentation JEE et son écossystéme
Presentation JEE et son écossystémePresentation JEE et son écossystéme
Presentation JEE et son écossystéme
 
Hibernate
HibernateHibernate
Hibernate
 
Spring Boot in Action
Spring Boot in Action Spring Boot in Action
Spring Boot in Action
 

Semelhante a 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
 

Semelhante a 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
 

Último

Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
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 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
 
On Starlink, presented by Geoff Huston at NZNOG 2024
On Starlink, presented by Geoff Huston at NZNOG 2024On Starlink, presented by Geoff Huston at NZNOG 2024
On Starlink, presented by Geoff Huston at NZNOG 2024APNIC
 
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls KolkataVIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...Diya Sharma
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxellan12
 
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$kojalkojal131
 
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024APNIC
 
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
 
VIP Kolkata Call Girl Salt Lake 👉 8250192130 Available With Room
VIP Kolkata Call Girl Salt Lake 👉 8250192130  Available With RoomVIP Kolkata Call Girl Salt Lake 👉 8250192130  Available With Room
VIP Kolkata Call Girl Salt Lake 👉 8250192130 Available With Roomishabajaj13
 
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
 
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
 
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...aditipandeya
 
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With RoomVIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Roomgirls4nights
 

Último (20)

Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
 
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 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🔝
 
On Starlink, presented by Geoff Huston at NZNOG 2024
On Starlink, presented by Geoff Huston at NZNOG 2024On Starlink, presented by Geoff Huston at NZNOG 2024
On Starlink, presented by Geoff Huston at NZNOG 2024
 
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls KolkataVIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Call Girls In South Ex 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In South Ex 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICECall Girls In South Ex 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In South Ex 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
 
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...
₹5.5k {Cash Payment}New Friends Colony Call Girls In [Delhi NIHARIKA] 🔝|97111...
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
 
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$
Call Girls Dubai Prolapsed O525547819 Call Girls In Dubai Princes$
 
Model Call Girl in Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in  Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝Model Call Girl in  Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
 
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
 
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
 
VIP Kolkata Call Girl Salt Lake 👉 8250192130 Available With Room
VIP Kolkata Call Girl Salt Lake 👉 8250192130  Available With RoomVIP Kolkata Call Girl Salt Lake 👉 8250192130  Available With Room
VIP Kolkata Call Girl Salt Lake 👉 8250192130 Available With Room
 
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
Dwarka Sector 26 Call Girls | Delhi | 9999965857 🫦 Vanshika Verma More Our Se...
 
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
 
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
 
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
 
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
 
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With RoomVIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
 

Clean architecture with ddd layering in php