Building Rich Domain Models

  • 3,750 views
Uploaded on

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

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

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
3,750
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
197
Comments
1
Likes
8

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 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.
  • 20. DEMO Code Walkthrough 3/1/2009 20 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.
  • 66. DEMO Refactoring procedural code 3/1/2009 66 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.