3

Say my AggregateRoot is the Order-Model. There is a collection with OrderItems (Entities). I have only one Repository for the AggregateRoot (Order) but not for OrderItems.

What should I do when the client wants to update only a small change like the Remarks-field on one OrderItem?

My current understanding ist that the client sends the update by a DTO. Then the middleware loads the whole Order, then update the single detail, and commit the whole Order to repository.

If I understood it correctly is that a good practice in real life or do you handle it differnently? It sounds not performant and not maintenance friendly to me.

Alois
  • 361
  • 2
  • 18

1 Answers1

4

As everything in DDD the answer lays in Domain rules. Everything has to gravitated arround rules not arround data structures.

Warning: Too simplistic example below!

You have to chage the Remarks field of one order item so make you a question: What restrictions and invariats has the Remarks field changing operation? Has OrderItem all info it needs for this? If yes then in this case OrderItem is your aggregate root.

Are some of Remarks not allowed into a OrderItem because it belongs to some type of Order but other Order types allows this Remarks. Then the Order is your aggregate root.

This gives you a clue about how you have to approach it BUT as your comment loading a Order with all OrderItems just to change one OrderItem Remark is absolutely not performant.

“I’m sorry that I coined the term ‘objects,’ because it gets many people to focus on the lesser idea. The big idea is ‘messaging’” ~ Alan Kay

Remember that I said that DDD has to gravitated arround rules and not arround data structures? So, do not think about data structures. Model everything arround the commands and the events (the messages) and the rules. Make your persistence repositories bring the appropiate Aggregate Root for that command, use the AR to apply the command, return a domain event whith the changes produced and use that event to persist the new system state and notify other services about the change.

Code example from Aggregate root invariant enforcement with application quotas

class ApplicationService{
    public void registerUser(RegisterUserCommand registerUserCommand){

      var user = new UserEntity(registerUserCommand.userData); //avoid wrong entity state; ctor. fails if some data is incorrect

      RegistrationAggregate agg = aggregatesRepository.Handle(registerUserCommand); //handle is overloaded for every command we need. Use registerUserCommand.tenantId to bring total_active_users and quota from persistence, create RegistrarionAggregate fed with TenantData

      var userRegisteredEvent = agg.registerUser(user); //return domain changes expressed as a event

      persistence.Handle(userRegisteredEvent); //handle is overloaded for every event we need; open transaction, persist  userRegisteredEvent.fromTenant.total_active_users where tenantId, optimistic concurrency could fail if total_active_users has changed since we read it (rollback transaction), persist userRegisteredEvent.user in relationship with tenantId, commit transaction

    eventBus.publish(userRegisteredEvent); //notify external sources for eventual consistency

  }

This allows you to bring a OrderItemRemarkManagerAggregate into memory from persistence that has just the info you need to chage the Remarks (i.e. OrderItem ID, current Remarks, OrderItem status, OrderType belonged, etc); just use it to apply the operation and apply the changes into persistence.

Later you could worry about reusing an aggregate for several operations (allways in the same Bounded Context of course) or even refactor as you need.

jlvaquero
  • 8,571
  • 1
  • 29
  • 45
  • 1
    Great answer! That would help me for future Things. Your example has new Concept too for me. I think I can use parts of it. – Alois Nov 26 '19 at 09:33
  • "Make your persistence repositories bring the appropiate Aggregate Root for that command" - Does this mean, based on the command we can have a different method in repository(which can load less data of aggregate root - considering it has required info to protect the invariants for the command received)? – Ayyappa Jan 07 '22 at 10:42
  • @Ayyappa. Yes. The Repository has a Handle method for every command it has to handle. – jlvaquero Jan 09 '22 at 17:48
  • I understand the reason for having specific methods but won't be too tough for developer instead of having a single Load method + Lazy Loading instead of lots of specific methods which has a learning curve over time? – Ayyappa Jan 11 '22 at 06:37