The main characteristic of an anemic model is it doesn't contain much (or any) business logic. The domain logic is then placed outside the model, usually to services.
I'll use a simple example to highlight the main differences of an anemic and a rich domain model. In our sample system there are customers, they have payment accounts. On each payment a discount is calculated according to the membership level of the customer. A basic member gets discount only if his payment exceeds a certain amount. A premium member always gets a discount.
An anemic model for the specification above would look like this:
class Customer { private Account account; private Membership membership; public Account getAccount() { return account; } public Membership getMembership() { return membership; } } class Account { private double balance; public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } } interface Membership { } class BasicMembership implements Membership { private double discount; private double discountMinimum; public double getDiscount() { return discount; } public double getDiscountMinimum() { return discountMinimum; } } class PremiumMembership implements Membership { private double discount; public double getDiscount() { return discount; } } class PaymentService { public void pay(Customer customer, double amount) { if(customer.getMembership() instanceof BasicMembership) { payBasic(customer, amount); } else if(customer.getMembership() instanceof PremiumMembership) { payPremium(customer, amount); } } private void payBasic(Customer customer, double amount) { BasicMembership basic = (BasicMembership) customer.getMembership(); double paid; if (amount >= basic.getDiscountMinimum()) paid = amount * basic.getDiscount(); else paid = amount; withdrawAmount(customer.getAccount(), paid); } private void payPremium(Customer customer, double amount) { PremiumMembership premium = (PremiumMembership) customer.getMembership(); double paid = amount * premium.getDiscount(); withdrawAmount(customer.getAccount(), paid); } private void withdrawAmount(Account account, double amount) { if(account.getBalance() > amount) account.setBalance(account.getBalance() - amount); else throw new RuntimeException("Balance low"); } }A sample use is: paymentService.pay(customer, amount);
Here we can see some of the problems of an anemic model. It violates basic OO principles like encapsulation: The model can't guarantee it's consistency. The service code gets complex and things get worse when the complexity of the domain increases.
Let's see how things look with a rich domain model instead:
class Customer { private Account account; private Membership membership; public void pay(double amount) { double paid = membership.discountPrice(amount); account.withdraw(paid); } } class Account { private double balance; public void withdraw(double amount) { if(balance >= amount) balance -= amount; else throw new RuntimeException("Balance low"); } } interface Membership { public double discountPrice(double origAmount); } class BasicMembership implements Membership { private double discount; private double discountMinimum; public double discountPrice(double origAmount) { return (discountMinimum >= origAmount) ? origAmount * discount : origAmount; } } class PremiumMembership implements Membership { private double discount; public double discountPrice(double origAmount) { return origAmount * discount; } }A sample use is: customer.pay(amount);
The rich domain model shows many benefits over the anemic model. Objects are encapsulated, the model can guarantee it's correctness. High complexity can be easier dealt with because advantages of OO can be leveraged. On top of that, such a domain model can be much more expressive for humans reading.
So the question can be raised, what are the valid reasons for an anemic model? Making a rich domain model can be more difficult and requires developers with solid OO design skills. Also a rich model emphasizes the general tension between the principles of encapsulation and separation of concerns. For instance in a persistence or view layer its more trivial to extract data from an anemic model. There are a ways around that problem but generally require a little extra effort. For quick prototypes and really simple models, it may be cheaper to go anemic.
Since the benefits of it are so great I think it's usually best to go with a rich domain model - unless there is a really good reason not to.
The example is nice and makes the choice look obvious. But how can I save a model like this to a database without putting all that code into the domain objects?
ReplyDeleteI recommend you to take a look at the Repository pattern, it will keep storage code out of your domain objects. You'll still need to deal with extracting data from your objects, you can achieve that using restricted method access.
ReplyDelete