4

Assume that we are doing aggregate design for a ride hailing application and we have created the following aggregates: DriverAvailability aggregate, which has a value object containing the status (available/unavailable) and DriverLocation aggregate which has a value object which contains the latitude and the longitude of the driver as well as the timestamp.

This design seems to make sense in DDD since we tend to create as smaller aggregates as possible but with enough information to enforce the actual business rules. Both aggregates seem to change independently, thus it would make sense to be different. Though it seems it does not make sense to have an unique Id which conceptually be other than the driver's Id.

So if we proceed with this design, it seems that the DriverAvailability aggregate Id as well as the DriverLocation aggregate Id should be the same Id which will be the driver Id. Is this correct in the context of aggregate design? Or DriverAvailability aggregate and DriverLocation aggregate should have their own unique Id, and just a reference driverId inside? In other words, does the use of the same Id for different aggregates (different kind) create a problem in DDD design?

7
  • Can you tell a little bit about what these aggregates do? What behavior do they support?
    – Rik D
    Commented Jul 12, 2023 at 6:27
  • The behaviours for the DriverAvailability aggregate could be: makeDriverAvailable(), makeDriverUnavailbale() and for the DriverLocation aggregate it could have something like updateDriverLocation().
    – Antonis
    Commented Jul 12, 2023 at 7:18
  • "DriverAvailability aggregate, which has a value object containing the status (available/unavailable) [...] it seems that the DriverAvailability aggregate Id as well as the DriverLocation aggregate Id should be the same Id which will be the driver Id" - sooo... why are these modeled as aggregates and not just value objects? Commented Jul 12, 2023 at 16:24
  • UpdateDriverLocation is not real behavior. Question yourself why you need the location and how you can combine it with other information to make decisions and raise events. The thing between the input (command) and output (event) is the aggregate and chances are it won’t make sense to call it DriverLocation.
    – Rik D
    Commented Jul 12, 2023 at 18:41
  • 1
    Can a driver possibly be Available if you have no information about where they are? Is there any use you can make of an Unavailable driver's Location (do you even have their location if they turn the app off when they're Unavailable)? If the answer to both of those questions are both "no", then you potentially only need (possibly absent) locations. If a driver has location data they are available, and if a driver goes unavailable you delete their location data.
    – Ben
    Commented Jul 13, 2023 at 3:47

3 Answers 3

9

The fact that you see DriverID as unique identifier for these micro-aggregates somehow suggest that they are not really independent aggregates but entities within the Driver aggregate, or even attributes of the Driver.

Consistency rules relating the three (e.g that location may be unknown if there is no availability, or location must be known if available, or that location must be in a given area when available) could further reinforce this point of view.

5
  • Thanks for the answer! Though if these kind of invariants (business) are not existed for this specific system, could this design make sense? I am asking since in my mind there are many cases it would be convenient to do it, though I have not seen this approach in DDD examples out there. So if we do not have these kind of rules between them is it actually a reason not to go with this design?
    – Antonis
    Commented Jul 12, 2023 at 10:42
  • @Antonis The fact that the invariant doesn't exist would not change my analysis. Using the same DriverID shows that in reality you access those elements via the driver, which suggest that driver is in reality the aggregate root. Moreover, if you'd delete the driver, your independent position or availability would no longer make sense.
    – Christophe
    Commented Jul 12, 2023 at 19:01
  • Just a friendly heads up, my answer is built off of yours, so feel free to review and correct me if I made a mistake somewhere.
    – Flater
    Commented Jul 13, 2023 at 1:20
  • @Flater +1 for your in-depth analysis and interesting thoughts. My point is about the domain design (DDD) and what the reuse of the id means in that context. I have the impression that DocBrown's answer seems more about the design of the database that could implement such a domain design.
    – Christophe
    Commented Jul 13, 2023 at 7:15
  • 1
    @Christophe: well, I just use the relational concepts as a litmus test if this kind of DDD model can make sense. Note I perfectly agree that the most obvious way of modeling the example in DDD would be to use Driver as an aggregate root and DriverAvailability as well as DriverLocation as value objects. But that can make it hard to achieve a transaction on DriverAvailability objects alone, and in case the OP has a valid use case for it, IMHO using separate aggregates is also valid DDD, in my understanding. DDD gives hints and recommendation for modeling, it is not a law code.
    – Doc Brown
    Commented Jul 13, 2023 at 9:07
5

Yes.

