0

I’m applying the DDD Aggregate pattern in a Spring Boot application. For example, Order is the aggregate root of OrderItem, and they are mapped like this:

@Entity
public class Order {

    @Id
    private Long id;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> orderItems = new ArrayList<>();

    public void renameOrderItem(Long itemId, String newName) {
        orderItems.stream()
            .filter(item -> item.getId().equals(itemId))
            .findFirst()
            .orElseThrow()
            .rename(newName);
    }
}

@Entity
public class OrderItem {

    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Order order;

    private String name;

    public void rename(String newName) {
        this.name = newName;
    }
}

✅ Problem

According to the DDD Aggregate rules, OrderItem must be modified through its root, Order.

So even for updating just one OrderItem, I need to load the entire Order and call order.renameOrderItem(...).

However, this leads to a few issues:

  1. Without fetch join → Accessing orderItems causes an N+1 problem.
  2. With fetch join → All OrderItems are loaded, even if I only need one → memory inefficiency.
  3. With orphanRemoval=true → If I only partially load OrderItems and then save the Order, the other items might get deleted.

❓ My Question

How can I safely modify an OrderItem through its aggregate root Order while still avoiding N+1 queries and memory waste when using Spring JPA and OneToMany mapping?

💡 What I’ve considered

•   Keep OrderItemEntity and domain model OrderItem separate
•   Load only the OrderItemEntity I need
•   Construct an Order domain model containing just this OrderItem
•   Run business logic via Order.findItem(id).rename()
•   Convert the updated domain model back to entity and save

This way I preserve aggregate integrity, but it results in additional conversion logic and a more complex repository layer.

❓ TL;DR

How do you respect aggregate encapsulation in DDD while also avoiding fetch-join-all or N+1 problems with JPA?

I’m especially interested in real-world solutions or architecture patterns that you’ve used in production.


I’ve seen several related questions, but none of them provided a clear answer.

2
  • imho you are too concerned about the memory inefficiency. Commented Apr 24 at 6:49
  • No questions provide a clear answer as there aren't any. I really wouldn't use orphanRemoval as a feature on a collection that you don't want to fetch into memory. If you have memory concerns on loading this mapping into your entity, don't map it at all. You can fetch the list yourself when you need it, and pass partial/paginated lists in your entityDTO and handle it how ever works for your usecases. Otherwise, some providers (EclipseLink) use change detection and indirection that allow you to add to lists without actually fetching them, but again, best option might be to not map it Commented Apr 28 at 16:00

1 Answer 1

2

Please be aware, that DDD only mandates that the domain layer invocations operate on the aggregate root level. Is does not mandate the same for the persistence layer invocations. That’s out of scope. That’s why I would go for the following approach assuming that you have an "OrderAggregateDomainService" with a method renameOrderItem(orderId, orderItemId, newOrderItemName). The method would basically perform the following 3 steps:

  1. Load the whole Order domain object via the “OrderRepository.retrieveOrderById(orderId)” call.

  2. Invoke Order.renameOrderItem(orderItemId, newOrderItemName) => I assume there is some business logic required that checks the validity of the “newOrderItemName” and that we want to implement that logic in the domain type.

  3. If the validity check is ok, then invoke a second repository method “OrderRepository.renameOrderItem(orderId, orderItem : OrderItem)” => This method does not load the whole order again, it only doublechecks if the persisted OrderItem actually refers to the given orderId and then performs the update of the OrderItem

Additional Remarks:

If someone else would try to rename the same OrderItem at the same time then we could actually face a race condition. Let’s assume both try to update from the same version 1. There are two cases: If you map the version attribute from the JPA entity also to the domain entity and vice versa, then the slower one should get the classical OptimisticLockException. If you do NOT map the version attribute to the domain entity and back, then things get too complicated to explain all variations in detail here, but the bottom line is that you should in fact map the version attribute to the domain entity.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.