Say we have an imaginary system managing purchases made by customers, and there are the following business rules:

  1. You can only buy a product if you have enough money on your pre-paid account
  2. You can deposit any value to your pre-paid account
  3. 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){
      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)?

  • I am not experienced modeler, but I solve similar domain logic with DomainService which hits the database for the information we need to perform business logic and pass it to Customer class. Therefore, As @king-side-slide points out, you dont need Purchase collection at all, but what is content of PurchaseRequest and how its intented to use, I dont know either.
    – Muflix
    Commented Mar 27, 2020 at 7:52
  • So the idea is that Application Service get's all the additional information you need and pass them to domain objects. (The information may be also from external sources and different systems)
    – Muflix
    Commented Mar 27, 2020 at 8:02

3 Answers 3


This is precisely the kind of situation that will often trip up inexperienced modelers!

Let us preface the solution with a quick refresher on the general "flow" an application will follow when handling commands:

1. Retrieve all state (domain model) necessary to coordinate use-case

2. Coordinate domain model to fulfill use-case ("tell don't ask")

3. Persist domain model

In your case, either we are pulling way too much state in step 1, or we are retrieving more state in step 2. Neither of these are ideal. So where is the solution?

A little more "knowledge crunching" can reveal that we are missing a concept that can be used to encapsulate the critical piece of state (NeverBoughtProduct). What we need here is a PurchaseRequest (VO). So now instead of:

// 1
customer = customers.Find( cmd.CustomerId );

// 2
customer.Purchase( cmd.ProductId, cmd.Price);

// 3
customers.Save( customer );

we have:

// 1
customer = customers.Find( cmd.CustomerId );

purchaseRequest = purchaseRequests.Find( cmd.CustomerId, cmd.ProductId );

// 2
customer.Purchase( purchaseRequest, cmd.Price ); 

// 3
customers.Save( customer );

In the end, you actually knew the solution to your problem! (take a look at your non-DDD passage - that's your PurchaseRequest).

  • Thanks, this is very educational. Just one more thing I don't understand. What is purchaseRequest exactly? Is it some kind of non-persistent projection of system state deduced both from clients and purchases? How do we classify it? Entity? DTO? Value object? Would it simply have a single property like CanGiveDiscount? Commented Dec 27, 2018 at 18:36
  • Also, what would customer class look like now? Precisely, how can we add a purchase to a customer? Customer should clearly have some kind of purchases collection, but the collection would hold only purchases made since customer object creation. In that case, how should we name the collection? 'Purchases' would be misleading since it only contains a tiny subset to recent purchases. 'PendingPurchases' doesn't work either I guess. Form customer class perspective, the purchase isn't pending. Commented Dec 27, 2018 at 18:57
  • @AndrzejGis In my example above, a PurchaseRequest (or whatever name is most appropriate in your UL) represents the pending intent for the purchase of a Product by a Customer. As you astutely point out, in it's current for it needn't be persisted and would be best-understood as a Value Object. It allows you to encapsulate a snapshot of persistent state to help mediate a purchase. It is not strictly necessary to put any logic on the PR. Because it is a VO, it can be consumed as part of another aggregate to propagate state (hint Purchase). Commented Dec 27, 2018 at 19:39
  • 2
    @AndrzejGis I would disagree that it's clear a Customer needs a collection of Purchases. Let us allow the behavior of our system inform its design. Currently, I see no reason for a Customer to have any reference to their purchases. That is, I see no rules necessitating this relationship. That said, I already know what you are thinking next, "how is a Purchase created then?". Well, object creation patterns can be weird. Because a Customer is not changed by making a purchase, we actually needn't save the Customer. With that in mind... Commented Dec 27, 2018 at 19:44
  • 1
    @Muflix PurchaseRequest is a projection of persistent state that holds the value of NeverBoughtProduct such that the Customer can then use that value as part of its Purchase operation. It’s essentially just a container that allows us to reorganize our logic so that we don’t have to issue a query as part of a domain operation. Instead we “front-load” the query in our service layer (hydrate PurchaseRequest) in order to keep our domain free of a persistence dependency. In a way, yes, we are just moving things around (to achieve cleaner design). Commented Mar 27, 2020 at 13:10

I think the problem here is that you operate at Purchase level. With it, you need to fetch every record.

I would introduce a separate PurchaseHistory interface. It would be able to do all kinds of analytics with Purchases and timelines (which you are going to need anyway).

In particular, it would have something like std::optional<Purchase> FindLatestPurchase(string ProductId). It would be able to use "ORM tricks" to only query the last purchase record from the database, for a particular user and product, and indicate the absence of such record.

(Other benefits, like an ability to retire old purchase records while preserving summaries stats computed from them, you will discover later.)

  • 1
    I don't get what your domain model would look like. Could you please elaborate? Commented Dec 26, 2018 at 17:54
  • My idea was to follow the standard DDD idea of an aggregate root as illustrated by "order vs line items" example. That is, purchase history is an aggreagate root, and individual purchases are non-root entities. Purchase history at best loads an entire list of references to purchases, not entire purchases. I don't see why logically transparent lazy-loading can't be implemented here, as long as we access old and create new Purchases only through PurchaseHistory. OTOH maybe PurchaseHistory a Domain Service could be a better approach.
    – 9000
    Commented Dec 26, 2018 at 18:58
  • 1
    Issuing a query from your domain is generally a bad idea as it not only creates another dependency, it creates one in the wrong direction! Think about it like this: is it really the case that the piece of state we are after (NeverBoughtProduct) cannot be known before the flow of control reaches our domain model? That is, is the CustomerId and ProductId contained in the request/command not enough information to resolve NeverBoughtProduct before Customer.Purchase is invoked? Of course we know that it is. The question, then, is what do we do about it? See my answer. Commented Dec 27, 2018 at 17:26

I think that the Purchase method should not be part of Customer. A Purchase (or an Order - depending on your needs) should be it's own aggregate.

To overcome your initial problem:

I cannot make the Purchase an aggregate root, because then I wouldn't be able to encapsulate the business rules in domain objects.

I would introduce a DomainService, named e.g. PurchaseService or OrderingService. Domain services are exactly a place where you put domain logic (in your case 50% off for first order) which does not naturally fit entities (in your case Purchase method does not fit Customer entity).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.