(Yes, they can, even when I think the more obvious approach will be to model your example using one aggregate, where the Driver entity is the aggregate root, see Christoph's answer.). But there may be reasons why you don't want to.

If you have aggregates which correlate to each other in a 1:1 manner (like here, Driver, DriverLocation and DriverAvailability), there is nothing inherently wrong in using the same ID for all of them.

When you map those objects to a relational database, Driver, DriverLocation and DriverAvailability will become database entities, all with the same primary key DriverID and DriverLocation.DriverID as well as DriverAvailability.DriverID at the same time being foreign keys referencing Driver.DriverID. This is fine - PKs can be FKs themselves, or part of a combined FK. I have seen and used this surely more than a hundred times in the past in relational modeling, and never seen a relational database which did not support it.

3
  • Just a friendly heads up, my answer is built off of yours, so feel free to review and correct me if I made a mistake somewhere.
    – Flater
    Commented Jul 13, 2023 at 1:20
  • @Flater: I agree with most parts of your answer (+1 from me), as well as with Christophe's, though I would probably not say "separate aggregates are not in line with the spirit or intention of DDD". IMHO both approaches (separate aggregates vs one aggregate) can be valid within DDD. They can both lead to the same database schema, but with differently sliced transactions, for example. The OP has surely more real world context to make a decision, but DDD does not inherently "forbid" different aggregates with the same unique ID. (Side note: you can address me as a male).
    – Doc Brown
    Commented Jul 13, 2023 at 7:38
  • I think it does forbid that, indirectly. Sharing an ID means you have a 1-to-0..1 relationship, which means that you can create A and later B, but then you cannot delete A without impacting B. That breaches the individual lifecycles of A and B. I'm not saying it can't be done, or even that it wouldn't be the right decision for someone's very particular circumstance, but it's not DDD, it would be a deviation.
    – Flater
    Commented Jul 13, 2023 at 8:54
1

The two given answers (Doc Brown vs Christophe) seem to contradict one another, and I think this has something to do with the ambiguous nature of "can [...]?" I think Doc Brown and Christophe are both right, but they're answering a subtly different question, and I realize that your question is genuinely ambiguous between the two interpretations. The purpose of my answer is to help disambiguate this.

If you're asking if it's possible to do so in a way that it renders a working application, the answer is yes. Doc Brown is not wrong about what they state in their answer.
If you're asking if this is in line with the spirit/intention of DDD, the answer is no - I agree with Christophe here.

Sharing the same identifier inherently means that you share the same lifecycle, which is the key criterion by which an aggregate root is defined.

Aggregate root is key here. While I thought you had considered this when I first read your question, on re-reading it I realized that you never alluded to the distinction between aggregates and aggregate roots, leading me to wonder if you know the difference between them. It is unclear to me whether you are omitting the "root" verbally but are referring to aggregate roots, or if you're only thinking about aggregates and not aggregate roots.

A very clear indication of this was when you said:

Though it seems it does not make sense to have an unique Id which conceptually be other than the driver's Id.

It sounds to me like you're talking about the aggregate root ID here. Your observation is correct in that sense, because aggregate roots inherently define themselves as being the sensible point of reference for both themselves and any of their (sub)aggregates.

In other words, if Availability and Location were an aggregate under the Driver root aggregate, then you would inherently always fetch an availability (or a location) using the driver ID. The aggregate root acts as the point of reference for all of the data under its root. In DDD, you're generally not allowed to fetch a (sub)aggregate by itself, even if you're only interested in that aggregate. You must fetch the aggregate root, and the specific data you're interested in will be part of the data you receive.

DriverAvailability aggregate, which has a value object containing the status (available/unavailable) and DriverLocation aggregate which has a value object which contains the latitude and the longitude of the driver as well as the timestamp.

Value objects don't need IDs to begin with. That's not to say they can't have any, but it's not inherently required from the domain's perspective. The conclusion here is that you don't inherently need an identifier unless you feel like you need one.

For example, your location value object might be a simple lat/lon pair (two decimal values), and there would likely not be a reason to ever refer to this in any other way than by using the two coordinate values - this is a perfect example of an identifier-less value object.

To Doc Brown's point, if the above paragraph is correct for you, and you're only tracking one location object per driver (or none, when no location has been entered yet), then your persistence layer could indeed be storing the location data in a separate table whose PK happens to be the same as the driver table's PK.
Personally, I'd be inclined to just add these columns to the driver table itself, but there are reasons to pick one over the other and I cannot judge your situation that precisely.

However, somewhat against Doc Brown's point, that has no bearing on the domain and how you design it. From the POV of the domain model, I'd expect a driver to have a Location property, without there being any particular reference to a second ID value (i.e. not just Driver.Id but something like Driver.Location.Id).

Both aggregates seem to change independently, thus it would make sense to be different.

This statement gets very hairy very quickly when considering DDD aggregate roots.

Having a scenario where you only change one subaggregate without changing other subaggregates (or the aggregate root) is not proof that you need a separate aggregate root.
DDD does not prescribe a minimum (or maximum) amount of changed values that can be applied to an aggregate (root) in a single transaction.

With DDD, it's not about discrete updates, it's about lifecycle dependency. The fact that you call it a DriverLocation and DriverAvailability betrays that you consider these value objects as belonging to a driver, hence suggesting that you probably need a Driver aggregate root with Location and Availability subaggregates (or just plain old value objects within Driver - the distinction between these two options can be arbitrary and subjective).

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.