building rich domain models

68
Improving Application Design with a Rich Domain Model with a Rich Domain Model Ch i Ri h d Chris Richardson Author of POJOs in Action Chris Richardson Consulting, Inc http://www.chrisrichardson.net Slide 1 3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Post on 21-Oct-2014

6.385 views

Category:

Technology


2 download

DESCRIPTION

Describes the pieces of a domain model, procedural code smells, and how to refactor procedural code into a rich domain model.

TRANSCRIPT

Page 1: Building Rich Domain Models

Improving Application Design with a Rich Domain Modelwith a Rich Domain Model

Ch i Ri h dChris Richardson

Author of POJOs in ActionChris Richardson Consulting, Inc

http://www.chrisrichardson.netp //

Slide 13/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 2: Building Rich Domain Models

Overall presentation goalp g

Learn how to improve application design by using truly object-g y g y j

oriented business logic

3/1/2009 2Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 3: Building Rich Domain Models

About Chris

Grew up in EnglandGrew up in EnglandLive in Oakland, CAOver twenty years of software development experience

Building object-oriented software since 1986Using Java since 1996Using J2EE since 1999

Author of POJOs in ActionSpeaker at JavaOne, JavaPolis, NFJS, JUGs, ….Chair of the eBIG Java SIG in Chair of the eBIG Java SIG in Oakland (www.ebig.org)Run a consulting and training company that helps organizations build better organizations build better software faster

3/1/2009 3Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 4: Building Rich Domain Models

Agendag

Wh th l bj t ?Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to OOEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 4Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 5: Building Rich Domain Models

Objects in LISP (1987-1993)j ( )(defclass Account ()((account-id :accessor account-id :initarg :account-id)(balance :accessor account-balance :initarg :balance))

)State

+ )

(defmethod debit ((Account account) amount) (decf (account-balance account) amount))

(defmethod credit ((Account account) amount)(incf (account balance account) amount))

+ Behavior

(incf (account-balance account) amount))

