A classic from 2007. This is a presentationthat I gave at SpringOne in Antwerp, Belgium. It describes show to improve application design by using a rich domain model
2. Improving Applications Design with a
Rich Domain Model
Chris Richardson
Author of POJOs in Action
www.chrisrichardson.net
3. About Chris
Grew up in England
Live in Oakland, CA
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)
(www.ebig.org)
Run a consulting and training
company that helps organizations
build better software faster
4. Overall presentation goal
Learn how to improve application design
with truly object-oriented business logic
5. Agenda
The
Th ups and d
d downs of OO design
f d i
Overview of the Domain Model pattern
Domain model building blocks
Common code smells
Refactoring existing code
6. Designing business logic
Spring promotes good design practices:
– Dependency injection for loose coupling
– AOP for handling cross cutting concerns
But you must decide how to structure your
business logic:
– Domain Model pattern – object-oriented
– Transaction Script pattern – procedural
Choice of pattern impacts ease of:
– Development testing maintainability ….
Development, testing, maintainability,
7. Lots of procedural Java code
Java is an object oriented language
object-oriented
AND
Object-oriented design is a better way to tackle complexity
YET
Many complex enterprise Java applications are written in a
procedural style
10. Example 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) throws MoneyTransferException { 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;
case Account.NEVER: private Date dateOpened;
if (newBalance < 0) private double requiredYearsOpen;
throw new MoneyTransferException("In sufficient funds"); 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("Limit exceeded");
break; public double getBalance() { return balance; }
default:
throw new MoneyTransferException( Unknown overdraft type: "
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;
}
11. A seductive programming style
Implementing new f
I l ti functionality i easy
ti lit is
– Add a new transaction script
– Add code to a new transaction script
No need to do any real design, e.g.
– Create new classes
– Determine responsibilities
p
12. Unable to handle complexity
Works well for simple business logic
– E.g. the example wasn’t that bad
But with complex business logic:
– Large transaction scripts: 100s/1000s LOC
– Difficult/impossible to understand, test, and maintain
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
13. Today – OO is growing in popularity
POJOs
– Plain Old Java Objects
– Leverage OO features of Java
O/R mapping frameworks for
persisting POJO
i ti POJOs:
– Hibernate
– Java Persistence API
– …
Spring AOP and AspectJ for
handling cross-cutting
concerns:
– Transaction management
– Security
– Logging
– Auditing
– …
14. Agenda
The
Th ups and d
d downs of OO design
f d i
Overview of the Domain Model pattern
Domain model building blocks
Common code smells
Refactoring existing code
15. Using the Domain Model Pattern
Business l i spread amongst a collection of
B i logic d t ll ti f
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
19. Benefits of the Domain Model Pattern
Improved maintainability
– The design reflects reality
– 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
g
– Creates shared understanding
– Develops an ubiquitous language
21. Drawbacks of the Domain Model pattern
Requires object-oriented d i skills
R i bj t i t d design kill
Requires domain model to be
transparently “mappable” to the data
– E.g. nice database schema
– Ugly schemas and data stored in other
applications is a challenge
22. When to use it
The b i
Th business l i i reasonably complex
logic is bl l
or you anticipate that it will be
You have the skills to design one
You can use an ORM framework
23. Agenda
The
Th ups and d
d downs of OO design
f d i
Overview of the Domain Model pattern
Domain model building blocks
Common code smells
Refactoring existing code
24. Domain model building blocks
Roles k
R l aka
stereotypes
Benefits of roles:
– Guide design
– Help name objects
– Aid understanding
Roles (from Domain-
Domain
Driven Design)
25. Entity
public class Account {
Objects ith di ti t
Obj t with a distinct private int id;
identity private double balance;
private OverdraftPolicy overdraftPolicy;
Typically correspond private String accountId;
private CalendarDate dateOpened;
to real world concepts Account() {
}
Almost always public void debit(double amount) throws MoneyTransferException {
assert amount > 0;
double originalBalance = balance;
persistent double newBalance = balance - amount;
overdraftPolicy.beforeDebitCheck(this, originalBalance, newBalance);
balance = newBalance;
overdraftPolicy.afterDebitAction(this, originalBalance, newBalance);
}
public void credit(double amount) {
assert amount > 0;
balance += amount;
}
26. Value Objects
Objects that are public class CalendarDate {
private Date date;
defined by the values CalendarDate() {
}
of their attributes public CalendarDate(Date date) {
this.date = date;
Two instances with }
public Date getDate() {
identical values can }
return date;
be
b used d public double getYearsOpen() {
Calendar then = Calendar.getInstance();
then.setTime(date);
interchangeably Calendar now = Calendar.getInstance();
int yearsOpened = now.get(Calendar.YEAR) –
Often immutable and then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) -
then.get(Calendar.MONTH);
persistent if (monthsOpened < 0) {
yearsOpened--;
monthsOpened += 12;
}
Part of an entityy return yearsOpened + (monthsOpened/12.0);
}
}
27. Aggregates
A cluster of related
entities and values
Behaves as a unit
Has a root
Has a bou da y
as boundary
Objects outside the
aggregate can only
reference the root
f
Deleting the root
removes everything
28. Repositories
public interface AccountRepository {
Manages a collection of Account findAccount(String accountId);
objects void addAccount(Account account);
Provides methods for: }
Adding an object
public class HibernateAccountRepository implements AccountRepository {
Finding object or objects
private HibernateTemplate hibernateTemplate;
Deleting objects public HibernateAccountRepository(HibernateTemplate template) {
hibernateTemplate = template;
p p
Consists of an interface and an }
implementation class public void addAccount(Account account) {
hibernateTemplate.save(account);
Encapsulates database access }
mechanism public Account findAccount(final String accountId) {
return (Account) DataAccessUtils.uniqueResult(hibernateTemplate
.findByNamedQueryAndNamedParam(
Keeps the ORM framework out "Account.findAccountByAccountId", "accountId",
accountId));
of the domain model }
Similar to a DAO }
29. Services
public interface MoneyTransferService {
Implements logic that cannot BankingTransaction transfer(String fromAccountId,
be put in a single entity String toAccountId, double amount)
throws MoneyTransferException;
Not persistent }
Consists of an interface and an
implementation class public class MoneyTransferServiceImpl implements MoneyTransferService
{
Service method usually: private final AccountRepository accountRepository;
Invoked (indirectly) by p
private final BankingTransactionRepository
g p
bankingTransactionRepository;
y
presentation tier public MoneyTransferServiceImpl(AccountRepository accountRepository,
BankingTransactionRepository bankingTransactionRepository) {
Invokes one or more this.accountRepository = accountRepository;
this.bankingTransactionRepository = bankingTransactionRepository;
repositories }
Invokes one or more entities public BankingTransaction transfer(String fromAccountId,
String toAccountId, double amount) {
…
Keep them thin }
}
30. Factories
Use when a constructor is insufficient
– Encapsulates complex object creation logic
– Varying products
Different kinds of factories
–F t
Factory classes
l
– Factory methods
Example: O d F t
E l OrderFactory
– Creates Order from a shopping cart
– Add li it
Adds line items
31. Role of Spring 1
Use the
U th POJO programming model
i d l
– Minimize dependencies on infrastructure frameworks:
your domain model might outlive them
– Avoid @DoThisAnnotations: e.g. @Transactional
Spring instantiates and wires together
– Services, factories and repositories
Dependency injection into entities
p y j
– One option is @Configurable but it’s not POJO
– Hibernate Interceptor/Manual injection is preferable
32. Role of Spring 2
Spring
S i AOP for service-level crosscutting
f i l l tti
concerns:
– E g transaction management, security logging etc
E.g. management security, etc.
AspectJ for entity and value object crosscutting
concerns
– E.g. tracking changes to fields
– AJC/Load-time weaving has a cost
g
Use Spring ORM in the repository
implementation classes
33. Agenda
The
Th ups and d
d downs of OO design
f d i
Overview of the Domain Model pattern
Domain model building blocks
Common code smells
Refactoring existing code
34. Overview of code smells
Code
C d smell = something about th code
ll thi b t the d
that does not seem right
Impacts ease of development and testing
Some are non-OOD
Some are the consequences of non-OOD
35. Long method
Methods should be short p
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
y p p y
public BankingTransaction transfer(String fromAccountId, String toAccountId,
double amount) throws MoneyTransferException {
Account fromAccount = accountDAO.findAccount(fromAccountId);
But business logic is Account toAccount = accountDAO.findAccount(toAccountId);
assert amount > 0;
double newBalance = fromAccount.getBalance() - amount;
concentrated in the switch (fromAccount.getOverdraftPolicy()) {
case Account.NEVER:
if (newBalance < 0)
services ⇒ l
throw new MoneyTransferException( In sufficient funds");
MoneyTransferException("In funds );
i long methods
th d break;
case Account.ALLOWED:
Calendar then = Calendar.getInstance();
then.setTime(fromAccount.getDateOpened());
Long methods are difficult Calendar now = Calendar.getInstance();
double yearsOpened = now.get(Calendar.YEAR) - then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) - then.get(Calendar.MONTH);
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("Limit exceeded");
– Maintain break;
default:
throw new MoneyTransferException("Unknown overdraft type: "
– Test }
+ fromAccount.getOverdraftPolicy());
fromAccount.setBalance(newBalance);
Fix: toAccount.setBalance(toAccount.getBalance() + amount);
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date());
bankingTransactionDAO.addTransaction(txn);
return txn;
– Splitting into smaller }
methods
36. Feature Envy
Methods that are far p
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
y p p y
public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) throws
MoneyTransferException {
Account fromAccount = accountDAO.findAccount(fromAccountId);
too interested in data Account toAccount = accountDAO.findAccount(toAccountId);
assert amount > 0;
double newBalance = fromAccount.getBalance() - amount;
switch (fromAccount.getOverdraftPolicy()) {
belonging to other
g g case Account.NEVER:
if (newBalance < 0)
throw new MoneyTransferException( In sufficient funds");
break;
MoneyTransferException("In funds );
classes case Account.ALLOWED:
Calendar then = Calendar.getInstance();
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--;
monthsOpened += 12
th O d + 12;
– Poor encapsulation }
yearsOpened = yearsOpened + (monthsOpened / 12.0);
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
– Long methods throw new MoneyTransferException("Limit exceeded");
break;
default:
throw new MoneyTransferException("Unknown overdraft type: "
+ fromAccount.getOverdraftPolicy());
Fix by moving }
fromAccount.setBalance(newBalance);
toAccount.setBalance(toAccount.getBalance() + amount);
methods to the class TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date());
bankingTransactionDAO.addTransaction(txn);
return txn;
that has the data
}
37. Data class
public class Account {
Classes that are j t
Cl th t just public static final int NEVER = 1;
public static final int ALLOWED = 2;
getters and setters private
private
int id;
double balance;
private int overdraftPolicy;
No business logic - private
private
private
String accountId;
Date dateOpened;
double requiredYearsOpen;
private double limit;
it’s in the service Account() {}
Leads to: public Account(String accountId, double balance, int overdraftPolicy,
{….. }
Date dateOpened, double requiredYearsOpen, double limit)
– Feature envy public int getId() {return id;}
public String getAccountId() {return accountId;}
Fix by moving public void setBalance(double balance) { this.balance = balance; }
public double getBalance() { return balance; }
methods that act on public int getOverdraftPolicy() { return overdraftPolicy; }
public Date getDateOpened() { return dateOpened; }
data into class public double getRequiredYearsOpen() { return requiredYearsOpen; }
public double getLimit() {return limit; }
}
38. Primitive Obsession
public class Account {
private Date dateOpened;
Code uses built in
built-in }
types instead of public class Account {
private Date dateOpened;
application classes }
public class MoneyTransferServiceProceduralImpl implements
Consequences:
C MoneyTransferService {
public BankingTransaction transfer(String fromAccountId, String
– Reduces toAccountId,
double amount) throws MoneyTransferException {
understandability Account fromAccount = accountDAO.findAccount(fromAccountId);
Account toAccount = accountDAO findAccount(toAccountId);
accountDAO.findAccount(toAccountId);
– L
Long methods
h d …
Calendar then = Calendar.getInstance();
– Code duplication then.setTime(fromAccount.getDateOpened());
Calendar now = Calendar.getInstance();
– Added complexity double yearsOpened = now.get(Calendar.YEAR) -
then.get(Calendar.YEAR);
int monthsOpened = now.get(Calendar.MONTH) –
Fix by moving data then.get(Calendar.MONTH);
if (monthsOpened < 0) {
and code into new yearsOpened--;
monthsOpened += 12;
class }
yea sOpe ed
yearsOpened = yea sOpe ed + (monthsOpened / 12.0);
yearsOpened ( o t sOpe ed 0);
if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())
…
}
39. Switch Statements
public class Account {
Use of type codes and public static final int NEVER = 1;
switch statements instead public static final int ALLOWED = 2;
…
of polymorphism
Consequences:
C public class MoneyTransferServiceProceduralImpl
implements MoneyTransferService {
– Longer methods
public BankingTransaction transfer(String
– Poor maintainability caused fromAccountId, String toAccountId,
, g ,
by d duplication
b code d li i double amount) throws MoneyTransferException {
…
– Increased code complexity switch (fromAccount.getOverdraftPolicy()) {
case Account.NEVER:
Fix by introducing class …
break;
b k
hierarchy and moving case Account.ALLOWED:
…
each part of switch default:
…
statement into a }
overriding method }
…
40. Data clumps
Multiple fields or public class A
bli l Account {
t
method parameters public static final int NEVER = 1;
that belong together
g g public static final int ALLOWED = 2;
Consequences: private int id;
private double balance;
– Long methods private String accountId;
private
p i ate Date dateOpened
dateOpened;
– Duplication
private int overdraftPolicy;
Fix by: private double requiredYearsOpen;
– Moving fields into their private double limit;
own class Account() {}
– Eliminate resulting
}
Feature Envy
41. Agenda
The
Th ups and d
d downs of OO design
f d i
Overview of the Domain Model pattern
Domain model b ildi bl k
D i d l building blocks
Common code smells
Refactoring existing code
42. Transforming procedural code
Inside
I id every procedural d i i a
d l design is
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!
43. Refactoring to an OO design
Transform a
procedural design to
an OO design by
g y
applying refactorings
Refactoring:
– Restructure the code
– Without changing
behavior
Essential cleanups for
decaying code
44. Basic refactorings
Extract Method
– Eliminates long methods
Move Method
– Move a method to a
different class (field or
parameter)
– Moves method to where
the data i
th d t is
Push Down
– Move a method into
subclasses
– Optionally leave an
abstract method behind
– Part of eliminating
g
conditional logic
…
45. Compound refactorings
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
gy yp
Replace Conditional With Polymorphism
– Turn into part of a switch statement into an overriding method in
a subclass
Replace Data Value with Object
– Move field into it’s own class
– Eliminates Primitive Obsession
47. Summary
Organizes the business logic as classes
with state AND behavior
Improves maintainability and testability
Enabled by POJOs and non-invasive
frameworks
Incrementally apply by refactoring
Use It!
48. For more information
Buy my book ☺
– Go to manning.com
Send email:
chris@chrisrichardson.net
Visit my website:
http://www.chrisrichardson.net
http://www chrisrichardson net
Talk to me about consulting
and training