How to Troubleshoot Apps for the Modern Connected Worker
Building Rich Domain Models
1. Improving Application Design
with a Rich Domain Model
Chris Ri h d
Ch i Richardson
Author of POJOs in Action
Chris Richardson Consulting, Inc
http://www.chrisrichardson.net
p //
3/1/2009 Slide 1
Copyright (c) 2007 Chris Richardson. All rights reserved.
2. Overall presentation g
p goal
Learn how to improve application
design by using truly object-
g y g y j
oriented business logic
3/1/2009 2
Copyright (c) 2007 Chris Richardson. All rights reserved.
3. About Chris
Grew up in England
Live in Oakland, CA
Over twenty years of software
development experience
Building object-oriented
software since 1986
Using Java since 1996
Using J2EE since 1999
Author of POJOs in Action
Speaker at JavaOne, JavaPolis,
NFJS, JUGs, ….
Chair of the eBIG Java SIG in
Oakland (www.ebig.org)
Run a consulting and training
company that helps
organizations build better
software faster
3/1/2009 3
Copyright (c) 2007 Chris Richardson. All rights reserved.
4. Agenda
g
Where are the real objects?
Wh th l bj t ?
Overview of the Domain Model pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
Refactoring existing code
3/1/2009 4
Copyright (c) 2007 Chris Richardson. All rights reserved.
5. Objects in LISP (
j (1987-1993)
)
(defclass Account ()
((account-id :accessor account-id :initarg :account-id)
State
(balance :accessor account-balance :initarg :balance))
+
)
Behavior
(defmethod debit ((Account account) amount)
(decf (account-balance account) amount))
(defmethod credit ((Account account) amount)
(incf (account balance account) amount))
(account-balance
CL-USER 5 > (setq a (make-instance 'account :account-id quot;abc123quot; :balance 10.0))
#<ACCOUNT 200C05AF>
CL-USER 6 > (describe a)
#<ACCOUNT 200C05AF> is an ACCOUNT
ACCOUNT-ID quot;abc123quot;
BALANCE 10.0
CL-USER
CL USER 7 > (debit a 5)
5.0
CL-USER 8 > (describe a)
#<ACCOUNT 200C05AF> is an ACCOUNT
ACCOUNT-ID
ACCOUNT ID quot; b 123quot;
quot;abc123quot;
BALANCE 5.0
3/1/2009 5
Copyright (c) 2007 Chris Richardson. All rights reserved.
6. Objects in C++ (
j (1993-1996)
)
#ifndef ACCOUNT_H_
#define ACCOUNT_H_
class Account {
public:
Account(char* account_id, double balance);
void debit(double amount);
void credit(double amount);
double getBalance();
private:
char* account_id;
State
double balance;
};
+
#endif /*ACCOUNT_H_*/
Behavior
#include quot;Account.hquot;
#incl de quot;Acco nt hquot;
Account::Account(char* account_id, double balance) {
…
}
void Account::debit (double amount) {
balance -= amount;
bl
}
void Account::credit(double amount) {
balance += amount;
}
double Account::getBalance() { return balance; }
3/1/2009 6
Copyright (c) 2007 Chris Richardson. All rights reserved.
7. Objects in Java (
j (1996-1999)
)
public class Account {
private int id;
private double balance;
private OverdraftPolicy overdraftPolicy;
State
private String accountId;
+
private CalendarDate dateOpened;
Behavior
Account() {
}
p
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);
d ftP li ft D bitA ti (thi i i lB l Bl )
}
public void credit(double amount) {
assert amount > 0;
balance += amount;
}
3/1/2009 7
Copyright (c) 2007 Chris Richardson. All rights reserved.
8. EJB objects (
j (1999- ?)
)
Applications were still built
pp
from objects
But those objects were very
different …
3/1/2009 8
Copyright (c) 2007 Chris Richardson. All rights reserved.
9. Example Banking UI
p g
3/1/2009 9
Copyright (c) 2007 Chris Richardson. All rights reserved.
10. Example p
p procedural design
g
3/1/2009 10
Copyright (c) 2007 Chris Richardson. All rights reserved.
11. Example p
p procedural code
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService { public class Account {
p
public BankingTransaction transfer(String fromAccountId, String toAccountId,
g ( g , g , p
public static final int NEVER = 1;
;
double amount) { public static final int ALLOWED = 2;
Account fromAccount = accountDAO.findAccount(fromAccountId);
Account toAccount = accountDAO.findAccount(toAccountId); private int id;
assert amount > 0; private double balance;
double newBalance = fromAccount.getBalance() - amount; private int overdraftPolicy;
switch (fromAccount.getOverdraftPolicy()) { private String accountId;
Behavior State
case Account.NEVER: private Date dateOpened;
if (newBalance < 0) private double requiredYearsOpen;
throw new MoneyTransferException(quot;In sufficient fundsquot;); private double limit;
break;
case Account.ALLOWED: Account() {}
Calendar then = Calendar.getInstance();
then.setTime(fromAccount.getDateOpened());
Calendar now = Calendar.getInstance(); public Account(String accountId, double balance, int
overdraftPolicy,
double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR); Date dateOpened, double requiredYearsOpen,
int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH); double limit)
if (monthsOpened < 0) { {….. }
yearsOpened--;
O d
monthsOpened += 12; public int getId() {return id;}
}
yearsOpened = yearsOpened + (monthsOpened / 12.0); public String getAccountId() {return accountId;}
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit()) public void setBalance(double balance) { this.balance = balance; }
throw new MoneyTransferException(quot;Limit exceededquot;);
break; public double getBalance() { return balance; }
default:
throw new MoneyTransferException(quot;Unknown overdraft type: quot;
MoneyTransferException( Unknown public int getOverdraftPolicy() { return overdraftPolicy; }
+ fromAccount.getOverdraftPolicy());
public Date getDateOpened() { return dateOpened; }
}
fromAccount.setBalance(newBalance); public double getRequiredYearsOpen() {
toAccount.setBalance(toAccount.getBalance() + amount); return requiredYearsOpen; }
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date()); public double getLimit() {return limit; }
bankingTransactionDAO.addTransaction(txn); }
return txn;
}
3/1/2009 11
Copyright (c) 2007 Chris Richardson. All rights reserved.
12. Why write code like this?
y
EJB made writing object-oriented code
difficult/impossible
diffi lt/i ibl
Implementing new functionality is easy
Add a new transaction script
i i
Add code to a new transaction script
Manipulating relational data is easier
Distribution is easier
No
N need to do any real design, e.g.
dt d ld i
Create new classes
Determine responsibilities
3/1/2009 12
Copyright (c) 2007 Chris Richardson. All rights reserved.
13. Unable to handle complexity
p y
Works well for simple business logic
E.g.
E g the example wasn’t that bad
wasn t
But with complex business logic:
Large transaction scripts: 100s/1000s LOC
Difficult/impossible to understand, test, and
maintain
What s
What’s worse: business logic has a habit
of growing
New requirements ⇒ Add a few more lines
to the transaction script
Many new requirements ⇒ big mess
Soon or later you end up with
unmaintainable code
3/1/2009 13
Copyright (c) 2007 Chris Richardson. All rights reserved.
14. The legacy of EJB
gy
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 14
Copyright (c) 2007 Chris Richardson. All rights reserved.
15. Today – rich domain models are
growing in popularity
POJOs
Plain Old Java Objects
Leverage OO features of Java
O/R mapping frameworks for
persisting POJOs:
Hibernate
Java Persistence API
…
Spring AOP and A
Si d AspectJ f
tJ for
handling cross-cutting
concerns:
Transaction management
Security
S it
Logging
Auditing
…
3/1/2009 15
Copyright (c) 2007 Chris Richardson. All rights reserved.
16. Agenda
g
Where are the real objects?
Overview of the Domain Model
pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
g
Refactoring existing code
3/1/2009 16
Copyright (c) 2007 Chris Richardson. All rights reserved.
17. Using the Domain Model Pattern
g
Business logic spread amongst a
Bi li d t
collection of classes
Many classes correspond to real world
concepts: Order, Customer, …
Many classes are true objects having
both:
State – fields
Behavior – methods that act on the state
3/1/2009 17
Copyright (c) 2007 Chris Richardson. All rights reserved.
18. Procedural versus OO
Presentation Tier
Presentation Tier
Business Tier
Business Tier
Transaction
Scripts
Facade
(Session
Beans)
Domain Model
Data
Objects
Data Access Tier
Data Access Tier
3/1/2009 18
Copyright (c) 2007 Chris Richardson. All rights reserved.
19. An example domain model
p
Web Tier
Behavior
Business Tier
MoneyTransferService
BankingTransaction transfer(fromId, toId, amount)
from
Account
Banking Account
BankingTransaction
Transaction Repository
Repository
balance
to
amount findAccount(id)
addTransaction(…) debit(amount)
date
credit(amount)
Explicit
<<interface>>
OverdraftPolicy
Representation
State +
of key
Behavior Limited
conceptst
NoOverdraft Overdraft
Policy
limit
3/1/2009 19
Copyright (c) 2007 Chris Richardson. All rights reserved.
21. Benefits of the Domain Model
Pattern
Improved maintainability
The design reflects reality
Key domain classes are represented by
classes
The design is more modular
Improved testability
Small classes that can be tested in isolation
Improved reusability
Classes can be used in other applications
Building a domain model
Creates shared understanding
Develops an ubiquitous language
p q gg
3/1/2009 21
Copyright (c) 2007 Chris Richardson. All rights reserved.
22. Q
Quantifiably simpler code
y p
Procedural – few longer more
few, longer, Object-oriented more, simpler
Object oriented – more simpler,
complex methods shorter methods
3/1/2009 22
Copyright (c) 2007 Chris Richardson. All rights reserved.
23. Drawbacks of the Domain Model
p
pattern
Requires object-oriented design skills
Works best if domain model is
transparently “mappable” to the data
E.g. nice database schema
Ugly schemas and data stored in other
applications is a challenge
l h ll
3/1/2009 23
Copyright (c) 2007 Chris Richardson. All rights reserved.
24. When to use it
The business logic is reasonably
complex or you anticipate that it will
be
You have the skills to design one
You can either:
Use an ORM framework
Invest in writing a data access
framework
3/1/2009 24
Copyright (c) 2007 Chris Richardson. All rights reserved.
25. Agenda
g
Where are the real objects?
Wh th l bj t ?
Overview of the Domain Model pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
Refactoring existing code
3/1/2009 25
Copyright (c) 2007 Chris Richardson. All rights reserved.
26. Domain model building blocks
g
Roles aka
stereotypes
Benefits of roles:
Guide design
Help name
objects
bj t
Aid understanding
Roles (from
Domain-Driven
Design)
3/1/2009 26
Copyright (c) 2007 Chris Richardson. All rights reserved.
27. Entity
y
Objects with a public class Account {
distinct identity private int id;
Typically correspond
private double balance;
to real world
private OverdraftPolicy overdraftPolicy;
private String accountId;
concepts private CalendarDate dateOpened;
Almost always Account() {
persistent
}
public void debit(double amount) {
Encapsulate state and assert amount > 0;
double originalBalance = balance;
behavior double newBalance = balance - amount;
overdraftPolicy.beforeDebitCheck(this, originalBalance, newBalance);
balance = newBalance;
Often modal ⇒ call overdraftPolicy.afterDebitAction(this, originalBalance, newBalance);
}
methods in a public void credit(double amount) {
particular order assert amount > 0;
balance += amount;
}
3/1/2009 27
Copyright (c) 2007 Chris Richardson. All rights reserved.
28. Value Objects
j
Objects that are
public class CalendarDate {
defined by the values private Date date;
of their attributes CalendarDate() {
}
Two instances with public CalendarDate(Date date) {
identical values can be
this.date = date;
}
used i t
d interchangeably
h bl public Date getDate() {
return date;
Two flavors }
Persistent – parts of public double getYearsOpen() {
Calendar then = Calendar.getInstance();
entities then.setTime(date);
then setTime(date);
Calendar now = Calendar.getInstance();
Transient – intermediate
values int yearsOpened = now.get(Calendar.YEAR) –
then.get(Calendar.YEAR);
Ideally immutable int monthsOpened = now.get(Calendar.MONTH) -
then.get(Calendar.MONTH);
Often missing from
Oft ii f
if (monthsOpened < 0) {
yearsOpened--;
procedural code –
monthsOpened += 12;
}
Primitive Obsession return yearsOpened + (monthsOpened/12.0);
code smell }
}
3/1/2009 28
Copyright (c) 2007 Chris Richardson. All rights reserved.
29. More examples of Value Objects
p j
public class User {
public class User {
private int id;
private String firstName;
private PersonName name;
p
private String lastName;
g ;
private UserId login;
private String login;
private Password password;
private String password;
…
…
}
}
public class Password implements Serializable {
private String passwordString;
public class PersonName { public Password(String passwordString) {
private String firstName; this.passwordString = passwordString == null ? null : passwordString.trim();
private String lastName; }
PersonName() { …
} @Override
public String t St i () {
bli St i toString()
public PersonName(String firstName, return new ToStringBuilder(this).append(quot;passwordquot;, quot;****quot;).toString();
String lastName) { }
this.firstName = firstName;
this.lastName = lastName;
}
3/1/2009 29
Copyright (c) 2007 Chris Richardson. All rights reserved.
30. Aggregates
gg g
A cluster of related
entities and values
Behaves as a unit
Has
H a root t
Has a boundary
Objects outside the
aggregate can only
reference the root
Deleting the root
removes
everything
3/1/2009 30
Copyright (c) 2007 Chris Richardson. All rights reserved.
31. Repositories
p
public interface AccountRepository {
Manages a collection of
objects Account findAccount(String accountId);
void addAccount(Account account);
Provides methods for: }
Adding an object
Finding object or objects
Deleting objects public class HibernateAccountRepository implements AccountRepository {
Consists of an interface private HibernateTemplate hibernateTemplate;
and an implementation public HibernateAccountRepository(HibernateTemplate template) {
hibernateTemplate = template;
class
l }
Encapsulates database public void addAccount(Account account) {
hibernateTemplate.save(account);
access mechanism }
Keeps the ORM public Account findAccount(final String accountId) {
return (Account) DataAccessUtils.uniqueResult(hibernateTemplate
framework out of the .findByNamedQueryAndNamedParam(
domain model
quot;Account.findAccountByAccountIdquot;, quot;accountIdquot;,
accountId));
}
Similar to a DAO }
3/1/2009 31
Copyright (c) 2007 Chris Richardson. All rights reserved.
32. Services
public interface MoneyTransferService {
Implements logic that BankingTransaction transfer(String fromAccountId,
cannot be put in a single String toAccountId, double amount);
entity }
Not persistent
p
Consists of an interface public class MoneyTransferServiceImpl implements MoneyTransferService
{
and an implementation private final AccountRepository accountRepository;
class private final BankingTransactionRepository
Service method usually: bankingTransactionRepository;
public MoneyTransferServiceImpl(AccountRepository accountRepository,
Invoked (indirectly) by BankingTransactionRepository bankingTransactionRepository) {
presentation tier this.accountRepository = accountRepository;
this.bankingTransactionRepository = bankingTransactionRepository;
Invokes one or more }
repositories public BankingTransaction transfer(String fromAccountId,
String toAccountId, double amount) {
Invokes one or more …
entities }
Keep them thin }
3/1/2009 32
Copyright (c) 2007 Chris Richardson. All rights reserved.
33. Factories
Use when a constructor is insufficient
Encapsulates complex object creation
E l l bj i
logic
Handles varying products
Different kinds of factories
Factory classes
Factory methods
Example: OrderFactory
p y
Creates Order from a shopping cart
Adds line items
3/1/2009 33
Copyright (c) 2007 Chris Richardson. All rights reserved.
34. Agenda
g
Where are the real objects?
Wh th l bj t ?
Overview of the Domain Model pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
Refactoring existing code
3/1/2009 34
Copyright (c) 2007 Chris Richardson. All rights reserved.
35. Use the POJO programming model
pg g
Your domain model might outlive
infrastructure frameworks ⇒ Minimize
dependencies on them
POJO = Plain Old Java Object
Don't implement any infrastructure
interfaces
Don't call infrastructure APIs
No infrastructure framework
annotations?
3/1/2009 Slide 35
Copyright (c) 2007 Chris Richardson. All rights reserved.
36. Use dependency injection
p yj
Spring instantiates and wires together components:
Services, factories and repositories
S i fti d it i
Dependency injection into entities
One option is @Configurable but it’s not POJO
Use Hibernate Interceptor + manual injection instead?
Benefits:
Decouples components from one another and the
infrastructure
Improves testability
<beans>
<bean id=quot;accountServicequot;
class=quot;net.chris...domain.AccountServiceImplquot;>
public AccountServiceImpl(AccountDao accountDao, <constructor-arg ref=quot;accountDaoquot;/>
<constructor-arg ref=quot;bankingTransactionDaoquot;/>
BankingTransactionDao bankingTransactionDao) {
</bean>
this.accountDAO = accountDao;
this.bankingTransactionDAO = bankingTransactionDao; …
} </beans>
3/1/2009 Slide 36
Copyright (c) 2007 Chris Richardson. All rights reserved.
37. Use Aspect-Oriented Programming
p g g
Spring AOP for service-level
crosscutting concerns:
E.g. transaction
management, security,
logging etc.
AspectJ for entity and value
object crosscutting concerns
E.g. tracking changes to
fields
But AJC/Load-time weaving
has a cost
Benefits
Decouples code from
infrastructure
Improves modularity
3/1/2009 37
Copyright (c) 2007 Chris Richardson. All rights reserved.
38. Use object/relational mapping
j / pp g
Persisting objects
g j <class name=quot;Accountquot;
table BANK_ACCOUNT
table=quot;BANK ACCOUNTquot; >
with JDBC is usually
<id name=quot;idquot; column=quot;ACCOUNT_IDquot;>
<generator class=quot;nativequot; />
</id>
too much work <property name=quot;balancequot; />
<property name=quot;accountIdquot; />
Implement DAOs
<property name=quot;dateOpenedquot; />
<many-to-one name=quot;overdraftPolicyquot; />
</class>
with Spring ORM
Benefits public class HibernateAccountDao
implements AccountDao {
private HibernateTemplate hibernateTemplate;
Less code public HibernateAccountDao(HibernateTemplate
template) {
Simpler code
Si l d this.hibernateTemplate = template;
}
Improved testability public void addAccount(Account account) {
hibernateTemplate.save(account);
}
…
}
3/1/2009 Slide 38
Copyright (c) 2007 Chris Richardson. All rights reserved.
39. Agenda
g
Where are the real objects?
Overview of the Domain Model
pattern
Domain model building blocks
Role of frameworks
Obstacles to good OO design
Eliminating common code smells
Refactoring existing code
3/1/2009 Slide 39
Copyright (c) 2007 Chris Richardson. All rights reserved.
40. Web frameworks need Java Beans
Web frameworks can bind request
parameters directly to domain objects
Easy creation of domain objects
Easy editing of detached domain objects
asy ed g o de ac ed do a objec s
1. Load object graph from database
2. Store object graph in HttpSession
3.
3 Bind Http parameters to object's properties
4. Reattach object graph and update the database
A lot less code
But domain objects must be JavaBeans:
Public default constructor
JavaBean-style setters
Ja aBean st le sette s
3/1/2009 Slide 40
Copyright (c) 2007 Chris Richardson. All rights reserved.
41. The trouble with setters
public class Address {
Poor encapsulation private String street1;
private String street2;
Objects are now exposing public String getStreet1() {...};
their internal state public void setStreet1(String street1) {...}
…
No immutable objects }
Increases code complexity public class Order {
Makes it more error-prone private Address deliveryAddress;
Developers might b
D l i ht bypass public void updateDeliveryAddress(
Address deliveryAddress) {
business method ⇒ Bugs, // validate before changing
p
poor design
g
…
}
public Address getAddress() {…}
…
}
order.getAddress().setStreet1(quot;Bad!quot;);
3/1/2009 Slide 41
Copyright (c) 2007 Chris Richardson. All rights reserved.
42. How to cope
p
Live with it:
It's often not that bad
But be vigilant for code bypassing
business methods
bi th d
Use DTOs
Hides domain objects from web tier
But you have to write more code
Encourage the development of better
frameworks
3/1/2009 Slide 42
Copyright (c) 2007 Chris Richardson. All rights reserved.
43. More on web binding
g
Web frameworks can bind to
properties of nested objects, e.g.
order.deliveryAddress.street1
But some frameworks require that
intermediate objects to be non-null
This can impact the domain model
3/1/2009 Slide 43
Copyright (c) 2007 Chris Richardson. All rights reserved.
44. Using ORM frameworks
g
ORM frameworks can access private fields
No setters (or g
( getters) required ☺
)q
But require:
Default constructor
Non-final fields
id fields
Subtle changes:
Collection fields: null ⇒ empty
py
Embedded objects with null fields ⇒ null reference
Objects sometimes require equals()/hashCode()
Proxy-based lazy loading:
Don't access fields outside of instance ⇒ affects
equals() and hashCode()
Can affect object identity: this !=
proxyToSameObjectAsThis
3/1/2009 Slide 44
Copyright (c) 2007 Chris Richardson. All rights reserved.
45. Performance tuning changes the
design
g
Different web requests DisplayProject DisplayProject
Details ForReview
display different object Controller Controller
graphs
Display project details
ProjectCoordinator
getProject(projectId)
Display project for
approval or rejection ProjectRepository
Improve performance hibernateTemplate.get()
findProject(projectId)
by loading object
graphs using optimized
queries (e.g. fetch ProjectCoordinator
join) getProjectDetails(projectId)
getProjectForReview(projectId)
Therefore we need
multiple Dao (and
from Project p
ProjectRepository
inner join fetch p.operations
Service) methods
findProjectDetails(projectId)
findProjectForReview(projectId)
from Project p
inner join fetch p.operations
Inner join fetch p.createdBy
3/1/2009 Slide 45
Copyright (c) 2007 Chris Richardson. All rights reserved.
46. Untangling what to load from the
code
Java Data Objects (JDO) has fetch
groups
Declarative specification of the object graph
to load
Fields and related objects
Flow:
Web tier
W b ti configures f t h groups
fi fetch
Calls ProjectCoordinator.get()
O/RM loads object graph specified by active
fetch groups
Look for this feature in JPA
implementations th t evolved f
i l t ti that l d from JDO
3/1/2009 Slide 46
Copyright (c) 2007 Chris Richardson. All rights reserved.
47. Agenda
g
Where are the real objects?
Wh th l bj t ?
Overview of the Domain Model pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
Refactoring existing code
3/1/2009 47
Copyright (c) 2007 Chris Richardson. All rights reserved.
48. Overview of code smells
Code smell = something
about the code that does not
seem right
Impacts ease of
development and testing
Some are non-OOD
Some are the consequences
of non-OOD
3/1/2009 48
Copyright (c) 2007 Chris Richardson. All rights reserved.
49. Refactoring – the cure for stinky
code
Refactoring:
Systematic way to
restructure the
code
Without changing
behavior
bh i
Essential
cleanups for
decaying code
3/1/2009 49
Copyright (c) 2007 Chris Richardson. All rights reserved.
50. Basic refactorings
g
Extract Method
Eliminates long methods
Move Method
Move a method to a
different class (field or
parameter)
Moves method to where
the data is
Push Down
Move a method into
subclasses
Optionally leave an
abstract method behind
Part of eliminating
conditional logic
…
3/1/2009 50
Copyright (c) 2007 Chris Richardson. All rights reserved.
51. Compound refactorings
p g
A sequence of simpler refactorings
Compose method
Apply Extract Method repeatedly
Use to replace long method with more readable
shorter methods
Replace Type Code With Strategy
Define GOF Strategy class for each type code
Replace C diti
Rl Conditional With P l
l Polymorphism
hi
Turn into part of a switch statement into an
overriding method in a subclass
Replace D
Rl Data Value with Object
Vl i h Obj
Move field into it’s own class
Eliminates Primitive Obsession
3/1/2009 51
Copyright (c) 2007 Chris Richardson. All rights reserved.
52. Long method
g
Methods should be
short p
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
y p p y
public BankingTransaction transfer(String fromAccountId, String toAccountId,
double amount) {
But business logic is Account fromAccount = accountDAO.findAccount(fromAccountId);
Account toAccount = accountDAO.findAccount(toAccountId);
assert amount > 0;
concentrated in the
double newBalance = fromAccount.getBalance() - amount;
switch (fromAccount.getOverdraftPolicy()) {
case Account.NEVER:
services ⇒ long
if (newBalance < 0)
throw new MoneyTransferException(quot;In sufficient fundsquot;);
MoneyTransferException( In funds );
break;
methods
case Account.ALLOWED:
Calendar then = Calendar.getInstance();
then.setTime(fromAccount.getDateOpened());
Calendar now = Calendar.getInstance();
Long methods are double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
difficult to:
if (monthsOpened < 0) {
yearsOpened--;
monthsOpened + 12
th O d += 12;
}
Read and understand
yearsOpened = yearsOpened + (monthsOpened / 12.0);
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
throw new MoneyTransferException(quot;Limit exceededquot;);
Maintain break;
default:
throw new MoneyTransferException(quot;Unknown overdraft type: quot;
Test
+ fromAccount.getOverdraftPolicy());
}
Fix:
fromAccount.setBalance(newBalance);
toAccount.setBalance(toAccount.getBalance() + amount);
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date());
Splitting into smaller
bankingTransactionDAO.addTransaction(txn);
return txn;
}
methods
3/1/2009 52
Copyright (c) 2007 Chris Richardson. All rights reserved.
53. Compose Method Refactoring
p g
public class MoneyTransferServiceProceduralImpl implements
MoneyTransferService {
public class MoneyTransferServiceProceduralImpl
public BankingTransaction transfer(String fromAccountId, String
implements MoneyTransferService {
toAccountId,
toAccountId double amount) {
Account fromAccount =
public BankingTransaction transfer(String fromAccountId,
accountDAO.findAccount(fromAccountId);
String toAccountId, double amount) {
Account toAccount = accountDAO.findAccount(toAccountId);
Account fromAccount =
assert amount > 0;
accountDAO.findAccount(fromAccountId);
debit(fromAccount, amount);
Account toAccount =
credit(toAccount, amount);
accountDAO.findAccount(toAccountId);
TransferTransaction txn = new
Extract
Et t
double newBalance = fromAccount getBalance() –
fromAccount.getBalance()
TransferTransaction(fromAccount, toAccount,
amount;
Method amount, new Date());
switch (fromAccount.getOverdraftPolicy()) {
bankingTransactionDAO.addTransaction(txn);
case Account.NEVER:
return txn;
…
}
break;
default:
public void debit(Account fromAccount, double amount) {
…
double newBalance = fromAccount.getBalance() –
}
amount;
fromAccount.setBalance(newBalance);
switch (fromAccount.getOverdraftPolicy()) {
toAccount.setBalance(toAccount.getBalance() +
case Account.NEVER:
amount);
…
TransferTransaction txn = new
break;
TransferTransaction(fromAccount, toAccount,
default:
amount,
amount new Date());
…
bankingTransactionDAO.addTransaction(txn);
}
return txn;
fromAccount.setBalance(newBalance);
}
}
public void credit(Account toAccount, double amount) {
toAccount.setBalance(toAccount.getBalance() +
amount);
}
3/1/2009 53
Copyright (c) 2007 Chris Richardson. All rights reserved.
54. Feature Envy
y
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
public BankingTransaction transfer(String fromAccountId, String toAccountId, double
amount) {
Methods that are Account fromAccount = accountDAO.findAccount(fromAccountId);
Account toAccount = accountDAO.findAccount(toAccountId);
assert amount > 0;
far too interested
double newBalance = fromAccount.getBalance() - amount;
switch (fromAccount.getOverdraftPolicy()) {
case Account.NEVER:
in data belonging
if (newBalance < 0)
throw new MoneyTransferException(quot;In sufficient fundsquot;);
break;
case Account.ALLOWED:
to th
t other classes
l Calendar then = C l d
C l d th Calendar.getInstance();
tI t ()
then.setTime(fromAccount.getDateOpened());
Calendar now = Calendar.getInstance();
Results in:
double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
if (monthsOpened < 0) {
yearsOpened--;
Poor encapsulation monthsOpened += 12;
}
yearsOpened = yearsOpened + (monthsOpened / 12.0);
Long methods
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
throw new MoneyTransferException(quot;Limit exceededquot;);
break;
Fix by moving
default:
throw new MoneyTransferException(quot;Unknown overdraft type: quot;
+ fromAccount.getOverdraftPolicy());
methods to the
th d t th }
fromAccount.setBalance(newBalance);
class that has the
toAccount.setBalance(toAccount.getBalance() + amount);
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date());
bankingTransactionDAO.addTransaction(txn);
data
return txn;
}
3/1/2009 54
Copyright (c) 2007 Chris Richardson. All rights reserved.
55. Data class
public class Account {
public static final int NEVER = 1;
Classes that are public static final int ALLOWED = 2;
private int id;
just getters and private double balance;
private int overdraftPolicy;
private String accountId;
setters private Date dateOpened;
private double requiredYearsOpen;
p
private double limit;
;
No business logic - Account() {}
it’s in the service public Account(String accountId, double balance, int overdraftPolicy,
Date dateOpened, double requiredYearsOpen, double limit)
{….. }
Leads to: public int getId() {return id;}
Feature envy public String getAccountId() {return accountId;}
public void setBalance(double balance) { this.balance = balance; }
Fix by moving
y g public double getBalance() { return balance; }
methods that act
public int getOverdraftPolicy() { return overdraftPolicy; }
public Date getDateOpened() { return dateOpened; }
on data into class public double getRequiredYearsOpen() { return requiredYearsOpen; }
public double getLimit() {return limit; }
}
3/1/2009 55
Copyright (c) 2007 Chris Richardson. All rights reserved.
56. Move Method refactoring
g
public class MoneyTransferServiceProceduralImpl implements
MoneyTransferService {
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
public BankingTransaction transfer(String fromAccountId, String
public BankingTransaction transfer(String fromAccountId, String toAccountId, double
toAccountId,
toAccountId double amount) {
amount) {
Account fromAccount = accountDAO.findAccount(fromAccountId);
Account fromAccount =
Account toAccount = accountDAO.findAccount(toAccountId);
accountDAO.findAccount(fromAccountId);
assert amount > 0;
double newBalance = fromAccount.getBalance() - amount;
Account toAccount = accountDAO.findAccount(toAccountId);
switch (fromAccount.getOverdraftPolicy()) {
assert amount > 0;
case Account.NEVER:
if (newBalance < 0)
fromAccount.debit(amount);
throw new MoneyTransferException(quot;In sufficient fundsquot;);
toAccount.credit(amount);
break;
case Account.ALLOWED:
TransferTransaction txn = new
Calendar then = Calendar.getInstance();
TransferTransaction(fromAccount, toAccount,
then.setTime(fromAccount.getDateOpened());
Calendar now = Calendar.getInstance();
amount, new Date());
bankingTransactionDAO.addTransaction(txn);
double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
return txn;
if (monthsOpened < 0) {
}
yearsOpened--;
monthsOpened += 12;
}
yea sOpe ed
yearsOpened = yearsOpened + (monthsOpened / 12.0);
yea sOpe ed ( o t sOpe ed 0);
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
throw new MoneyTransferException(quot;Limit exceededquot;);
public class Account {
break;
public void debit(fAccount fromAccount, double amount) {
default:
throw new MoneyTransferException(quot;Unknown overdraft type: quot;
double newBalance = getBalance() – amount;
+ fromAccount.getOverdraftPolicy());
switch (getOverdraftPolicy()) {
}
….
fromAccount.setBalance(newBalance);
}
toAccount.setBalance(toAccount.getBalance()
toAccount setBalance(toAccount getBalance() + amount);
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
setBalance(newBalance);
amount, new Date());
}
bankingTransactionDAO.addTransaction(txn);
return txn;
}
public void credit(Account toAccount, double amount) {
setBalance(getBalance() + amount);
}
Extract and move feature envy
code into data class
3/1/2009 56
Copyright (c) 2007 Chris Richardson. All rights reserved.
57. Primitive Obsession
public class Account {
Code uses built- private Date dateOpened;
}
in types instead
yp
of application
public class Account {
private Date dateOpened;
classes
}
public class MoneyTransferServiceProceduralImpl
Consequences:
q implements MoneyTransferService {
public BankingTransaction transfer(String f
f fromAccountId,
Reduces String toAccountId,
double amount) {
understandability Account fromAccount = accountDAO.findAccount(fromAccountId);
Account toAccount = accountDAO.findAccount(toAccountId);
Long methods …
Calendar then = Calendar.getInstance();
Code duplication
C d d li ti then.setTime(fromAccount.getDateOpened());
th tTi (f A t tD t O d())
Calendar now = Calendar.getInstance();
Added double yearsOpened = now.get(Calendar.YEAR) -
complexity then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) –
Fix by moving
then.get(Calendar.MONTH);
if (monthsOpened < 0) {
data and code
yearsOpened--;
monthsOpened += 12;
}
into new class yearsOpened = yearsOpened + (monthsOpened / 12.0);
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
…
}
3/1/2009 57
Copyright (c) 2007 Chris Richardson. All rights reserved.
58. Replace Data Value with Object
p j
public class CalendateDate {
private Date dateOpened;
public class Account { Date getDateOpened( return dateOpened; }
private Date dateOpened; }
}
public class Account {
private CalendateDate dateOpened;
Move Method public double getYearsOpen() {
Calendar then = Calendar.getInstance();
then.setTime(dateOpened.getDateOpened());
public class Account {
Calendar now = Calendar.getInstance();
private CalendateDate dateOpened;
double yearsOpened = now.get(Calendar.YEAR) -
public double getYearsOpen() {
then.get(Calendar.YEAR);
return dateOpened.getYearsOpened();
int monthsOpened = now.get(Calendar.MONTH) –
}
then.get(Calendar.MONTH);
if (monthsOpened < 0) {
yearsOpened--;
public class CalendateDate {
monthsOpened += 12;
private Date dateOpened;
}
yearsOpened = yearsOpened +
public double getYearsOpen() {
(monthsOpened / 12.0);
return yearsOpened;
t O d
Calendar then = Calendar.getInstance();
}
yearsOpened = yearsOpened +
(monthsOpened / 12.0);
return yearsOpened;
}
}
3/1/2009 58
Copyright (c) 2007 Chris Richardson. All rights reserved.
59. Switch Statements
Use of type codes and public class Account {
switch statements instead p
public static final int NEVER = 1;
;
of polymorphism public static final int ALLOWED = 2;
Key concepts are private int overdraftPolicy ;
…
represented by type codes
instead of classes
public class MoneyTransferServiceProceduralImpl
Consequences:
C implements MoneyTransferService {
Longer methods
public BankingTransaction transfer(String
Poor maintainability caused
fromAccountId, String toAccountId,
by code duplication
double amount) {
Increased code complexity …
Fix by introducing class switch (fromAccount.getOverdraftPolicy()) {
case Account.NEVER:
hierarchy and moving …
each part of switch break;
statement into a case Account ALLOWED:
Account.ALLOWED:
overriding method …
default:
…
}
…
}
3/1/2009 59
Copyright (c) 2007 Chris Richardson. All rights reserved.
60. Replace Type Code with Strategy
p yp gy
public class Account {
public static final int NEVER = 1;
public static final int ALLOWED = 2;
public class MoneyTransferServiceProceduralImpl … {
private OverdraftPolicy overdraftPolicy;
… public BankingTransaction transfer(String fromAccountId,
String toAccountId,
double amount) {
)
…
switch (fromAccount.getOverdraftPolicy().getTypeCode())
{
case Account.NEVER:
…
break;
b k
case Account.ALLOWED:
…
default:
…
}
…
}
3/1/2009 60
Copyright (c) 2007 Chris Richardson. All rights reserved.
61. Replace Conditional with
Polymorphism
y p
Extract/Move public class MoneyTransferServiceProceduralImpl … {
Method public BankingTransaction transfer( ) {
transfer(…)
…
fromAccount.getOverdraftPolicy().beforeDebitCheck(…);
…
}
Push Down
& simplify
3/1/2009 61
Copyright (c) 2007 Chris Richardson. All rights reserved.
62. Data clumps
p
Multiple fields or public class A
bli l Account {
t
method
parameters that public static final int NEVER = 1;
public static final int ALLOWED = 2;
belong together
Consequences: private int id;
private double balance;
Long methods private String accountId;
Duplication
D li ti private Date dateOpened;
Fix by: private int overdraftPolicy;
Moving fields into private double requiredYearsOpen;
their
th i own class
l private double limit;
Eliminate resulting Account() {}
Feature Envy
}
3/1/2009 62
Copyright (c) 2007 Chris Richardson. All rights reserved.
63. Extract Class
public class Account {
public static final int NEVER = 1;
public static final int ALLOWED = 2;
private int id;
Move Field private double balance;
private String accountId;
private Date dateOpened;
private OverdraftPolicy overdraftPolicy;
Account() {}
}
pub c c ass O e d a t o cy
public class OverdraftPolicy {
private int overdraftPolicy;
private double requiredYearsOpen;
p
private double limit;
;
…
3/1/2009 63
Copyright (c) 2007 Chris Richardson. All rights reserved.
64. Agenda
g
Where are the real objects?
Overview of the Domain Model
pattern
Domain model building blocks
Role of frameworks
Obstacles to OO
Eliminating common code smells
Refactoring existing code
3/1/2009 64
Copyright (c) 2007 Chris Richardson. All rights reserved.
65. Transforming p
g procedural code
Inside every procedural design is a
domain model just trying to get out
Incrementally transform a procedural
design into an OO design
Small, localized changes
Something to do on Monday morning!
3/1/2009 65
Copyright (c) 2007 Chris Richardson. All rights reserved.
67. Summary
y
A rich domain model:
Organizes the business logic as
classes with state AND behavior
Improves maintainability and
I i t i bilit d
testability
Enabled by POJOs and non invasive
non-invasive
frameworks (mostly)
Emerges from procedural code by
incremental refactoring
Use it – starting monday!
3/1/2009 67
Copyright (c) 2007 Chris Richardson. All rights reserved.
68. For more information
Buy my book ☺
Send email:
chris@chrisrichardson.net
Visit my website:
http://www.chrisrichardson.
net
Talk to me about
consulting and training
g g
3/1/2009 68
Copyright (c) 2007 Chris Richardson. All rights reserved.