SlideShare une entreprise Scribd logo
1  sur  177
Télécharger pour lire hors ligne
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/

Contenu connexe

Tendances

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

Tendances (20)

Real Life Clean Architecture
Real Life Clean ArchitectureReal Life Clean Architecture
Real Life Clean Architecture
 
Hexagonal architecture in PHP
Hexagonal architecture in PHPHexagonal architecture in PHP
Hexagonal architecture in PHP
 
Applying Domain-Driven Design to craft Rich Domain Models
Applying Domain-Driven Design to craft Rich Domain ModelsApplying Domain-Driven Design to craft Rich Domain Models
Applying Domain-Driven Design to craft Rich Domain Models
 
Introducing Clean Architecture
Introducing Clean ArchitectureIntroducing Clean Architecture
Introducing Clean Architecture
 
Domain-Driven Design
Domain-Driven DesignDomain-Driven Design
Domain-Driven Design
 
Modeling microservices using DDD
Modeling microservices using DDDModeling microservices using DDD
Modeling microservices using DDD
 
Implementing DDD with C#
Implementing DDD with C#Implementing DDD with C#
Implementing DDD with C#
 
Solid principles
Solid principlesSolid principles
Solid principles
 
Clean architecture
Clean architectureClean architecture
Clean architecture
 
Spring Core
Spring CoreSpring Core
Spring Core
 
Solid Principles
Solid PrinciplesSolid Principles
Solid Principles
 
Microservice vs. Monolithic Architecture
Microservice vs. Monolithic ArchitectureMicroservice vs. Monolithic Architecture
Microservice vs. Monolithic Architecture
 
Terraform -- Infrastructure as Code
Terraform -- Infrastructure as CodeTerraform -- Infrastructure as Code
Terraform -- Infrastructure as Code
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architecture
 
A Practical Guide to Domain Driven Design: Presentation Slides
A Practical Guide to Domain Driven Design: Presentation SlidesA Practical Guide to Domain Driven Design: Presentation Slides
A Practical Guide to Domain Driven Design: Presentation Slides
 
A Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidA Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on Android
 
Domain Driven Design Demonstrated
Domain Driven Design Demonstrated Domain Driven Design Demonstrated
Domain Driven Design Demonstrated
 
Terraform
TerraformTerraform
Terraform
 
An Introduction To Jenkins
An Introduction To JenkinsAn Introduction To Jenkins
An Introduction To Jenkins
 
Solid NodeJS with TypeScript, Jest & NestJS
Solid NodeJS with TypeScript, Jest & NestJSSolid NodeJS with TypeScript, Jest & NestJS
Solid NodeJS with TypeScript, Jest & NestJS
 

Similaire à Clean architecture with ddd layering in php

Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014 Devopsdays State of the Union Amsterdam 2014
Devopsdays State of the Union Amsterdam 2014
John Willis
 
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Cis 555 Week 4 Assignment 2 Automated Teller Machine (Atm)...
Karen Thompson
 
Domain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron EdwardsDomain Driven Design in the Browser - Cameron Edwards
Domain Driven Design in the Browser - Cameron Edwards
Hakka Labs
 
Android application development
Android application developmentAndroid application development
Android application development
Linh Vi Tường
 

Similaire à 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
 

Dernier

VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
dharasingh5698
 
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
@Chandigarh #call #Girls 9053900678 @Call #Girls in @Punjab 9053900678
 
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
Chandigarh Call girls 9053900678 Call girls in Chandigarh
 
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
Call Girls In Delhi Whatsup 9873940964 Enjoy Unlimited Pleasure
 
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRLLucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
Lucknow ❤CALL GIRL 88759*99948 ❤CALL GIRLS IN Lucknow ESCORT SERVICE❤CALL GIRL
imonikaupta
 
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
nilamkumrai
 

Dernier (20)

"Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency""Boost Your Digital Presence: Partner with a Leading SEO Agency"
"Boost Your Digital Presence: Partner with a Leading SEO Agency"
 
VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Himatnagar 7001035870 Whatsapp Number, 24/07 Booking
 
VIP Model Call Girls Hadapsar ( Pune ) Call ON 9905417584 Starting High Prof...
VIP Model Call Girls Hadapsar ( Pune ) Call ON 9905417584 Starting  High Prof...VIP Model Call Girls Hadapsar ( Pune ) Call ON 9905417584 Starting  High Prof...
VIP Model Call Girls Hadapsar ( Pune ) Call ON 9905417584 Starting High Prof...
 
VIP Model Call Girls NIBM ( Pune ) Call ON 8005736733 Starting From 5K to 25K...
VIP Model Call Girls NIBM ( Pune ) Call ON 8005736733 Starting From 5K to 25K...VIP Model Call Girls NIBM ( Pune ) Call ON 8005736733 Starting From 5K to 25K...
VIP Model Call Girls NIBM ( Pune ) Call ON 8005736733 Starting From 5K to 25K...
 
Hire↠Young Call Girls in Tilak nagar (Delhi) ☎️ 9205541914 ☎️ Independent Esc...
Hire↠Young Call Girls in Tilak nagar (Delhi) ☎️ 9205541914 ☎️ Independent Esc...Hire↠Young Call Girls in Tilak nagar (Delhi) ☎️ 9205541914 ☎️ Independent Esc...
Hire↠Young Call Girls in Tilak nagar (Delhi) ☎️ 9205541914 ☎️ Independent Esc...
 
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
2nd Solid Symposium: Solid Pods vs Personal Knowledge Graphs
 
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
6.High Profile Call Girls In Punjab +919053900678 Punjab Call GirlHigh Profil...
 
Real Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirtReal Men Wear Diapers T Shirts sweatshirt
Real Men Wear Diapers T Shirts sweatshirt
 
Pune Airport ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready...
Pune Airport ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready...Pune Airport ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready...
Pune Airport ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready...
 
Dubai=Desi Dubai Call Girls O525547819 Outdoor Call Girls Dubai
Dubai=Desi Dubai Call Girls O525547819 Outdoor Call Girls DubaiDubai=Desi Dubai Call Girls O525547819 Outdoor Call Girls Dubai
Dubai=Desi Dubai Call Girls O525547819 Outdoor Call Girls Dubai
 
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
Call Girls Sangvi Call Me 7737669865 Budget Friendly No Advance BookingCall G...
 
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
APNIC Policy Roundup, presented by Sunny Chendi at the 5th ICANN APAC-TWNIC E...
 
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
best call girls in Hyderabad Finest Escorts Service 📞 9352988975 📞 Available ...
 
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Prashant Vihar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
Low Sexy Call Girls In Mohali 9053900678 🥵Have Save And Good Place 🥵
 
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
Thalassery Escorts Service ☎️ 6378878445 ( Sakshi Sinha ) High Profile Call G...
 
Trump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts SweatshirtTrump Diapers Over Dems t shirts Sweatshirt
Trump Diapers Over Dems t shirts Sweatshirt
 
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrStory Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Story Board.pptxrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
 
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
 
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
( Pune ) VIP Baner Call Girls 🎗️ 9352988975 Sizzling | Escorts | Girls Are Re...
 

Clean architecture with ddd layering in php