48
20 – 22 June 2007 Metropolis Antwerp, Belgium

Improving application design with a rich domain model (springone 2007)

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Improving application design with a rich domain model (springone 2007)

20 – 22 June 2007Metropolis Antwerp, Belgium

Page 2: Improving application design with a rich domain model (springone 2007)

Improving Applications Design with aImproving Applications Design with a Rich Domain ModelChris RichardsonAuthor of POJOs in Actionwww.chrisrichardson.net

Page 3: Improving application design with a rich domain model (springone 2007)

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

Page 4: Improving application design with a rich domain model (springone 2007)

Overall presentation goal

Learn how to improve application design with truly object-oriented business logic

Page 5: Improving application design with a rich domain model (springone 2007)

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

Page 6: Improving application design with a rich domain model (springone 2007)

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, ….

Page 7: Improving application design with a rich domain model (springone 2007)

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

Page 8: Improving application design with a rich domain model (springone 2007)

Example banking application

Page 9: Improving application design with a rich domain model (springone 2007)

Example procedural design

Page 10: Improving application design with a rich domain model (springone 2007)

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

Page 11: Improving application design with a rich domain model (springone 2007)

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

Page 12: Improving application design with a rich domain model (springone 2007)

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

Page 13: Improving application design with a rich domain model (springone 2007)

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– …

Page 14: Improving application design with a rich domain model (springone 2007)

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

Page 15: Improving application design with a rich domain model (springone 2007)

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

Page 16: Improving application design with a rich domain model (springone 2007)

Procedural versus OO

Page 17: Improving application design with a rich domain model (springone 2007)

An example domain model

Page 18: Improving application design with a rich domain model (springone 2007)

DEMOCode Walkthrough

Page 19: Improving application design with a rich domain model (springone 2007)

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

Page 20: Improving application design with a rich domain model (springone 2007)

Quantifiably simpler methodsProcedural – few, longer, more complex methods

Object-oriented – more, simpler shorter methodscomplex methods simpler, shorter methods

Page 21: Improving application design with a rich domain model (springone 2007)

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

Page 22: Improving application design with a rich domain model (springone 2007)

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

Page 23: Improving application design with a rich domain model (springone 2007)

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

Page 24: Improving application design with a rich domain model (springone 2007)

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)

Page 25: Improving application design with a rich domain model (springone 2007)

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;

}

Page 26: Improving application design with a rich domain model (springone 2007)

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

}

}

Page 27: Improving application design with a rich domain model (springone 2007)

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

Page 28: Improving application design with a rich domain model (springone 2007)

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

}

}

Page 29: Improving application design with a rich domain model (springone 2007)

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

…}

}

Page 30: Improving application design with a rich domain model (springone 2007)

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

Page 31: Improving application design with a rich domain model (springone 2007)

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

Page 32: Improving application design with a rich domain model (springone 2007)

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

Page 33: Improving application design with a rich domain model (springone 2007)

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

Page 34: Improving application design with a rich domain model (springone 2007)

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

Page 35: Improving application design with a rich domain model (springone 2007)

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

Page 36: Improving application design with a rich domain model (springone 2007)

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;

}

Page 37: Improving application design with a rich domain model (springone 2007)

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

Page 38: Improving application design with a rich domain model (springone 2007)

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

Page 39: Improving application design with a rich domain model (springone 2007)

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}

…}

Page 40: Improving application design with a rich domain model (springone 2007)

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 }

Page 41: Improving application design with a rich domain model (springone 2007)

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

Page 42: Improving application design with a rich domain model (springone 2007)

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!

Page 43: Improving application design with a rich domain model (springone 2007)

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

Page 44: Improving application design with a rich domain model (springone 2007)

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…

Page 45: Improving application design with a rich domain model (springone 2007)

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

Page 46: Improving application design with a rich domain model (springone 2007)

DEMORefactoring procedural code

Page 47: Improving application design with a rich domain model (springone 2007)

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!

Page 48: Improving application design with a rich domain model (springone 2007)

For more informationBuy my book ☺Buy my book ☺– Go to manning.com

Send email:Send email:

[email protected]

Visit my website:

http://www chrisrichardson nethttp://www.chrisrichardson.net

Talk to me about consulting and trainingand training