Assume we are building a system for managing large, tree-like domain-structures like banking loans or insurance contract. At the root of the domain model is a Loan class. It serves as an entry point to the layers of the domain model e.g. loan parts, collection information, contractual parties, collateral, associated insurances,...
Lets say our software can manage different types of Loans (e.g. real estate credit and consumer credit). The Loan class has an (Enum) attribute loanType which describes the type of Loan we are lloking at (e.g. RealEstateLoan, ConsumerLoan, InvestmentGoodsLoan,...).
Depending on the type of Loan the individual classes of the domain model should expose specific behaviour (e.g. a Loan Part in a RealEstateLoan needs certain types of collateral, in a ConsumerLoan only natural persons are allowed as borrowers,...).
The important point is that the type of the root of the domain model should influence the behaviour of some (not all) leaf nodes of the model (e.g. the ContractualParty class which is several layers apart from the Loan class).
Our first implementation used simple switch statements like
public class ContractualParty extends DomainObject
public Loan getLoanRoot() {
// navigation magic to navigate through several layers of domain
// objects all the way to the root Loan object
return root;
}
public List<PartyType> getAllowedTypes {
switch (getLoanRoot().getLoantype()) {
case ConsumerLoan: return new ArrayList<PartyType> (PartyType.NaturalPerson, PartyType.Couple) break;
case InvestmentGoodsLoan: return new ArrayList<PartyType> (PartyType.Company) break;
...
}
}
Obviously there are shortcomings of this kind of design as it does not scale well when the domain complexity is increasing and/or if the number of behavioural differences in the individual classes is increasing.
What I'd like to achieve is an implementation that: - allows to introduce new Loan types to the system without breaking the old ones - is type-safe (or as type safe as possible) when introducing new loan types in the LoanType Enumeration. Instead of sifting through pages of references to loanType or loanType switch statements,... the compiler tells me where extensions are necessary when introducing a new loan type
We started thinking about introducing subclasses of Loan. The subclasses could then introduce methods containg the loanType dependent logic like Loan.getAllowedPartyType(). Individual domain objects could then "ask" their Loan instance for the list of allowed party types. Each Loan subclass could implement these methods according to its specific rules. The problem with this approach appeared to us that the Loan subclasses would start to suck up logic from all over the domain objects and the domain objects would simply relegate many requests to the Loan root object.
A slight twist on this design would be to introduce a new Interface, abstract base class Loantype which would offer the type specific business logic to the domain objects. Instead of subclassing the Loan class it would get a reference on a Loantype instance which could then be used by the individual domain objects. This however is just a slight difference to the first design and suffers the same problem of turning the domain tree upside down and sucking the domain logic out of the individual domain objects.
I'd appreciate your thoughts, idea, pointers, ... on how to design these domain classes.
Loan
your only aggregate root? It may be possible, but large aggregates are usually a sign of erroneous designs that do not align well with the business realities. The fact that you even have agetLoanRoot
method is suspicious.getAllowedTypes
?