2

Assume that I have two aggregates: Vehicles and Drivers, And I have a rule that a vehicle cannot be assigned to a driver if the driver is on vacation.

So, my implementation is:

class Vehicle {
    public void assignDriver(driver Driver) {
        if (driver.isInVacation()){
            throw new Exception();
        }

        // ....
    }
}

Is it ok to pass an aggregate to another one as a parameter? Am I doing anything wrong here?

3 Answers3

3

I'd say your design is perfectly valid and reflects the Ubiquitous Language very well. There's several examples in the Implementing Domain-Driven Design book where an AR is passed as an argument to another AR.

e.g.

  1. Forum#moderatePost: Post is not only provided to Forum, but modified by it.

  2. Group#addUser: User provided, but translated to GroupMember.

If you really want to decouple you could also do something like vehicule.assignDriver(driver.id(), driver.isInVacation()) or introduce some kind of intermediary VO that holds only the necessary state from Driver to make an assignation decision.

However, note that any decision made using external data is considered stale. For instance, what happens if the driver goes in vacation right after it's been assigned to a vehicule?

In such cases you may want to use exception reports (e.g. list all vehicules with an unavailable driver), flag vehicules for a driver re-assignation, etc. Eventual consistency could be done either through batch processing or messaging (event processing).

You could also seek to make the rule strongly-consistent by inverting the relationship, where Driver keeps a set of vehiculeId it drives. Then you could use a DB unique constraint to ensure the same vehicule doesn't have more than 1 driver assigned. You could also violate the rule of modifying only 1 AR per transaction and model the 2-way relationship to protect both invariants in the model.

However, I'd advise you to think of the real world scenario here. I doubt you can prevent a driver from going away. The system must reflect the real world which is probably the book of record for that scenario, meaning the best you can do with strong consistency is probably unassign a driver from all it's vehicules while he's away. In that case, is it really important that vehicules gets unassigned immediately in the same TX or a delay could be acceptable?

plalx
  • 42,889
  • 6
  • 74
  • 90
0

In general, an aggregate should keep its own boundaries (to avoid data-load issues and transaction-scoping issues, check this page for example), and therefore only reference another aggregate by identity, e.g. assignDriver(id guid).

That means you would have to query the driver prior to invoking assignDriver, in order to perform validation check:

class MyAppService {
  public void execute() {
    // Get driver...
    if (driver.isInVacation()){
      throw new Exception();
    }
    // Get vehicle...
    vehicle.assignDriver(driver.id);
  }
}
desertech
  • 911
  • 3
  • 7
  • Thanks for your answer but what I get from the link is opposite to your answer. Look at `assignTeamMemberToTask` method on backlog item. – Payam Mohammadi Jan 19 '21 at 00:20
  • 1
    @PayamMohammadi every rule of thumb has its exceptions... to be more precise, I actually cannot understand from that piece of code why the "team" aggregate is passed into the assign method. The reason, for example, can be purely convenience. True, it is not always easy to follow strict rules, but lets put it that way: it is *preferable* that one aggregate reference another aggregate by identity. – desertech Jan 19 '21 at 19:39
  • So, Why we should not do this in aggregates? In your example, you are doing business validations in application service. – Payam Mohammadi Jan 20 '21 at 15:42
  • 1
    I'm in agreement with @opel here. Dealing with aggregate instances is OK when you have them but even in those methods only store the relevant `Id` and have an overload for just the relevant `Id`. When you hydrate your aggregate it is orders of magnitude simpler working with the `Id` since you only deal with data and do not require an instance of an aggregate that is, and should be, beyond the scope of your repository scope. – Eben Roux Jan 20 '21 at 19:19
0

Suppose you're in a micro-services architecture, you have a 'Driver Management' service, and an 'Assignation Service' and you're not sharing code between both apart from technical libraries. You'll naturally have 2 classes for 'Driver', An aggregate in 'Driver Management' which will hold the operations to manage the state of a driver. And a value object in the 'Assignation Service' which will only contain the relevant information for assignation. This separation is harder to see/achieve when you're in a monolithic codebase

I also agree with @plalx, there's more to it for the enforcement of the rule, not only a check on creation, for which you could implement on of the solutions he suggested. I encourage you to think in events, what happens when:

  • a driver has scheduled vacation
  • when he's back from vacation
  • if he changes he vacation dates

Did you explore creating an Aggregate for Assignation?