Over the past year I've given a few presentations on how to clean up those bloated service classes that are sadly all to common in Java EE applications. A real-world example of this kind of service would be too large to include in a blog so here is an example that is only a minor mess:
public class AccountServiceProceduralImpl implements AccountService {
private Log logger = LogFactory.getLog(getClass());
private final AccountDao accountDao;
private final BankingTransactionDao bankingTransactionDao;
public AccountServiceProceduralImpl() {
this.accountDao = new JdbcAccountDao();
this.bankingTransactionDao = new JdbcBankingTransactionDao();
}
public BankingTransaction transfer(String fromAccountId, String toAccountId,
double amount) {
BankingSecurityManager.verifyCallerAuthorized(AccountService.class,
"transfer");
logger.debug("Entering AccountServiceProceduralImpl.transfer()");
TransactionManager.getInstance().begin();
AuditingManager.getInstance().audit(AccountService.class, "transfer",
new Object[] { fromAccountId, toAccountId, amount });
try {
Account fromAccount = accountDao.findAccount(fromAccountId);
Account toAccount = accountDao.findAccount(toAccountId);
double newBalance = fromAccount.getBalance() - amount;
switch (fromAccount.getOverdraftPolicy()) {case Account.NEVER:
if (newBalance < 0)
throw new MoneyTransferException("Insufficient 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--;
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());
}
fromAccount.setBalance(newBalance);
toAccount.setBalance(toAccount.getBalance() + amount);
accountDao.saveAccount(fromAccount);
accountDao.saveAccount(toAccount);
TransferTransaction txn = new TransferTransaction(fromAccount, toAccount,
amount, new Date());
bankingTransactionDao.addTransaction(txn);
TransactionManager.getInstance().commit();
logger.debug("Leaving AccountServiceProceduralImpl.transfer()");
return txn;
} catch (MoneyTransferException e) {logger.debug(
"Exception thrown in AccountServiceProceduralImpl.transfer()", e);
TransactionManager.getInstance().commit();
throw e;
} catch (RuntimeException e) {logger.debug(
"Exception thrown in AccountServiceProceduralImpl.transfer()", e);
throw e;
} finally {TransactionManager.getInstance().rollbackIfNecessary();
}
}
public void create(Account account) {
BankingSecurityManager.verifyCallerAuthorized(AccountService.class,
"create");
logger.debug("Entering AccountServiceProceduralImpl.create()");
TransactionManager.getInstance().begin();
AuditingManager.getInstance().audit(AccountService.class, "create",
new Object[] { account.getAccountId() });
try {
accountDao.addAccount(account);
TransactionManager.getInstance().commit();
logger.debug("Leaving AccountServiceProceduralImpl.create()");
} catch (RuntimeException e) { logger.debug("Exception thrown in AccountServiceProceduralImpl.create()",e);
throw e;
} finally {TransactionManager.getInstance().rollbackIfNecessary();
}
}
}
This code has a few characteristics that make it difficult to maintain and test:
Compare and contrast this with a service from version of the same code that uses aspects, dependency injection and a real domain model:
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
private final BankingTransactionRepository bankingTransactionRepository;
public MoneyTransferServiceImpl(AccountRepository accountRepository,
BankingTransactionRepository bankingTransactionRepository) {this.accountRepository = accountRepository;
this.bankingTransactionRepository = bankingTransactionRepository;
}
public BankingTransaction transfer(String fromAccountId,
String toAccountId, double amount) throws MoneyTransferException {Account fromAccount = accountRepository.findAccount(fromAccountId);
Account toAccount = accountRepository.findAccount(toAccountId);
fromAccount.debit(amount);
toAccount.credit(amount);
TransferTransaction txn = new TransferTransaction(fromAccount,
toAccount, amount, new Date());
bankingTransactionRepository.addTransaction(txn);
return txn;
}
public void create(Account account) {accountRepository.addAccount(account);
}
}
As you can see there are some significant improvements:
To find out more take a look at these presentations:
Chris, first of all thanks for your excellent book which to me is thought
provoking as Rod's J2EE design & development expert 1-on-1. That aside, in
one of your presentations you mentioned the cons of using @configurable and
you said that you used a hibernate interceptor to provide injection to
objects created outside Spring & my question is; would we see the hibernate
interceptor code? I searched for it in your sample code and couldnt find
it. Thanks.