Improving application design with a rich domain model (springone 2007)
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 2,255 views

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

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

Statistics

Views

Total Views
2,255
Views on SlideShare
1,302
Embed Views
953

Actions

Likes
2
Downloads
32
Comments
0

4 Embeds 953

http://plainoldobjects.com 945
http://theknowledge.me.uk 4
https://twitter.com 2
http://www.newsblur.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • 20 – 22 June 2007Metropolis Antwerp, Belgium
  • Improving Applications Design with aRich Domain ModelChris RichardsonAuthor of POJOs in Actionwww.chrisrichardson.net
  • 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
  • Overall presentation goal Learn how to improve application design with truly object-oriented business logic
  • 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
  • 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,
  • 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
  • Example banking application
  • Example procedural design
  • 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; }
  • 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
  • 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
  • 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 – …
  • 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
  • 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
  • Procedural versus OO
  • An example domain model
  • DEMOCode Walkthrough
  • 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
  • Quantifiably simpler methods Procedural – few, longer, more Object-oriented – more, complex methods simpler, simpler shorter methods
  • 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
  • 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
  • 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
  • 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)
  • 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; }
  • 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); } }
  • 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
  • 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 }
  • 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 } }
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • 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 }
  • 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; } }
  • 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()) … }
  • 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 } …
  • 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
  • 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
  • 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!
  • 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
  • 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 …
  • 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
  • DEMORefactoring procedural code
  • 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!
  • 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