Say we have an imaginary system managing purchases made by customers, and there are the following business rules:
- You can only buy a product if you have enough money on your pre-paid account
- You can deposit any value to your pre-paid account
- If you buy a particular product for the first time, you get 50% discount
First thing that comes to my mind is to model it like so (C#-like pseudo-code):
class Purchase : Entity
{
private Guid PurchaseId;
private Guid GlobalProductId;
private string ProductName;
private decimal Price;
}
class Customer : AggregateRoot
{
private Guid CustomerId;
private List<Purchase> Purchases;
private decimal Balance;
public void Deposit(value){
Balance += value;
}
public Result Purchase(globalProductId, productName, price){
if(NeverBoughtProduct(globalProductId)){
price = price * 0.5;
}
if(price > Balance){
return Result.Fail('Insufficient funds');
}
Balance -= price;
Purchases.Add(new Purchase(globalProductId, productName, price))
return Result.Ok();
}
private bool NeverBoughtProduct(globalProductId){
return Purchases.Any(purchase => purchase.GlobalProductId == globalProductId)
}
}
The problem is that the number of purchases can grow rapidly. In order to make a purchase, you have to fetch all previous purchases for the customer form database.
In no-DDD scenario the solution would be trivial - a simple query fetching only a single boolean value whether the product has already been purchased.
I cannot make the Purchase
an aggregate root, because then I wouldn't be able to encapsulate the business rules in domain objects.
Lazy loading isn't going to work either, since NeverBoughtProduct
will fetch potentially every Purchase
anyway.
This is a very common scenario, but I couldn't find a good solution on the web. How do you tackle such problems? Do you use some ORM tricks? Or would you change the model (if so, how would you do it)?