Domain Driven Design

46
DOMAIN DRIVEN DESIGN TACKLING COMPLEXITY

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

Transcript of Domain Driven Design

Page 1: Domain Driven Design

DOMAIN DRIVEN DESIGN

TACKLING COMPLEXITY

Page 2: Domain Driven Design

@pascallarocque

○ TRUSTCHARGE TEAM

○ BEHAT GUY

○ TDD GUY

○ SOLID GUY

○ PATTERN GUY

○ FATHER OF 3

○ STAR WARS GEEK

Page 3: Domain Driven Design

SOFTWARE IS COMPLICATED

Page 4: Domain Driven Design

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

Page 5: Domain Driven Design

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

Page 6: Domain Driven Design

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

Page 7: 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

Page 8: Domain Driven Design

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

Page 9: Domain Driven Design

DEVELOPMENT IMPACT

EFFORT TO

ENHANCE /

MAINTAIN

COMPLEXITY TO IMPLEMENT

SOURCE: PATTERNS OF ENTERPRISE APPLICATION ARCHITECTURE, MARTIN FOWLER

TRANSACTION

SCRIPTS

TABLE MODULES

DOMAIN MODEL

Page 10: Domain Driven Design

HOW TO DO DDD

THE UBIQUITOUS LANGUAGE

Page 11: Domain Driven Design

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

Page 12: Domain Driven Design

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();

}

Page 13: Domain Driven Design

DOMAIN OBJECTS ARE INSTANCES OF REAL ENTITIES THAT HOLD THE

BUSINESS LOGIC.

DOMAIN OBJECTS

Page 14: Domain Driven Design

MAIN ELEMENTS OF DDD

Page 15: Domain Driven Design

○ 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

Page 16: Domain Driven Design

/** @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

Page 17: Domain Driven Design

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?

Page 18: Domain Driven Design

$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());}}

Page 19: Domain Driven Design

○ 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

Page 20: Domain Driven Design

○ 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

Page 21: Domain Driven Design

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;

}

}

Page 22: Domain Driven Design

○ 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

Page 23: Domain Driven Design

AGGREGATE

MEMBERSHIP

CUSTOMER

CREDIT CARD EMAILTRANSACTION

EMAILCREDIT CARDTRANSACTION

TRANSACTION

SITE

Page 24: Domain Driven Design

○ PROVIDES ENCAPSULATION FOR OBJECT / AGGREGATE CREATION

○ PRODUCES AN OBJECT IN A CONSISTENT STATE

FACTORIES

Page 25: Domain Driven Design

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);

}

}

Page 26: Domain Driven Design

○ PATTERN FOR RETRIEVING AND SAVING OBJECTS IN THE DB

○ SHOULD NOT BE TIED TO SPECIFIC FRAMEWORK (ORM)

○ EASY SUBSTITUTION FOR TESTING

REPOSITORIES

Page 27: Domain Driven Design

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);

}

}

Page 28: Domain Driven Design

○ 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

Page 29: Domain Driven Design

"Dependency Injection" is a 25-dollar

term for a 5-cent concept. [...]

Dependency injection means giving

an object its instance variables. [...].

- James Shore

Page 30: Domain Driven Design

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()

];

Page 31: Domain Driven Design

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;

}

Page 32: Domain Driven Design

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');

}

}

Page 33: Domain Driven Design

DOMAIN & SUB-DOMAIN

THE HEART OF DDD

Page 34: Domain Driven Design

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

Page 35: Domain Driven Design

○ 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

Page 36: Domain Driven Design

ORGANIZATIONS WHICH DESIGN

SYSTEMS ARE CONSTRAINED TO

PRODUCE DESIGNS WHICH ARE

COPIES OF THE COMMUNICATION

STRUCTURES OF THESE

ORGANIZATIONS- Melvin Conway

Page 37: Domain Driven Design

SUB-DOMAIN BOUNDARIES ARE DETERMINED IN PART BY THE

COMMUNICATION STRUCTURES WITHIN AN ORGANIZATION

CONWAY’S LAW

Page 38: Domain Driven Design
Page 39: Domain Driven Design

○ CODE BASE FOR DOMAIN MODEL CONTEXT

○ EVERY MODEL’S PROPERTIES AND OPERATIONS HAS SPECIAL

MEANING WITHIN THE SPECIFIC CONTEXT

BOUNDED CONTEXTS

Page 40: Domain Driven Design

ENTITYVALUE OBJECT

Page 41: Domain Driven Design

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

Page 42: Domain Driven Design

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

Page 43: Domain Driven Design

Any 3rd party system

that I have to integrate

with, was written by

a drunken monkey

typing with his feet

- Oren Eini

Page 44: Domain Driven Design

SUB-DOMAIN

SUB-DOMAIN

ANTICORRUPTION

LAYER THAT

TRANSLATES

USER/ROLES

BETWEEN SUB-

DOMAINS

Page 45: Domain Driven Design

Any fool can write code that a

computer can understand. Good

programmers write code that humans

can understand.

- Martin Fowler

Page 46: Domain Driven Design

REFERENCES

DOMAIN-DRIVEN DESIGN

BY ERIC EVANS

IMPLEMENTING DOMAIN-DRIVEN

DESIGN

BY VAUGHN VERNON