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

2,842
-1

Published on

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

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,842
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
53
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

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

  1. 1. 20 – 22 June 2007Metropolis Antwerp, Belgium
  2. 2. Improving Applications Design with aRich Domain ModelChris RichardsonAuthor of POJOs in Actionwww.chrisrichardson.net
  3. 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. 4. Overall presentation goal Learn how to improve application design with truly object-oriented business logic
  5. 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. 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. 7. Lots of procedural Java code Java is an object oriented language object-oriented ANDObject-oriented design is a better way to tackle complexity YETMany complex enterprise Java applications are written in a procedural style
  8. 8. Example banking application
  9. 9. Example procedural design
  10. 10. Example procedural codepublic class MoneyTransferServiceProceduralImpl implements MoneyTransferService { public class Account {ppublic 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. 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. 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. 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. 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. 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
  16. 16. Procedural versus OO
  17. 17. An example domain model
  18. 18. DEMOCode Walkthrough
  19. 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
  20. 20. Quantifiably simpler methods Procedural – few, longer, more Object-oriented – more, complex methods simpler, simpler shorter methods
  21. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  46. 46. DEMORefactoring procedural code
  47. 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. 48. For more information Buy my book ☺ – Go to manning.com Send email:chris@chrisrichardson.net Visit my website:http://www.chrisrichardson.nethttp://www chrisrichardson net Talk to me about consulting and training

×