CL-USER 5 > (setq a (make-instance 'account :account-id "abc123" :balance 10.0))#<ACCOUNT 200C05AF>

CL-USER 6 > (describe a)CL-USER 6 > (describe a)

#<ACCOUNT 200C05AF> is an ACCOUNTACCOUNT-ID "abc123"BALANCE 10.0

CL USER 7 > (debit a 5)CL-USER 7 > (debit a 5)5.0

CL-USER 8 > (describe a)

#<ACCOUNT 200C05AF> is an ACCOUNTACCOUNT ID " b 123"

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 5

ACCOUNT-ID "abc123"BALANCE 5.0

Page 6: Building Rich Domain Models

Objects in C++ (1993-1996)j ( )#ifndef ACCOUNT_H_#define ACCOUNT_H_

class Account {class Account {public:

Account(char* account_id, double balance);void debit(double amount);void credit(double amount);double getBalance();

private:private:char* account_id;double balance;

};#endif /*ACCOUNT_H_*/

#incl de "Acco nt h"

State+

Behavior#include "Account.h"

Account::Account(char* account_id, double balance) {…

}void Account::debit (double amount) {

b lbalance -= amount;}

void Account::credit(double amount) {balance += amount;

}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 6

double Account::getBalance() { return balance; }

Page 7: Building Rich Domain Models

Objects in Java (1996-1999)j ( )public class Account {

private int id;

private double balance;

private OverdraftPolicy overdraftPolicy;

private String accountId; Stateprivate CalendarDate dateOpened;

Account() {}

public void debit(double amount) {

+ Behavior

p ( ) {assert amount > 0;double originalBalance = balance;double newBalance = balance - amount;overdraftPolicy.beforeDebitCheck(this, originalBalance,

newBalance);balance = newBalance;

d ftP li ft D bitA ti (thi i i lB l B l )overdraftPolicy.afterDebitAction(this, originalBalance, newBalance);}

public void credit(double amount) {assert amount > 0;balance += amount;

}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 7

}

Page 8: Building Rich Domain Models

EJB objects (1999- ?)j ( )

Applications were still built ppfrom objects

But those objects were very But those objects were very different …

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 8

Page 9: Building Rich Domain Models

Example Banking UI p g

3/1/2009 9Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 10: Building Rich Domain Models

Example procedural designp p g

3/1/2009 10Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 11: Building Rich Domain Models

Example procedural codep p

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId,

public class Account {

public static final int NEVER = 1;p g ( g , g ,double amount) {

Account fromAccount = accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;double newBalance = fromAccount.getBalance() - amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

if (newBalance < 0)throw new MoneyTransferException("In sufficient funds");

p ;public static final int ALLOWED = 2;

private int id;private double balance;private int overdraftPolicy;private String accountId;private Date dateOpened;private double requiredYearsOpen;private double limit;

Behavior Statebreak;

case Account.ALLOWED:Calendar then = Calendar.getInstance();then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);if (monthsOpened < 0) {

O d

Account() {}

public Account(String accountId, double balance, int overdraftPolicy,

Date dateOpened, double requiredYearsOpen, double limit) {….. }

yearsOpened--;monthsOpened += 12;

}yearsOpened = yearsOpened + (monthsOpened / 12.0);if (yearsOpened < fromAccount.getRequiredYearsOpen()

|| newBalance < fromAccount.getLimit())throw new MoneyTransferException("Limit exceeded");

break;default:

throw new MoneyTransferException("Unknown overdraft type: "

public int getId() {return id;}

public String getAccountId() {return accountId;}

public void setBalance(double balance) { this.balance = balance; }

public double getBalance() { return balance; }

public int getOverdraftPolicy() { return overdraftPolicy; }throw new MoneyTransferException( Unknown overdraft type: + fromAccount.getOverdraftPolicy());

}fromAccount.setBalance(newBalance);toAccount.setBalance(toAccount.getBalance() + amount);TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,

amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

public int getOverdraftPolicy() { return overdraftPolicy; }

public Date getDateOpened() { return dateOpened; }

public double getRequiredYearsOpen() { return requiredYearsOpen; }

public double getLimit() {return limit; }}

return txn;}

3/1/2009 11Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 12: Building Rich Domain Models

Why write code like this?y

EJB made writing object-oriented code diffi lt/i ibldifficult/impossibleImplementing new functionality is easy

Add i iAdd a new transaction scriptAdd code to a new transaction script

Manipulating relational data is easierManipulating relational data is easierDistribution is easierN d t d l d i No need to do any real design, e.g.

Create new classesDetermine responsibilitiesDetermine responsibilities

3/1/2009 12Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 13: Building Rich Domain Models

Unable to handle complexityp y

Works well for simple business logicE g the example wasn’t that badE.g. the example wasn t that bad

But with complex business logic: Large transaction scripts: 100s/1000s LOCDifficult/impossible to understand, test, and maintain

What’s worse: business logic has a habit What s worse: business logic has a habit of growing

New requirements ⇒ Add a few more lines to the transaction scriptto the transaction scriptMany new requirements ⇒ big messSoon or later you end up with unmaintainable code unmaintainable code

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 13

Page 14: Building Rich Domain Models

The legacy of EJBg y

Java is an object-oriented language

AND

Object-oriented design is a better way to tackle complexity

YET

Many complex enterprise Java applications are written in a procedural style

3/1/2009 14Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 15: Building Rich Domain Models

Today – rich domain models are growing in popularitygrowing in popularity

POJOs POJOs Plain Old Java ObjectsLeverage OO features of Java

O/R mapping frameworks for persisting POJOs:persisting POJOs:

HibernateJava Persistence API…

S i AOP d A tJ f Spring AOP and AspectJ for handling cross-cutting concerns:

Transaction managementS itSecurityLoggingAuditing…

3/1/2009 15Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 16: Building Rich Domain Models

Agendag

Where are the real objects?Where are the real objects?Overview of the Domain Model patternpatternDomain model building blocksRole of frameworksRole of frameworksObstacles to OOEliminating common code smellsgRefactoring existing code

3/1/2009 16Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 17: Building Rich Domain Models

Using the Domain Model Patterng

B i l i d t Business logic spread amongst a collection of classes Many classes correspond to real world Many classes correspond to real world concepts: Order, Customer, …Many classes are true objects having Many classes are true objects having both:

State – fieldsState fieldsBehavior – methods that act on the state

3/1/2009 17Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 18: Building Rich Domain Models

Procedural versus OOPresentation Tier

Presentation Tier

Business Tier

TransactionScripts

(Session Beans)

Business Tier

Domain Model

Facade

Data ObjectsObjects

Data Access Tier Data Access Tier

3/1/2009 18Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 19: Building Rich Domain Models

An example domain modelpWeb Tier

MoneyTransferService

BehaviorBusiness Tier

BankingTransaction transfer(fromId, toId, amount)

debit(amount)credit(amount)

balance

Account

amountdate

BankingTransaction

findAccount(id)

AccountRepository

from

toaddTransaction(…)

BankingTransactionRepository

<<interface>>OverdraftPolicy

NoOverdraftLimited

State + Behavior

ExplicitRepresentation

of key tNoOverdraft

Policylimit

Overdraft concepts

3/1/2009 19Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 20: Building Rich Domain Models

DEMODEMO

Code Walkthrough

3/1/2009 20Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 21: Building Rich Domain Models

Benefits of the Domain Model Pattern

Improved maintainabilityThe design reflects realityThe design reflects realityKey domain classes are represented by classesThe design is more modularThe design is more modular

Improved testabilitySmall classes that can be tested in isolation

Improved reusabilityClasses can be used in other applications

Building a domain modelBuilding a domain modelCreates shared understandingDevelops an ubiquitous languagep q g g

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 21

Page 22: Building Rich Domain Models

Quantifiably simpler codeQ y p

Procedural few longer more Object oriented more simpler Procedural – few, longer, more complex methods

Object-oriented – more, simpler, shorter methods

3/1/2009 22Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 23: Building Rich Domain Models

Drawbacks of the Domain Model patternp

Requires object-oriented design skillsWorks best if domain model is transparently “mappable” to the data

E.g. nice database schemaUgly schemas and data stored in other

l h llapplications is a challenge

3/1/2009 23Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 24: Building Rich Domain Models

When to use it

The business logic is reasonably complex or you anticipate that it will beYou have the skills to design oneYou can either:

Use an ORM frameworkInvest in writing a data access framework

3/1/2009 24Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 25: Building Rich Domain Models

Agendag

Wh th l bj t ?Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to OOEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 25Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 26: Building Rich Domain Models

Domain model building blocksg

Roles aka Roles aka stereotypesBenefits of roles:Benefits of roles:

Guide designHelp name bj tobjects

Aid understandingRoles (from Roles (from Domain-Driven Design)

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 26

Page 27: Building Rich Domain Models

Entityy

Objects with a public class Account {Objects with a distinct identityTypically correspond to real world

public class Account {

private int id;

private double balance;

private OverdraftPolicy overdraftPolicy;

to real world conceptsAlmost always persistent

private String accountId;

private CalendarDate dateOpened;

Account() {}persistent

Encapsulate state and behaviorOften modal ⇒ call

public void debit(double amount) {assert amount > 0;double originalBalance = balance;double newBalance = balance - amount;overdraftPolicy.beforeDebitCheck(this, originalBalance, newBalance);balance = newBalance;overdraftPolicy.afterDebitAction(this, originalBalance, newBalance);Often modal ⇒ call

methods in a particular order

}

public void credit(double amount) {assert amount > 0;balance += amount;

}

3/1/2009 27Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 28: Building Rich Domain Models

Value Objectsj

Objects that are defined by the values

public class CalendarDate {

private Date date;defined by the values of their attributesTwo instances with identical values can be

d i t h bl

CalendarDate() {}

public CalendarDate(Date date) {this.date = date;

}

used interchangeablyTwo flavors

Persistent – parts of entities

public Date getDate() {return date;

}

public double getYearsOpen() {Calendar then = Calendar.getInstance();then setTime(date);entities

Transient – intermediate values

Ideally immutableOft i i f

then.setTime(date);Calendar now = Calendar.getInstance();

int yearsOpened = now.get(Calendar.YEAR) –then.get(Calendar.YEAR);

int monthsOpened = now.get(Calendar.MONTH) -then.get(Calendar.MONTH);

if (monthsOpened < 0) {Often missing from procedural code –Primitive Obsession code smell

if (monthsOpened 0) {yearsOpened--;monthsOpened += 12;

}return yearsOpened + (monthsOpened/12.0);

}

}

3/1/2009 28Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 29: Building Rich Domain Models

More examples of Value Objectsp jpublic class User {

private String firstName;private String lastName;

public class User {private int id;private PersonName name;private UserId login;

p g ;private String login;private String password;…

}

private UserId login;private Password password;…

}

public class Password implements Serializable {

private String passwordString;

public Password(String passwordString) { this.passwordString = passwordString == null ? null : passwordString.trim();

}

…@Override

bli St i t St i () {

public class PersonName {private String firstName;private String lastName;

PersonName() {}

public String toString() {return new ToStringBuilder(this).append("password", "****").toString();

}public PersonName(String firstName,

String lastName) {this.firstName = firstName;this.lastName = lastName;

}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 29

Page 30: Building Rich Domain Models

Aggregatesgg g

A cluster of related A cluster of related entities and valuesBehaves as a unitH tHas a rootHas a boundaryObjects outside the Objects outside the aggregate can only reference the rootDeleting the root Deleting the root removes everything

3/1/2009 30Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 31: Building Rich Domain Models

Repositoriesp

Manages a collection of public interface AccountRepository {

Manages a collection of objectsProvides methods for:

Adding an objectFinding object or objects

Account findAccount(String accountId);

void addAccount(Account account);

}

Finding object or objectsDeleting objects

Consists of an interface and an implementation l

public class HibernateAccountRepository implements AccountRepository {

private HibernateTemplate hibernateTemplate;

public HibernateAccountRepository(HibernateTemplate template) {hibernateTemplate = template;

classEncapsulates database access mechanismKeeps the ORM

}

public void addAccount(Account account) {hibernateTemplate.save(account);

}

public Account findAccount(final String accountId) {Keeps the ORM framework out of the domain modelSimilar to a DAO

return (Account) DataAccessUtils.uniqueResult(hibernateTemplate.findByNamedQueryAndNamedParam(

"Account.findAccountByAccountId", "accountId",accountId));

}

}

3/1/2009 31Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 32: Building Rich Domain Models

Services

Implements logic that public interface MoneyTransferService {

Implements logic that cannot be put in a single entityNot persistent

BankingTransaction transfer(String fromAccountId,String toAccountId, double amount);

}

pConsists of an interface and an implementation class

public class MoneyTransferServiceImpl implements MoneyTransferService {

private final AccountRepository accountRepository;

private final BankingTransactionRepository

Service method usually:Invoked (indirectly) by presentation tierInvokes one or more

bankingTransactionRepository;

public MoneyTransferServiceImpl(AccountRepository accountRepository,BankingTransactionRepository bankingTransactionRepository) {

this.accountRepository = accountRepository;this.bankingTransactionRepository = bankingTransactionRepository;

}

repositoriesInvokes one or more entities

Keep them thin

public BankingTransaction transfer(String fromAccountId,String toAccountId, double amount) {

…}

}Keep them thin

3/1/2009 32Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 33: Building Rich Domain Models

Factories

Use when a constructor is insufficientE l l bj i Encapsulates complex object creation logicHandles varying productsHandles varying products

Different kinds of factoriesFactory classesFactory classesFactory methods

Example: OrderFactoryp yCreates Order from a shopping cartAdds line items

3/1/2009 33Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 34: Building Rich Domain Models

Agendag

Wh th l bj t ?Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to OOEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 34Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 35: Building Rich Domain Models

Use the POJO programming modelp g g

Your domain model might outlive infrastructure frameworks ⇒ Minimize dependencies on themPOJO = Plain Old Java ObjectDon't implement any infrastructure interfaces Don't call infrastructure APIsNo infrastructure framework annotations?

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 35

Page 36: Building Rich Domain Models

Use dependency injectionp y j

Spring instantiates and wires together components:S i f t i d it iServices, factories and repositories

Dependency injection into entitiesOne option is @Configurable but it’s not POJOUse Hibernate Interceptor + manual injection instead?Use Hibernate Interceptor + manual injection instead?

Benefits:Decouples components from one another and the infrastructureinfrastructureImproves testability

<beans>

<bean id="accountService"

public AccountServiceImpl(AccountDao accountDao, BankingTransactionDao bankingTransactionDao) {

this.accountDAO = accountDao;this.bankingTransactionDAO = bankingTransactionDao;

}

class="net.chris...domain.AccountServiceImpl"><constructor-arg ref="accountDao"/><constructor-arg ref="bankingTransactionDao"/>

</bean>

Slide 36

} </beans>

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 37: Building Rich Domain Models

Use Aspect-Oriented Programmingp g g

Spring AOP for service-level crosscutting concerns:crosscutting concerns:

E.g. transaction management, security, logging etc.

AspectJ for entity and value object crosscutting concerns

E.g. tracking changes to fieldsfieldsBut AJC/Load-time weaving has a cost

BenefitsBenefitsDecouples code from infrastructureImproves modularity

3/1/2009 37Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 38: Building Rich Domain Models

Use object/relational mappingj / pp g

Persisting objects <class name="Account" table="BANK ACCOUNT" >g j

with JDBC is usually too much work

table BANK_ACCOUNT ><id name="id" column="ACCOUNT_ID">

<generator class="native" /></id><property name="balance" /><property name="accountId" />

Implement DAOs with Spring ORM

<property name="dateOpened" /><many-to-one name="overdraftPolicy" />

</class>

BenefitsLess codeSi l d

public class HibernateAccountDao implements AccountDao {

private HibernateTemplate hibernateTemplate;

public HibernateAccountDao(HibernateTemplate template) {

Simpler codeImproved testability

template) {this.hibernateTemplate = template;

}

public void addAccount(Account account) {hibernateTemplate.save(account);

}

Slide 38

}…}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 39: Building Rich Domain Models

Agendag

Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to good OO designEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 39

Page 40: Building Rich Domain Models

Web frameworks need Java Beans

Web frameworks can bind request parameters directly to domain objectsparameters directly to domain objectsEasy creation of domain objectsEasy editing of detached domain objectsasy ed g o de ac ed do a objec s1. Load object graph from database2. Store object graph in HttpSession3 Bind Http parameters to object's properties3. Bind Http parameters to object's properties4. Reattach object graph and update the database

A lot less code But domain objects must be JavaBeans:

Public default constructorJa aBean st le sette sJavaBean-style setters

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 40

Page 41: Building Rich Domain Models

The trouble with setters

Poor encapsulationpublic class Address {

private String street1;private String street2;

Objects are now exposing their internal state

No immutable objects

private String street2;

public String getStreet1() {...};public void setStreet1(String street1) {...}…

}No immutable objectsIncreases code complexityMakes it more error-prone

D l i ht b

public class Order {

private Address deliveryAddress;

}

Developers might bypass business method ⇒ Bugs, poor design

public void updateDeliveryAddress(Address deliveryAddress) {// validate before changing…

}p gpublic Address getAddress() {…}…}

Slide 41

order.getAddress().setStreet1("Bad!");

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 42: Building Rich Domain Models

How to copep

Live with it:It's often not that badBut be vigilant for code bypassing b i th dbusiness methods

Use DTOsHides domain objects from web tierBut you have to write more code

Encourage the development of better frameworks

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 42

Page 43: Building Rich Domain Models

More on web bindingg

Web frameworks can bind to properties of nested objects, e.g. order.deliveryAddress.street1But some frameworks require that intermediate objects to be non-nullThis can impact the domain model

Slide 433/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 44: Building Rich Domain Models

Using ORM frameworks gORM frameworks can access private fields

No setters (or getters) required ☺( g ) qBut require:

Default constructorNon-final fieldsid fields

Subtle changes:Collection fields: null ⇒ emptyp yEmbedded objects with null fields ⇒ null referenceObjects sometimes require equals()/hashCode()

Proxy-based lazy loading:Don't access fields outside of instance ⇒ affects equals() and hashCode()Can affect object identity: this != proxyToSameObjectAsThisproxyToSameObjectAsThis

Slide 443/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 45: Building Rich Domain Models

Performance tuning changes the designg

Different web requests display different object

DisplayProjectDetails

Controller

DisplayProjectForReviewControllerdisplay different object

graphsDisplay project detailsDisplay project for

getProject(projectId)

ProjectCoordinator

Display project for approval or rejection

Improve performance by loading object

findProject(projectId)

ProjectRepositoryhibernateTemplate.get()

by loading object graphs using optimized queries (e.g. fetch join) getProjectDetails(projectId)

getProjectForReview(projectId)

ProjectCoordinator

join)Therefore we need multiple Dao (and Service) methods

getProjectForReview(projectId)

findProjectDetails(projectId)findProjectForReview(projectId)

ProjectRepository from Project pinner join fetch p.operations

from Project pService) methods

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 45

from Project pinner join fetch p.operations Inner join fetch p.createdBy

Page 46: Building Rich Domain Models

Untangling what to load from the code

Java Data Objects (JDO) has fetch groupsgroups

Declarative specification of the object graph to loadFields and related objects

Flow:W b ti fi f t h Web tier configures fetch groups Calls ProjectCoordinator.get()O/RM loads object graph specified by active O/RM loads object graph specified by active fetch groups

Look for this feature in JPA i l t ti th t l d f JDOimplementations that evolved from JDO

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. Slide 46

Page 47: Building Rich Domain Models

Agendag

Wh th l bj t ?Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to OOEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 47Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 48: Building Rich Domain Models

Overview of code smells

Code smell = something about the code that does not seem rightImpacts ease of development and testingSome are non-OODSome are the consequences of non-OOD

3/1/2009 48Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 49: Building Rich Domain Models

Refactoring – the cure for stinky code

Refactoring:Refactoring:Systematic way to restructure the restructure the codeWithout changing b h ibehavior

Essential cleanups for cleanups for decaying code

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 49

Page 50: Building Rich Domain Models

Basic refactoringsg

Extract MethodExtract MethodEliminates long methods

Move MethodMove a method to a different class (field or different class (field or parameter)Moves method to where the data is

Push DownPush DownMove a method into subclassesOptionally leave an abstract method behindabstract method behindPart of eliminating conditional logic

3/1/2009 50Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 51: Building Rich Domain Models

Compound refactoringsp g

A sequence of simpler refactoringsCompose methodCompose method

Apply Extract Method repeatedlyUse to replace long method with more readable shorter methodsshorter methods

Replace Type Code With StrategyDefine GOF Strategy class for each type code

R l C diti l With P l hiReplace Conditional With PolymorphismTurn into part of a switch statement into an overriding method in a subclass

R l D V l i h ObjReplace Data Value with ObjectMove field into it’s own classEliminates Primitive Obsession

3/1/2009 51Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 52: Building Rich Domain Models

Long methodg

Methods should be short public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {shortBut business logic is concentrated in the services ⇒ long

p y p p y {

public BankingTransaction transfer(String fromAccountId, String toAccountId,double amount) {

Account fromAccount = accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;double newBalance = fromAccount.getBalance() - amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

if (newBalance < 0)throw new MoneyTransferException("In sufficient funds");services ⇒ long

methodsLong methods are difficult to:

throw new MoneyTransferException( In sufficient funds );break;

case Account.ALLOWED:Calendar then = Calendar.getInstance();then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);if (monthsOpened < 0) {yearsOpened--;

th O d + 12difficult to:Read and understandMaintainTest

monthsOpened += 12;}yearsOpened = yearsOpened + (monthsOpened / 12.0);if (yearsOpened < fromAccount.getRequiredYearsOpen()

|| newBalance < fromAccount.getLimit())throw new MoneyTransferException("Limit exceeded");

break;default:

throw new MoneyTransferException("Unknown overdraft type: "+ fromAccount.getOverdraftPolicy());Test

Fix:Splitting into smaller methods

}fromAccount.setBalance(newBalance);toAccount.setBalance(toAccount.getBalance() + amount);TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,

amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

}

3/1/2009 52Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 53: Building Rich Domain Models

Compose Method Refactoringp g

public class MoneyTransferServiceProceduralImplimplements MoneyTransferService {

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId double amount) {

public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {Account fromAccount =

accountDAO.findAccount(fromAccountId);Account toAccount =

accountDAO.findAccount(toAccountId);double newBalance = fromAccount getBalance()

toAccountId, double amount) {Account fromAccount =

accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;debit(fromAccount, amount);credit(toAccount, amount);TransferTransaction txn = new E t tdouble newBalance = fromAccount.getBalance() –

amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

…break;

default:

TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,

amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

}

public void debit(Account fromAccount, double amount) {

ExtractMethod

…}fromAccount.setBalance(newBalance);toAccount.setBalance(toAccount.getBalance() +

amount);TransferTransaction txn = new

TransferTransaction(fromAccount, toAccount,amount new Date());

public void debit(Account fromAccount, double amount) {double newBalance = fromAccount.getBalance() –

amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:…break;

default:amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

}

…}fromAccount.setBalance(newBalance);

}

public void credit(Account toAccount, double amount) { toAccount.setBalance(toAccount.getBalance() +

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 53

amount);}

Page 54: Building Rich Domain Models

Feature Envyy

Methods that are

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {

Account fromAccount = accountDAO.findAccount(fromAccountId);Methods that are far too interested in data belonging t th l

Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;double newBalance = fromAccount.getBalance() - amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

if (newBalance < 0)throw new MoneyTransferException("In sufficient funds");

break;case Account.ALLOWED:

C l d th C l d tI t ()to other classesResults in:

Poor encapsulation

Calendar then = Calendar.getInstance();then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);if (monthsOpened < 0) {

yearsOpened--;monthsOpened += 12;

}Poor encapsulationLong methods

Fix by moving th d t th

}yearsOpened = yearsOpened + (monthsOpened / 12.0);if (yearsOpened < fromAccount.getRequiredYearsOpen()

|| newBalance < fromAccount.getLimit())throw new MoneyTransferException("Limit exceeded");

break;default:

throw new MoneyTransferException("Unknown overdraft type: "+ fromAccount.getOverdraftPolicy());

methods to the class that has the data

}fromAccount.setBalance(newBalance);toAccount.setBalance(toAccount.getBalance() + amount);TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,

amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

}

3/1/2009 54Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 55: Building Rich Domain Models

Data class

Classes that are public class Account {

public static final int NEVER = 1;public static final int ALLOWED = 2;Classes that are

just getters and setters

public static final int ALLOWED 2;

private int id;private double balance;private int overdraftPolicy;private String accountId;private Date dateOpened;private double requiredYearsOpen;private double limit;

No business logic -it’s in the serviceLeads to:

p ;

Account() {}

public Account(String accountId, double balance, int overdraftPolicy, Date dateOpened, double requiredYearsOpen, double limit)

{….. }

Leads to:Feature envy

Fix by moving

public int getId() {return id;}

public String getAccountId() {return accountId;}

public void setBalance(double balance) { this.balance = balance; }

public double getBalance() { return balance; }y gmethods that act on data into class

public int getOverdraftPolicy() { return overdraftPolicy; }

public Date getDateOpened() { return dateOpened; }

public double getRequiredYearsOpen() { return requiredYearsOpen; }

public double getLimit() {return limit; }}}

3/1/2009 55Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 56: Building Rich Domain Models

Move Method refactoringg

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {

public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId double amount) {amount) {

Account fromAccount = accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;double newBalance = fromAccount.getBalance() - amount;switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

if (newBalance < 0)throw new MoneyTransferException("In sufficient funds");

break;case Account.ALLOWED:

toAccountId, double amount) {Account fromAccount =

accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);assert amount > 0;fromAccount.debit(amount);toAccount.credit(amount);TransferTransaction txn = new Calendar then = Calendar.getInstance();

then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);if (monthsOpened < 0) {yearsOpened--;monthsOpened += 12;

}yearsOpened = yearsOpened + (monthsOpened / 12.0);

TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,

amount, new Date());bankingTransactionDAO.addTransaction(txn);return txn;

}

yea sOpe ed yea sOpe ed ( o t sOpe ed / 0);if (yearsOpened < fromAccount.getRequiredYearsOpen()

|| newBalance < fromAccount.getLimit())throw new MoneyTransferException("Limit exceeded");

break;default:

throw new MoneyTransferException("Unknown overdraft type: "+ fromAccount.getOverdraftPolicy());

}fromAccount.setBalance(newBalance);toAccount setBalance(toAccount getBalance() + amount);

public class Account {public void debit(fAccount fromAccount, double amount) {

double newBalance = getBalance() – amount;switch (getOverdraftPolicy()) {….}toAccount.setBalance(toAccount.getBalance() + amount);

TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,amount, new Date());

bankingTransactionDAO.addTransaction(txn);return txn;

}

}setBalance(newBalance);

}

public void credit(Account toAccount, double amount) { setBalance(getBalance() + amount);

}Extract and move feature envy

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 56

Extract and move feature envy code into data class

Page 57: Building Rich Domain Models

Primitive ObsessionCode uses built-in types instead

public class Account {private Date dateOpened;

}

ypof application classesConsequences:

public class Account {private Date dateOpened;

}

public class MoneyTransferServiceProceduralImplimplements MoneyTransferService {

f fq

Reduces understandabilityLong methodsC d d li ti

public BankingTransaction transfer(String fromAccountId, String toAccountId,double amount) {

Account fromAccount = accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO.findAccount(toAccountId);…

Calendar then = Calendar.getInstance();th tTi (f A t tD t O d())Code duplication

Added complexity

Fix by moving

then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) -then.get(Calendar.YEAR);

int monthsOpened = now.get(Calendar.MONTH) –then.get(Calendar.MONTH);

if (monthsOpened < 0) {Fix by moving data and code into new class

if (monthsOpened < 0) {yearsOpened--;monthsOpened += 12;

}yearsOpened = yearsOpened + (monthsOpened / 12.0);if (yearsOpened < fromAccount.getRequiredYearsOpen()

|| newBalance < fromAccount.getLimit())…}

3/1/2009 57Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 58: Building Rich Domain Models

Replace Data Value with Objectp j

public class Account {private Date dateOpened;

}

public class CalendateDate {private Date dateOpened;Date getDateOpened( return dateOpened; }

}}

