AggregateRoot A validation needs informations from AggregateRoot B

358 views Asked by At

I'm pretty new in a DDD world and I'm wondering how to handle a situation where I have one aggregate root (AggregateRootA) which needs some information from other aggregate root (AggregareRootB) to be able to do something (e.g. change state of AggregateRootA).

Maybe this will be more clear if I try to use more real world scenario... Lets consider a reservation system. In the system we have Reservation aggregate root and Resource aggregate root. Resource has Available/Unavailable state. The system permits to create a reservation and adds resources to it (in code its done by refering by Id of course). The next step is to accept the reservation and in this step the reservation has to check if its all resources are in available states. Lets say that it can't be done in the adding step or just that resource changed its state when it was already in reservation. How to perform such validation?

I came up with a solution where Reservation could have access to ResourceRepository but as I found out AggregateRoot should not be allowed to have such access in a DDD.

My second idea was to move validation to ApplicationService but such validation for me looks like domain logic so it should be placed in a domain model.

My last thoughts were that maybe I should write dedicated DomainService to handle this situation but again... should DomainService have access to Repositories?

2

There are 2 answers

0
Joonas Lindholm On

I think you are closing in on the point in your last paragraph but the question is not that should a DomainService have access to Repository, it's more of a question about could it have (in your technical implementation)? Evaluate the pros and cons of the approach on a technical basis, and if nothing truly bad comes up, do it. To me, it seems that is what your brain keeps telling you to do anyway.

Then as time goes by and your understanding of the surrounding domain, bounded context, language, etc. gets better (and it will), you can always remodel and refactor. Nothing is set in stone.

9
Maciej Pyszyński On

This is a very common and simple problem to solve, but you need to take a step back.

You basically should have two bounded contexts - reservations and resources. The reservations cannot have access to any classes/services/objects of any other bounded contexts because it breaks the encapsulation rule.

You should instead create a query hander in the resources bounded context. The resources should share query and response class that will provide the information you need - the public API which is a contract between your domains. The most important thing is that reservations bounded context shouldn't know any implementation details of resources.

Example in PHP:

Resource domain:

To request information:

class IsResourceAvaiableQuery
{
    private int $resourceId;

    __construct(int $resourceId)
    {
        $this->resourceId = $resourceId;
    }

    public function getResourceId(): int 
    {
        return $this->resourceId;
    }
}

To get a response that should never be a simple type, but represented by class:

class IsResourceAvaiableDTO
{
    private bool $isAvailable;

   __construct(bool $isAvailable)
   {
       $this->isAvailable = $isAvailable;
   }

   public function isAvailable(): bool 
   {
       return $this->isAvailable;
   }
}

Take a note that both query and response DTO are immutable objects - you cannot mutate their state.

And now in the reservation domain:

class ReservationValidator
{
    
   public function validate(Reservation $reservation): ViolationsList 
   {
       $isResourceAvailableQuery = new IsResourceAvaiableQuery($reservation->getResourceId());
       $isResourceAvailableDTO = $this->messageBus->query($isResourceAvailableQuery);


      if (false === $isResourceAvailableDTO->isAvailable()) {
          // mark $reservation as invalid  
      }
   }
}

Summary

This way you have a singular source of truth and the reservation domain doesn't need to have a clue how you decide if the resource is available or not. Moreover, you can change the implementation at any point in time and it won't affect the reservations domain as long as you don't change the contract (IsResourceAvaiableQuery & IsResourceAvaiableDTO).

Take a note that the resource could be available in a different sense in both domains and you need to do that split too:

  • resources - available to be booked - item that exists and is marked as operational
  • reservations - item is not already booked. And you'll need to merge decisions from both domains to have the full picture.