1

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?

pizzerman
  • 23
  • 5

2 Answers2

2

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.

2

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.
Maciej Pyszyński
  • 9,266
  • 3
  • 25
  • 28
  • With this approach, reservation bounded context would depend on resources bounded context, should not we avoid this kind of dependencies with replicas of resources data in reservation bounded context? – ismaelcabanas Jun 29 '21 at 07:17
  • It doesn't depend on another bounded context, but on its API. API is a public contract and while context implementation could change, API shouldn't. It's much easier to maintain API layer than use internal repositories or other parts of another domain. It's like using doorbell at the door. I got the social contract that someone will get to the door. You're not allowed to go straight in to someone else house and talk to the residents (internals of home). You use public API (doorbell), to get contacted response (some instance of human), but you don't know which implementation (e.g . Bob, Jane) – Maciej Pyszyński Jun 29 '21 at 14:43
  • And another aspect is that you shouldn't send replica of data owned by some context to another one. You should send an answer to the asked question and morning more. The internals should be transformed to fulfil the contact response. All not asked data shouldn't be passed – Maciej Pyszyński Jun 29 '21 at 14:50
  • Yeah, it is a good approach, but when resource bounded context comes down we cannot make more reservations, isn't it?, how do you handle these stuffs? – ismaelcabanas Jul 04 '21 at 16:05
  • What do you mean by "comes down"? – Maciej Pyszyński Jul 09 '21 at 13:48
  • I mean if resource bounded context fails, or is unavailable, the IsResourceAvailabe query would fail, then we cannot make reservations, isn't it?. I imagine a microservice approach where each bounded context is a microservice. I think, if I have understood fine, with your approach, you have a direct dependency between Reservation and Resource bounded context (microservice) and if Resource microservice fails, Reservation will be fail. – ismaelcabanas Aug 03 '21 at 14:06
  • Yes, you have a dependency. The same as if you don't use public API, but on a higher level of abstraction, so you don't bind to implementation details. You cannot run out from this dependency in any design pattern possible: monolithic spaghetti code monster, DDD monolithic app, or microservice. The goal here is to have it as loose as possible. – Maciej Pyszyński Aug 05 '21 at 09:01
  • yes, but if you replicate the resource bounded context data that you need in the reservation bounded context, you will be free of failures from resources bounded context, isn't it? – ismaelcabanas Aug 06 '21 at 09:48
  • That's right, but then you have another problem - your data could be inconsistent, and users could make multiple reservations in the same slot. – Maciej Pyszyński Aug 10 '21 at 13:21