Search code examples
foreign-keysdomain-driven-designaggregateroot

Using foreign keys when referencing aggregate roots by id


It is a best-practice to reference another aggregate root by ID and not by reference (see "Implementing Domain Driven Design" page 359 and following).

// direct reference
class AggregateRootA(val b: AggregateRootB)

// reference by ID (preferred)
class AggregateRootA(val b: AggregateRootBId)

class AggregateRootB(val id: AggregateRootBId)
class AggregateRootBId(val id: Long)

The central argument for this kind of decoupling is that each aggregate root should be a transaction boundary.

I wonder whether it is a good or bad idea to use a foreign key relationship in the database when using reference by id in the code. This constraint would enforce consistency at the database level, since there couldn't be a database record of AggregateRootA without the referenced record of AggregateRootB. This is essentially what I want, since the object would be invalid otherwise.

Is there any downside of using a foreign key here apart from slightly more cumbersome testing?


Solution

  • Is there any downside of using a foreign key here apart from slightly more cumbersome testing?

    The constraint introduces some temporal coupling; the database won't allow the write unless the foreign key is actually available, which introduces a "happens-before" relationship between the two.

    So if you have a real business concern that the bookkeeping about B has to happen before the bookkeeping linking A to B, then enforcing that constraint at the database is fine.

    But that isn't a universal concern, and the foreign key constraint can be painful if the information doesn't always arrive in the natural order.

    In short, measure carefully the costs of having the constraint with the benefits of having it.