public class Account {private CalendateDate dateOpened;

public double getYearsOpen() {Move MethodCalendar then = Calendar.getInstance();then.setTime(dateOpened.getDateOpened());Calendar now = Calendar.getInstance();

double yearsOpened = now.get(Calendar.YEAR) -then.get(Calendar.YEAR);

int monthsOpened = now.get(Calendar.MONTH) –

public class Account {private CalendateDate dateOpened;

public double getYearsOpen() {return dateOpened.getYearsOpened();

}then.get(Calendar.MONTH);

if (monthsOpened < 0) {yearsOpened--;monthsOpened += 12;

}yearsOpened = yearsOpened +

(monthsOpened / 12.0);t O d

public class CalendateDate {private Date dateOpened;

public double getYearsOpen() {

return yearsOpened;}

Calendar then = Calendar.getInstance();yearsOpened = yearsOpened +

(monthsOpened / 12.0);return yearsOpened;

}

}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 58

}

Page 59: Building Rich Domain Models

Switch StatementsUse of type codes and switch statements instead of polymorphism

public class Account {

public static final int NEVER = 1;of polymorphismKey concepts are represented by type codes instead of classesC

public class MoneyTransferServiceProceduralImpl

p ;public static final int ALLOWED = 2;private int overdraftPolicy ;…

Consequences:Longer methodsPoor maintainability caused by code duplicationIncreased code complexity

public class MoneyTransferServiceProceduralImplimplements MoneyTransferService {

public BankingTransaction transfer(String fromAccountId, String toAccountId,double amount) {

Increased code complexityFix by introducing class hierarchy and moving each part of switch statement into a

…switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:

…break;

case Account ALLOWED:statement into a overriding method

case Account.ALLOWED:…

default:…

}…

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 59

}

Page 60: Building Rich Domain Models

Replace Type Code with Strategyp yp gypublic class Account {

public static final int NEVER = 1;public static final int ALLOWED 2;

public class MoneyTransferServiceProceduralImpl … {

public BankingTransaction transfer(String fromAccountId, String toAccountId,double amount) {

public static final int ALLOWED = 2;

private OverdraftPolicy overdraftPolicy;…

) {…switch (fromAccount.getOverdraftPolicy().getTypeCode()) {case Account.NEVER:

…b kbreak;

case Account.ALLOWED:…

default:…

}}…

}

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 60

Page 61: Building Rich Domain Models

Replace Conditional with Polymorphismy p

public class MoneyTransferServiceProceduralImpl … {

public BankingTransaction transfer( ) {

Extract/Move Method public BankingTransaction transfer(…) {

…fromAccount.getOverdraftPolicy().beforeDebitCheck(…);…

}

Method

Push Down& simplify

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 61

Page 62: Building Rich Domain Models

Data clumpsp

Multiple fields or bli l A t {Multiple fields or method parameters that belong together

public class Account {

public static final int NEVER = 1;public static final int ALLOWED = 2;belong together

Consequences:Long methodsD li ti

private int id;private double balance;private String accountId;private Date dateOpened;Duplication

Fix by:Moving fields into th i l

private Date dateOpened;

private int overdraftPolicy;private double requiredYearsOpen;private double limit;their own class

Eliminate resulting Feature Envy

private double limit;

Account() {}

}}

3/1/2009 62Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 63: Building Rich Domain Models

Extract Classpublic class Account {

public static final int NEVER = 1;public static final int NEVER = 1;public static final int ALLOWED = 2;

private int id;private double balance;Move Fieldprivate String accountId;private Date dateOpened;

private OverdraftPolicy overdraftPolicy;

Account() {}

}

public class OverdraftPolicy {pub c c ass O e d a t o cy {

private int overdraftPolicy;private double requiredYearsOpen;private double limit;

3/1/2009 Copyright (c) 2007 Chris Richardson. All rights reserved. 63

p ;…

Page 64: Building Rich Domain Models

Agendag

Where are the real objects?Overview of the Domain Model patternDomain model building blocksRole of frameworksObstacles to OOEliminating common code smellsEliminating common code smellsRefactoring existing code

3/1/2009 64Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 65: Building Rich Domain Models

Transforming procedural codeg p

Inside every procedural design is a domain model just trying to get outIncrementally transform a procedural design into an OO design

Small, localized changesSomething to do on Monday morning!

3/1/2009 65Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 66: Building Rich Domain Models

DEMODEMO

Refactoring procedural code

3/1/2009 66Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 67: Building Rich Domain Models

Summaryy

A rich domain model:Organizes the business logic as classes with state AND behaviorI i t i bilit d Improves maintainability and testabilityEnabled by POJOs and non invasive Enabled by POJOs and non-invasive frameworks (mostly)Emerges from procedural code by Emerges from procedural code by incremental refactoring

Use it – starting monday!Use it – starting monday!3/1/2009 67Copyright (c) 2007 Chris Richardson. All rights reserved.

Page 68: Building Rich Domain Models

For more information

Buy my book ☺Buy my book ☺

Send email:

[email protected]

Visit my website:Visit my website:

http://www.chrisrichardson.netnet

Talk to me about consulting and trainingg g

3/1/2009 68Copyright (c) 2007 Chris Richardson. All rights reserved.