Upload
pascal-larocque
View
947
Download
3
Tags:
Embed Size (px)
DESCRIPTION
Software is complicated, trying to represent the business logic in code requires allot of communication between the programmers and the domain experts. Domain Driven Design provided methods to facilitate this process
Citation preview
DOMAIN DRIVEN DESIGN
TACKLING COMPLEXITY
@pascallarocque
○ TRUSTCHARGE TEAM
○ BEHAT GUY
○ TDD GUY
○ SOLID GUY
○ PATTERN GUY
○ FATHER OF 3
○ STAR WARS GEEK
SOFTWARE IS COMPLICATED
CURRENT ARCHITECTURE
CONTROLLER
ORM
DATABASE
APPLICATION
DATA ACCESS /
BUSINESS OBJECT/
PERSISTENCE
DATA STORE
BZ
● BUSINESS LOGIC IN
CONTROLLER AND IN DATA
ACCESS OBJECTS
● FRAMEWORK COUPLED TO
CONTROLLER
● DIRECT ACCESS TO DATA
OBJECT FROM CONTROLLER
PROBLEM
○ DEVELOPERS / ARCHITECTS ARE ONLY THINKING ABOUT THE
FRAMEWORK (DB, ORM, CACHING)
○ MOST OF OUR DEVELOPMENT TIME IS SPENT WRITING PLUMPING
FOR THE FRAMWORK INSTEAD OF REAL BUSINESS LOGIC
○ THE MEANING OF OOP IS LOST
WHAT
○ DOMAIN DRIVEN DESIGN IS ABOUT MAPPING BUSINESS DOMAIN
CONCEPT INTO CODE
WHY
○ TO CREATE SOFTWARE THAT REFLECT THE BUSINESS RATHER
THAN THE FRAMEWORK
DOMAIN DRIVEN DESIGN
DOMAIN DRIVEN ARCHITECTURE
CONTROLLER
SERVICE
DOMAIN
DAO
DATABASE
FRAMEWORK
APPLICATION
DOMAIN
DATA ACCESS /
PERSISTENCE
DATA STORE
● HTTP
● SESSION MANAGEMENT
● RPC
● PERSISTENCE
● CACHING
● SECURITY
● MESSAGING
● ALL LAYERS SUPPORT POPO BASED DESIGN
● CONTROLLERS AND SERVICES ARE
CONSUMERS OF DOMAIN OBJECTS
● BUSINESS LOGIC ONLY IN DOMAIN OBJECTS
● NO DIRECT ACCESS TO DAO EXCEPT FROM
DOMAIN OBJECT
● DOMAIN FIRST, FRAMEWORK SECOND
● FRAMEWORK CONCERNS ARE
IMPLEMENTED BY DI
ADVANTAGES
○ PROMOTES HIGH COHESION AND LOW COUPLING
○ EASY TO TEST DOMAIN COMPONENTS
○ BUSINESS (DOMAIN) LOGIC IS ISOLATED FROM NON-DOMAIN AND
FRAMEWORK CODE
○ ADDING / CHANGING SERVICES DOES NOT INFLUENCE THE
DOMAIN OR OTHER SERVICES
DEVELOPMENT IMPACT
EFFORT TO
ENHANCE /
MAINTAIN
COMPLEXITY TO IMPLEMENT
SOURCE: PATTERNS OF ENTERPRISE APPLICATION ARCHITECTURE, MARTIN FOWLER
TRANSACTION
SCRIPTS
TABLE MODULES
DOMAIN MODEL
HOW TO DO DDD
THE UBIQUITOUS LANGUAGE
UBIQUITOUS LANGUAGE
○ SHARED TEAM LANGUAGE (DEVELOPERS AND DOMAIN EXPERTS)
○ UBIQUITOUS IS NOT AN ATTEMPT TO DESCRIBE ENTERPRISE-WIDE
DOMAIN LANGUAGE
○ ONE UBIQUITOUS LANGUAGE PER BOUNDED CONTEXT (CODE
BASE)
○ IF YOU TRY TO APPLY A SINGLE UBIQUITOUS LANGUAGE TO AN
ENTIRE ENTERPRISE, YOU WILL FAIL
public function chargeCustomer(ChargecodeData $chargecode, Transaction $transaction) {
if($chargecode->getEmail() === $transaction->getCustomerEmail()
&& $transaction->getCustomerCreditCardExpiration > date(‘Y-m’)
&& in_array($transaction->getStatus(), [‘SALE’, ‘REBILL’, ‘AUTHORISE’])
&& $chargecode->isUsed() === false) {
// Do charge
}
throw new ChargeCustomerException();
}
/**
* @Inject
* @var ChargeCodeValidationPolicy
*/
protected $oneClickPolicy;
public function chargeCustomer(ChargecodeData $chargecode, Transaction $transaction) {
if($this->oneClickPolicy->isAllowed($chargecode, $transaction)) {
// Do charge
}
throw new ChargeCustomerException();
}
DOMAIN OBJECTS ARE INSTANCES OF REAL ENTITIES THAT HOLD THE
BUSINESS LOGIC.
DOMAIN OBJECTS
MAIN ELEMENTS OF DDD
○ DESIGN A CONCEPT AS AN ENTITY WHEN YOU CARE ABOUT ITS
INDIVIDUALITY, WHEN DISTINGUISHING IT FROM ALL OTHER
OBJECTS IN A SYSTEM IS A MANDATORY CONSTRAINT
(CUSTOMER, MEMBERSHIP)
○ THE ENTITY SHOULD NOT BE BOUND TO ANY FRAMEWORK (ORM),
IT SHOULD BE A PLAIN OLD PHP OBJECT (POPO)
ENTITIES
/** @Entity */class Membership{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $status;
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @OneToMany(targetEntity="Transaction", mappedBy="membership") */
private $transactions;
public function __construct {
$this->transactions = new ArrayCollection();
}
public function getCustomer() { return $this->customer; }
public function getTransactions() { return $this->transactions;}}POPO
VALUE OBJECT
○ STRIVE TO MODEL USING VALUE OBJECTS INSTEAD OF ENTITIES
WHEREVER POSSIBLE
○ IMMUTABLE, AFTER THE OBJECT HAS BEEN INSTANTIATED, NONE
OF ITS METHODS WILL CAUSE ITS STATE TO CHANGE
○ INSTEAD OF CHANGING THE ATTRIBUTES, WOULD OBJECT
REPLACEMENT WORK INSTEAD?
$factory = new ChargeCodeGenerationDataFactory();
$chargeCodeData = $factory->generateFromArray($data);
class ChargeCodeGenerationData{
private $transactionId;
private $emailAddress;
private $accountId;
public function __construct($transactionId, $emailAddress, $accountId) {
$this->transactionId = $transactionId;
$this->emailAddress = $emailAddress;
$this->accountId = $accountId;
}
public function toArray() { return [‘transactionId’ => $this->transactionId,
‘emailAddress’ => $this->emailAddress,
‘accountId’ => $this->accountId]; }
public function toJSON() { return json_encode($this->toArray());}}
○ IN A CUSTOMER MANAGEMENT CONTEXT CUSTOMER SHOULD BE
AN ENTITY
○ IN A MEMBERSHIP CONTEXT CUSTOMER SHOULD BE A VALUE
OBJECT
VO BASED ON BOUNDED CONTEXT
○ PROVIDES FUNCTIONALITIES FOR THE DOMAIN
○ STATELESS
○ DOMAIN SERVICES != APPLICATION SERVICES != CONTROLLER
○ DOMAIN SERVICES CAN HOST DOMAIN LOGIC
○ PERFORM A SIGNIFICANT BUSINESS PROCESS
○ TRANSFORM A DOMAIN OBJECT FROM ONE COMPOSITION TO ANOTHER
○ CALCULATE A VALUE REQUIRING INPUT FROM MORE THAN ONE DOMAIN OBJECT
SERVICES
class OneClickService{
/**
* @var ChargecodeAuthcodeValidatorInterface
*/
protected $_dataAccessor; /**
* @var \Tc_Bz_HashGenerator_Interface
*/
protected $_hashGenerator;
/**
* @var ChargecodeAuthcodeValidationResponseDataFactoryInterface
*/
protected $_factoryResponceValidate;
public function __construct($dataAccessor, $hashGenerator, $factory) { $this-
>_dataAccessor = $dataAccessor;
$this->_hashGenerator = $hashGenerator;
$this->_factoryResponceValidate = $factory;
}
/**
* validate chargecode By Authcode
*
* @param ChargecodeAuthcodeDataInterface $chargecodeAuthcodeValidationData
* @return ChargecodeAuthcodeValidationResponseData
* @throws ChargecodeAuthcodeValidationDataException
*/
public function validateChargecodeByAuthcode(ChargecodeAuthcodeDataInterface $data)
{
$decryptedData = $this->_hashGenerator->decipher( $data>getCryptedString());
if ($decryptedData === false) {
throw new ChargecodeAuthcodeValidationDataException('Not decipherable');
}
$this->_validateEmailLinkedToAuthcode($data->getEmailAddress(),
$data->getTransactionId());
$this->_validateCustomCodeIdLinkedToEnterprise($data->getAccountIdDestination(),
$data->getEnterpriseId());
$this->_validateCustomerIs1Clickable($data->getTransactionId());
$this->_validateCodeNotUsed($data->getAccountIdDestination(),
$data->getEmailAddress());
$reponseData = $data->toArray();
$reponseData['chargecode'] = $decryptedData['hash'];
$response = $this->_factoryResponseValidate->generateResponse($reponseData);
return $response;
}
}
○ GROUP OF ASSOCIATED ENTITIES AND VALUE OBJECTS TREATED
AS A UNIT FOR THE PURPOSE OF DATA EXCHANGE
○ ENTITY AS ROOT ELEMENT
○ ONLY THE ROOT IS OBTAINED THROUGH QUERIES
○ THE ENTITY IS RESPONSIBLE FOR MAINTAINING THE INVARIANCE
○ DELETE OPERATION MUST REMOVE EVERYTHING WITHIN THE
AGGREGATE BOUNDARY AT ONCE (CASCADE DELETE)
AGGREGATES
AGGREGATE
MEMBERSHIP
CUSTOMER
CREDIT CARD EMAILTRANSACTION
EMAILCREDIT CARDTRANSACTION
TRANSACTION
SITE
○ PROVIDES ENCAPSULATION FOR OBJECT / AGGREGATE CREATION
○ PRODUCES AN OBJECT IN A CONSISTENT STATE
FACTORIES
class ChargecodeAuthcodeGenerationResponseDataFactory
{
/**
* Factory method to generate chargecode validation data by authcode
*
* @param array $data Data used to generate
* @throws ChargecodeAuthcodeValidationDataException
* @return ChargecodeAuthcodeGenerationResponseData
*/
public function generateFromArray(array $data)
{
$this->_validateParameters($data);
$chargecodeData = $this->_generateDataAccessObject($data);
$data = $this->_unsetUnusedParameters($data);
$chargecodeData->setParams($data);
return $chargecodeData;
}
protected function _sendException()
{
throw new ChargecodeAuthcodeGenerationResponseDataException('Could not Generate a response');
}
protected function _generateDataAccessObject(array $data)
{
return new ChargecodeAuthcodeGenerationResponseData($data['authCode'], $data['account_id_destination'], $data['email_address'],
$data['crypted_string'], null);
}
}
○ PATTERN FOR RETRIEVING AND SAVING OBJECTS IN THE DB
○ SHOULD NOT BE TIED TO SPECIFIC FRAMEWORK (ORM)
○ EASY SUBSTITUTION FOR TESTING
REPOSITORIES
class SubEnterpriseRepository
{
/**
* @Inject
* @var SubEnterpriseDataAccessorInterface
*/
private $_dataAccessor;
/**
* @Inject
* @var SubEnterpriseParserInterface
*/
private $_dataParsor;
/**
* @Inject
* @var SubEnterpriseFactoryInterface
*/
private $_dataFactory;
/**
* @param $account
* @return mixed
*/
public function findSubEnterpriseByAccount(Account $account)
{
$results = $this->_dataAccessor->findSubEnterpriseByAccount($account);
$parsedResults = $this->_dataParsor->parseResults($results);
return $this->_dataFactory->create($parsedResults);
}
}
○ OBJECTS SHOULD NOT DEPEND ON CONCRETE CONSTRUCTOR
VARIABLES, INSTEAD TO SHOULD USE INTERFACES
○ OBJECTS SHOULD NOT HAVE TO CONFIGURE ITS INSTANCE
VARIABLES IN THE CONSTRUCTOR OR INIT FUNCTION, INSTEAD
THEY SHOULD RECEIVE THEM ALREADY PRE-CONFIGURED
DEPENDENCY INJECTION
“
”
"Dependency Injection" is a 25-dollar
term for a 5-cent concept. [...]
Dependency injection means giving
an object its instance variables. [...].
- James Shore
PHP-DI
class SubEnterpriseRepository
{
/**
* @Inject
* @var SubEnterpriseDataAccessorInterface
*/
private $_dataAccessor;
/**
* @Inject
* @var SubEnterpriseParserInterface
*/
private $_dataParsor;
/**
* @Inject
* @var SubEnterpriseFactoryInterface
*/
private $_dataFactory;
/**
* @param $account
* @return mixed
*/
public function findSubEnterpriseByAccount(Account $account)
{
$results = $this->_dataAccessor->findSubEnterpriseByAccount($account);
$parsedResults = $this->_dataParsor->parseResults($results);
return $this->_dataFactory->create($parsedResults);
}
}
// Load the container
$container = new DI\Container();
$container->addDefinitionsByFile(new ArrayDefinitionFile(‘di.php’));
// Create the object
$repository = new SubEnterpriseRepository();
// Inject the dependencies
$container->injectOn($repository);
// di.php
return [
‘SubEnterpriseDataAccessorInterface’
=> [ ‘class’ : ‘DoctrineSubEnterpriseAccessor’,
‘methods’ => [
‘setHydrator’ => DOCTRINE_CORE::HYDRATE_SCALAR
]
],
‘SubEnterpriseParserInterface’
=> new SubEnterpriseDoctrineToArrayParser(),
‘SubEnterpriseFactoryInterface’
=> new SubEnterpriseResultFactory()
];
PHP-DI-ZF1
/**
* Initialize the dependency injection container
*/
protected function _initDependencyInjection()
{
$this->bootstrap('DependencyInjectionContainerResource');
$container = $this->getResource('DependencyInjectionContainerResource');
$dispatcher = new \DI\ZendFramework1\Dispatcher();
$dispatcher->setContainer($container);
$frontController = Zend_Controller_Front::getInstance();
$frontController->setDispatcher($dispatcher);
}
class Tc_Application_Resource_DependencyInjectionContainerResource extends
Zend_Application_Resource_ResourceAbstract
{
public function init()
{
$this->_container = new \DI\Container();
foreach($this->_definitionFilePath as $DIResourceFile) {
$file = $this->_loadDefinitionFile(realpath($DIResourceFile));
$this->_container->addDefinitionsFromFile($file);
}
return $this->_container;
}
private function _loadDefinitionFile($DIResourceFile)
{
$file = null;
if (0 === substr_compare($DIResourceFile, 'php', -3, 3, true)) {
$file = new \DI\Definition\FileLoader\ArrayDefinitionFileLoader($DIResourceFile);
}
if (0 === substr_compare($DIResourceFile, 'yml', -3, 3, true)) {
$file = new \DI\Definition\FileLoader\YamlDefinitionFileLoader($DIResourceFile);
}
if (0 === substr_compare($DIResourceFile, 'json', -4, 4, true)) {
$file = new \DI\Definition\FileLoader\JsonDefinitionFileLoader($DIResourceFile);
}
if($file === null) {
throw new Gamma_Application_Resource_Exception('Invalid Definition File Type');
}
return $file;
}
PHP-DI-ZF1
class Direct_FollowController extends Zend_Controller_Action
{
/**
* @Inject(lazy=true)
* @var \Tc\Service\ChargeCodeService
*/
private $_oneClickService;
/**
* @Inject(lazy=true)
* @var \Tc\ChargeCode\Data\ChargecodeAuthcodeGenerationDataFactory
*/
private $_factory;
public function generateChargeCodeByAuthcodeAction()
{
$request = $this->getRequest();
$this->getResponse()->setHeader('Content-Type', 'application/json', true);
try {
$chargeCodeGenerationData = $this->_factory->generate($request->getParams());
$this->view->answer = $this->_oneClickService->generate($chargeCodeGenerationData);
$this->render('generate-charge-code');
} catch (\Tc\ChargeCode\Data\Exception\ChargeCodeGenerationDataException $chargeCodeException) {
$this->view->requiredParameters = $chargeCodeException;
$this->render('charge-code-generation-authcode-invalid-parameters');
} catch (\Tc\ChargeCode\Data\Exception\ChargecodeAuthcodeGenerationResponseDataException $chargeCodeException) {
$this->view->requiredParameters = $chargeCodeException;
$this->render('charge-code-generation-authcode-invalid-parameters');
}
}
DOMAIN & SUB-DOMAIN
THE HEART OF DDD
DOMAIN vs DOMAIN MODEL
○ THE DOMAIN IS THE PROBLEM TO BE ADDRESSED IN SOFTWARE
○ A DOMAIN MODEL IS THE REPRESENTATION OF IN CODE OF THE
SOLUTION FOR THE DOMAIN PROBLEM
○ HAS TO BE CREATED WITH THE COOPERATION OF DEVELOPERS
AND DOMAIN EXPERTS
○ THE GOAL OF DOMAIN DRIVEN DESIGN IS TO CREATE OBJECT IN
CODE THAT REFLECT THE DOMAIN
○ DOMAIN CAN BE DECOMPOSED INTO SUB-DOMAINS (PRODUCTS,
BILLING, MEMBERSHIP)
○ SUB-DOMAIN SPLIT THE DOMAIN INTO DIFFERENT UNIQUE
SECTIONS
○ BOUNDED CONTEXT SPLIT THE CODE INTO DIFFERENT CODE
BASES
○ SUB-DOMAIN CAN BE IMPLEMENTED BY MULTIPLE BOUNDED
CONTEXTS (MEMBERSHIP AND MEMBERSHIP REBILL)
SUB-DOMAIN vs BOUNDED CONTEXT
“
”
ORGANIZATIONS WHICH DESIGN
SYSTEMS ARE CONSTRAINED TO
PRODUCE DESIGNS WHICH ARE
COPIES OF THE COMMUNICATION
STRUCTURES OF THESE
ORGANIZATIONS- Melvin Conway
SUB-DOMAIN BOUNDARIES ARE DETERMINED IN PART BY THE
COMMUNICATION STRUCTURES WITHIN AN ORGANIZATION
CONWAY’S LAW
○ CODE BASE FOR DOMAIN MODEL CONTEXT
○ EVERY MODEL’S PROPERTIES AND OPERATIONS HAS SPECIAL
MEANING WITHIN THE SPECIFIC CONTEXT
BOUNDED CONTEXTS
ENTITYVALUE OBJECT
CONTEXT MAPPING
○ PARTNERSHIP○ SUCCEED OR FAIL TOGETHER
○ COORDINATED PLANNING
○ JOINT MANAGEMENT OF
INTEGRATION
○ SCHEDULED COMPLETION
○ SHARED KERNEL○ INTIMATE INTERDEPENDENCIES
○ KEEP IT SMALL
○ CAN’T BE CHANGED WITHOUT
CONSULTATION
○ CUSTOMER-SUPPLIER○ UPSTREAM / DOWNSTREAM
RELATIONSHIP
○ DOWNSTREAM PRIORITIES FACTOR
INTO UPSTREAM PLANNING
○ NEGOTIATED SCHEDULE
○ CONFORMIST○ UPSTREAM / DOWNSTREAM
RELATIONSHIP
○ UPSTREAM HAS NO MOTIVATION TO
PROVIDE FOR DOWNSTREAM
CONTEXT MAPPING
○ ANTICORRUPTION LAYER○ TRANSLATION LAYER
○ LAYER TRANSLATES IN ONE OR BOTH
DIRECTIONS BETWEEN THE TWO
MODELS
○ OPEN HOST SERVICE○ SOA
○ PROTOCOL TO GIVE ACCESS TO
YOUR SUBSYSTEM
○ PUBLISHED LANGUAGE○ WELL-DOCUMENTED SHARED
LANGUAGE
○ SEPARATE WAYS○ COMPLETELY CUT LOOSE FROM
EACH OTHER
○ INTEGRATION IS EXPENSIVE WITH
SMALL BENEFITS
○ BIG BALL OF MUD○ MIXED MODELS
○ INCONSISTENT BOUNDARIES
○ DRAW A BOUNDARY AROUND THE
MESS
○ DO NOT TRY TO APPLY
SOPHISTICATED MODELING
“
”
Any 3rd party system
that I have to integrate
with, was written by
a drunken monkey
typing with his feet
- Oren Eini
SUB-DOMAIN
SUB-DOMAIN
ANTICORRUPTION
LAYER THAT
TRANSLATES
USER/ROLES
BETWEEN SUB-
DOMAINS
“
”
Any fool can write code that a
computer can understand. Good
programmers write code that humans
can understand.
- Martin Fowler
REFERENCES
DOMAIN-DRIVEN DESIGN
BY ERIC EVANS
IMPLEMENTING DOMAIN-DRIVEN
DESIGN
BY VAUGHN VERNON