20 – 22 June 2007Metropolis Antwerp, Belgium
Improving Applications Design with aImproving Applications Design with a Rich Domain ModelChris RichardsonAuthor of POJOs in Actionwww.chrisrichardson.net
About ChrisGrew up in EnglandGrew up in EnglandLive in Oakland, CATwenty years of software development experience– Building object-oriented software– Building object-oriented software
since 1986– Using Java since 1996– Using J2EE since 1999Author of POJOs in ActionAuthor of POJOs in ActionSpeaker at JavaOne, JavaPolis, NFJS, JUGs, ….Chair of the eBIG Java SIG in Oakland (www ebig org)Oakland (www.ebig.org)Run a consulting and training company that helps organizations build better software faster
Overall presentation goal
Learn how to improve application design with truly object-oriented business logic
Agenda
Th d d f OO d iThe ups and downs of OO designOverview of the Domain Model patternDomain model building blocksCommon code smellsCommon code smellsRefactoring existing code
Designing business logicSpring promotes good design practices:Spring promotes good design practices:– Dependency injection for loose coupling
AOP for handling cross cutting concerns– AOP for handling cross cutting concernsBut you must decide how to structure your business logic:business logic:– Domain Model pattern – object-oriented
Transaction Script pattern procedural– Transaction Script pattern – proceduralChoice of pattern impacts ease of:
Development testing maintainability– Development, testing, maintainability, ….
Lots of procedural Java codeJava is an object oriented languageJava 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
Example banking application
Example procedural design
Example procedural codepublic 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) throws MoneyTransferException {
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;
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) {
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;}
A seductive programming style
I l ti f ti lit iImplementing new functionality is easy– 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 responsibilitiesp
Unable to handle complexity
Works well for simple business logic– E.g. the example wasn’t that bad
But with complex business logic: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 theNew requirements ⇒ Add a few more lines to the
transaction script– Many new requirements ⇒ big mess
Today – OO is growing in popularityPOJOsPOJOs – Plain Old Java Objects– Leverage OO features of Java
O/R mapping frameworks for i ti POJOpersisting POJOs:
– Hibernate– Java Persistence API– ……
Spring AOP and AspectJ for handling cross-cutting concerns:
Transaction management– Transaction management– Security– Logging– Auditing– …
Agenda
Th d d f OO d iThe ups and downs of OO designOverview of the Domain Model patternDomain model building blocksCommon code smellsCommon code smellsRefactoring existing code
Using the Domain Model Pattern
B i l i d t ll ti fBusiness logic spread amongst a collection of classes Many classes correspond to real world concepts:Many classes correspond to real world concepts: Order, Customer, …Many classes are true objects having both:Many classes are true objects having both:– State – fields– Behavior – methods that act on the stateBehavior methods that act on the state
Procedural versus OO
An example domain model
DEMOCode Walkthrough
Benefits of the Domain Model Pattern
Improved maintainabilityImproved maintainability– The design reflects reality– The design is more modular
Improved testability– Small classes that can be tested in isolation
Improved reusabilityImproved reusability– Classes can be used in other applications
Building a domain modelg– Creates shared understanding– Develops an ubiquitous language
Quantifiably simpler methodsProcedural – few, longer, more complex methods
Object-oriented – more, simpler shorter methodscomplex methods simpler, shorter methods
Drawbacks of the Domain Model pattern
R i bj t i t d d i killRequires object-oriented design skillsRequires 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
When to use it
Th b i l i i bl lThe business logic is reasonably complex or you anticipate that it will beYou have the skills to design oneYou can use an ORM framework
Agenda
Th d d f OO d iThe ups and downs of OO designOverview of the Domain Model patternDomain model building blocksCommon code smellsCommon code smellsRefactoring existing code
Domain model building blocks
R l kRoles aka stereotypesBenefits of roles:Benefits of roles:– Guide design– Help name objects– Help name objects– Aid understanding
Roles (from Domain-Roles (from DomainDriven Design)
EntityObj t ith di ti t public class Account {Objects with a distinct identityTypically correspond
public class Account {
private int id;
private double balance;
private OverdraftPolicy overdraftPolicy;
Typically correspond to real world conceptsAlmost always
private String accountId;
private CalendarDate dateOpened;
Account() {}
Almost always persistent
public void debit(double amount) throws MoneyTransferException {assert amount > 0;double originalBalance = balance;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;
}
Value ObjectsObjects that are public class CalendarDate {Objects that are defined by the values of their attributes
public class CalendarDate {
private Date date;
CalendarDate() {}
public CalendarDate(Date date) {
Two instances with identical values can b d
public CalendarDate(Date date) {this.date = date;
}
public Date getDate() {return date;
}
be used interchangeablyOften immutable and
public double getYearsOpen() {Calendar then = Calendar.getInstance();then.setTime(date);Calendar now = Calendar.getInstance();
int yearsOpened = now.get(Calendar.YEAR) –then.get(Calendar.YEAR);Often immutable and
persistentPart of an entity
int monthsOpened = now.get(Calendar.MONTH) -then.get(Calendar.MONTH);
if (monthsOpened < 0) {yearsOpened--;monthsOpened += 12;
}return yearsOpened + (monthsOpened/12.0);y
}
}
AggregatesA cluster of relatedA cluster of related entities and valuesBehaves as a unitBehaves as a unitHas a rootHas a boundaryas a bou da yObjects outside the aggregate can only
freference the rootDeleting the root removes everythingremoves everything
RepositoriesManages a collection of
public interface AccountRepository {
Manages a collection of objectsProvides methods for:Adding an object
Account findAccount(String accountId);
void addAccount(Account account);
}
Adding an objectFinding object or objectsDeleting objectsConsists of an interface and an
public class HibernateAccountRepository implements AccountRepository {
private HibernateTemplate hibernateTemplate;
public HibernateAccountRepository(HibernateTemplate template) {hibernateTemplate = template;Consists of an interface and an
implementation classEncapsulates database access mechanism
p p}
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));
}
}
ServicesImplements logic that cannot
public interface MoneyTransferService {
Implements logic that cannot be put in a single entityNot persistentConsists of an interface and an
BankingTransaction transfer(String fromAccountId,String toAccountId, double amount)throws MoneyTransferException;
}
Consists of an interface and an implementation classService method usually:Invoked (indirectly) by
public class MoneyTransferServiceImpl implements MoneyTransferService {
private final AccountRepository accountRepository;
private final BankingTransactionRepository Invoked (indirectly) by presentation tierInvokes one or more repositories
p g p ybankingTransactionRepository;
public MoneyTransferServiceImpl(AccountRepository accountRepository,BankingTransactionRepository bankingTransactionRepository) {
this.accountRepository = accountRepository;this.bankingTransactionRepository = bankingTransactionRepository;
}
Invokes one or more entitiesKeep them thin
public BankingTransaction transfer(String fromAccountId,String toAccountId, double amount) {
…}
}
FactoriesUse when a constructor is insufficientUse when a constructor is insufficient– Encapsulates complex object creation logic
Varying products– Varying productsDifferent kinds of factories
F t l– Factory classes– Factory methods
E l O d F tExample: OrderFactory– Creates Order from a shopping cart
Add li it– Adds line items
Role of Spring 1U th POJO i d lUse the POJO programming model– Minimize dependencies on infrastructure frameworks:
your domain model might outlive themyour domain model might outlive them– Avoid @DoThisAnnotations: e.g. @Transactional
Spring instantiates and wires togetherSpring instantiates and wires together– Services, factories and repositories
Dependency injection into entitiesp y j– One option is @Configurable but it’s not POJO– Hibernate Interceptor/Manual injection is preferable
Role of Spring 2S i AOP f i l l ttiSpring AOP for service-level crosscutting concerns:
E g transaction management security logging etc– E.g. transaction management, security, logging etc.AspectJ for entity and value object crosscutting concernsconcerns– E.g. tracking changes to fields– AJC/Load-time weaving has a costg
Use Spring ORM in the repository implementation classes
Agenda
Th d d f OO d iThe ups and downs of OO designOverview of the Domain Model patternDomain model building blocksCommon code smellsCommon code smellsRefactoring existing code
Overview of code smells
C d ll thi b t th dCode smell = something about the code that does not seem rightImpacts ease of development and testingSome are non-OODSome are the consequences of non-OOD
Long methodMethods should be short public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {Methods should be shortBut business logic is concentrated in the
i l th d
p y p p y {
public BankingTransaction transfer(String fromAccountId, String toAccountId,double amount) throws MoneyTransferException {
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 methods
Long 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 + 12
– Read and understand– Maintain– Test
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());
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;
}
methods
Feature EnvyMethods that are far public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {Methods that are far too interested in data belonging to other
p y p p y {
public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) throws MoneyTransferException {
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");
g gclassesResults in:
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 + 12– Poor encapsulation– Long methods
Fix by moving
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());Fix by moving
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;
}
Data classCl th t j t public class Account {Classes that are just getters and settersNo business logic
public class Account {
public static final int NEVER = 1;public static final int ALLOWED = 2;
private int id;private double balance;private int overdraftPolicy;private String accountId;No business logic -
it’s in the serviceLeads to:
private String accountId;private Date dateOpened;private double requiredYearsOpen;private double limit;
Account() {}
public Account(String accountId, double balance, int overdraftPolicy, Leads to:– Feature envy
Fix by moving
public Account(String accountId, double balance, int overdraftPolicy, Date dateOpened, double requiredYearsOpen, double limit)
{….. }
public int getId() {return id;}
public String getAccountId() {return accountId;}
public void setBalance(double balance) { this.balance = balance; }Fix by moving methods that act on data into class
public void setBalance(double balance) { this.balance balance; }
public double getBalance() { return balance; }
public int getOverdraftPolicy() { return overdraftPolicy; }
public Date getDateOpened() { return dateOpened; }
public double getRequiredYearsOpen() { return requiredYearsOpen; }public double getRequiredYearsOpen() { return requiredYearsOpen; }
public double getLimit() {return limit; }}
Primitive ObsessionCode uses built in
public class Account {private Date dateOpened;Code uses built-in
types instead of application classesC
private Date dateOpened;}
public class Account {private Date dateOpened;
}
public class MoneyTransferServiceProceduralImpl implements Consequences:– Reduces
understandabilityL h d
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
public BankingTransaction transfer(String fromAccountId, String toAccountId,
double amount) throws MoneyTransferException {Account fromAccount = accountDAO.findAccount(fromAccountId);Account toAccount = accountDAO findAccount(toAccountId);– Long methods
– Code duplication– Added complexity
Account toAccount = accountDAO.findAccount(toAccountId);…
Calendar then = Calendar.getInstance();then.setTime(fromAccount.getDateOpened());Calendar now = Calendar.getInstance();
double yearsOpened = now.get(Calendar.YEAR) -then.get(Calendar.YEAR);
Fix by moving data and code into new class
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);yea sOpe ed yea sOpe ed ( o t sOpe ed / 0);if (yearsOpened < fromAccount.getRequiredYearsOpen()
|| newBalance < fromAccount.getLimit())…}
Switch StatementsUse of type codes and
public class Account {
Use of type codes and switch statements instead of polymorphismC
public static final int NEVER = 1;public static final int ALLOWED = 2;…
Consequences:– Longer methods– Poor maintainability caused
b d d li i
public class MoneyTransferServiceProceduralImpl implements MoneyTransferService {
public BankingTransaction transfer(String fromAccountId, String toAccountId,
by code duplication– Increased code complexity
Fix by introducing class
, g ,double amount) throws MoneyTransferException {
…switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:
…b khierarchy and moving
each part of switch statement into a
break;case Account.ALLOWED:
…default:
…}
overriding method}
…}
Data clumpsMultiple 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;g g
Consequences:– Long methods
private int id;private double balance;private String accountId;p i ate Date dateOpened– Duplication
Fix by:Moving fields into their
private Date dateOpened;
private int overdraftPolicy;private double requiredYearsOpen;private double limit;– Moving fields into their
own class– Eliminate resulting
private double limit;
Account() {}
}Feature Envy }
AgendaTh d d f OO d iThe ups and downs of OO designOverview of the Domain Model patternD i d l b ildi bl kDomain model building blocksCommon code smellsRefactoring existing code
Transforming procedural code
I id d l d i iInside every procedural design is a domain model just trying to get outIncrementally transform a procedural design into an OO design– Small, localized changes– Something to do on Monday morning!
Refactoring to an OO designTransform aTransform a procedural design to an OO design by g yapplying refactoringsRefactoring:– Restructure the code– Without changing
behaviorEssential cleanups for decaying code
Basic refactoringsExtract MethodExtract Method– Eliminates long methods
Move MethodMove a method to a– Move a method to a different class (field or parameter)
– Moves method to where th d t ithe data is
Push Down– Move a method into
subclassessubclasses– Optionally leave an
abstract method behind– Part of eliminating g
conditional logic…
Compound refactoringsA sequence of simpler refactoringsA sequence of simpler refactoringsCompose 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 codegy yp
Replace Conditional With Polymorphism– Turn into part of a switch statement into an overriding method in
a subclassa subclassReplace Data Value with Object– Move field into it’s own class
Eliminates Primitive Obsession– Eliminates Primitive Obsession
DEMORefactoring procedural code
Summary
Organizes the business logic as classesOrganizes the business logic as classes with state AND behaviorImproves maintainability and testabilityImproves maintainability and testabilityEnabled by POJOs and non-invasive frameworksframeworksIncrementally apply by refactoring
Use It!
For more informationBuy my book ☺Buy my book ☺– Go to manning.com
Send email:Send email:
Visit my website:
http://www chrisrichardson nethttp://www.chrisrichardson.net
Talk to me about consulting and trainingand training