1

I have three entities inside one bounded context:

  • Company
  • Department
  • Agent

A few business requirements:

  1. A company has a list of departments, and a list of available phone numbers.
  2. A department has one phone number, and this phone number must be part of the company's list of available phone numbers.
  3. An agent can assign a phone number to a department.

There's a clear invariant between a company and a department.

For example, if company A has the phone number 911 registered, and I try to assign phone number 922 to a department within company A, it should fail; only the phone number 911 can be accepted.

Although this invariant exists, I have decided to model Company and Department as separate aggregate roots:

final case class Company(
  phoneNumbers: Set[PhoneNumber]
  // other data
)

final case class Department(
  companyId: CompanyId
  phoneNumber: PhoneNumber
  // other data
)

My question is, since they're separate, where should I put the business logic of assigning a phone number to a department?

Possible solutions

This is what I can think of:

  1. In a domain service
// domain/services/AddPhoneNumberToDepartmentService

function addPhoneNumberToDepartment(
  company: Company,
  department: Department,
  phoneNumber: PhoneNumber
) {
  if (!company.phoneNumbers.contains(phoneNumber)) {
    return Left(PhoneNumberNotInCompany)
  }

  Right(department.assignPhoneNumber(phoneNumber))
}
  1. Inside the Agent entity
// domain/entities/Agent

final case class Agent(
  companyId: UUID
  // other data
) {
  def assignPhoneNumberToDepartment(
    company: Company
    department: Department
    phoneNumber: PhoneNumber
  ) = {
    if (!company.phoneNumbers.contains(phoneNumber)) {
      Left(PhoneNumberNotInCompany)
    }

    Right(department.assignPhoneNumber(phoneNumber))
  }
}

The latter possible solution, in my opinion, makes the most sense to me.

It is clear, as aforementioned, that agents are the ones who assign phone numbers to a department.

Can't the infamous article Don't Create Aggregate Roots also be applied for modifications? The modification just doesn't happen out of nowhere; someone has to do it.

However, I still have two problems with this solution:

  1. The agent has to know about the company's internals. Maybe have the Agent call the domain service?
  2. The Department entity has a .assignPhoneNumber function, which doesn't check for invariants. That can be a problem; what if a developer in my team decides to use this function instead of the domain service which checks for the invariants?

I'd like to know if I'm missing out on anything obvious, or how you would approach this problem. Thanks.

6
  • Would you say that a department can exist outside of/without a company? If not (and you wish to follow DDD principles) what does that suggest about the relationship of the Company and Department classes? Mar 7 at 0:40
  • @JasonWeber Hi. No, a department should always be inside a company, hence the companyId in its definition. I was thinking of having the Department entity itself inside the Company; however, 1. concurrency is not an issue here, 2. departments will be referenced way more often than companies in other BCs and 3. there can be a large number of departments. for these reasons, I decided to make it a separate AR
    – kibe
    Mar 7 at 0:50
  • Re: #1, what happens if two (or more) people simultaneously update department phone numbers? Can you imagine any cases where this results in a problem? Mar 7 at 0:56
  • @JasonWeber really rare for it to happen, but if it ever does, the DB should be able to catch stale data by using optimistic locking
    – kibe
    Mar 7 at 1:00
  • Last comment: looking at your classes above, how many places would you say you're storing the phone numbers? Mar 7 at 1:03

1 Answer 1

2

Sometimes, the way humans phrase something makes the underlying logic needlessly complex, even though it makes perfect sense for us.

Annoyingly, the added logical complexity takes more time and effort in code than it does for a human to understand it, which leads to "human logic" sometimes making certain twists and bends (because it's not that hard for a human to take it into account), without considering how much more effort this may require in coding terms.
Tangentially, this is what makes government IT projects so difficult. Most governmental rules and regulations grew organically based on what made sense to the humans who discussed it at the time. The government doesn't hire a logician to refactor their rules for simplicity.

  • A company has a list of departments, and a list of available phone numbers.
  • A department has one phone number, and this phone number must be part of the company's list of available phone numbers.

This makes sense on a human level, but on a logical level, there's some duplication going on. You're registering the same phone number twice.

Having more than one source of truth inherently opens the door to all the effort that goes into making sure the sources of truth are synced up, and who to trust when they contradict one another.

It is significantly more efficient to change your approach so that there is only one source of truth, because this bypasses the need for making sure everything stays in sync with one another.

To that end, it makes more sense to rephrase your requirements (or, more precisely, changing your technical interpretation of the existing requirements). In the end, it will look the same to the end user, but the internal implementation can be changed to make things easier on you:

  • A department has one phone number.
  • A company has a list of departments, and a list of additional phone numbers.
  • The company should have an "all phone numbers" property which is the combination of all of its departments' phone numbers and the company's additional phone numbers.

Note that the last bullet point could also be an implementation on the UI level. It depends on where you need to be able to access a full list of phone numbers.

It looks exactly the same to a user, but now you don't have to write any validation logic, nor do you open yourself up to having contradicting sources of information.

Although this invariant exists, I have decided to model Company and Department as separate aggregate roots

If the sole reason for splitting these into two aggregate roots was because of this phone number duplication; the above approach avoids this issue and renders the need to separate your aggregate roots moot.

[From your comment] No, a department should always be inside a company

This further suggests that you should not have split these into two aggregate roots.

8
  • Hi, thanks for the suggestion; indeed, there is a duplication going on, thanks for pointing that out. Are you suggesting that Company becomes the single source of truth--it having something like (Map<PhoneNumber, Department>)? If so, although it'd work, it is essential for me to be able to query a department by a phone number.
    – kibe
    Mar 7 at 13:40
  • Perhaps PhoneNumber could be an entity? It would contain a companyId and departmentId. It would solve the duplication issue; a PhoneNumber can only be created with a companyId at first, and be assigned a departmentId later on. Might be too overkill to have yet another entity, though.
    – kibe
    Mar 7 at 13:43
  • 1
    @kibe: You're trading one job of having to manage the data consistency for another; because then you'd have to make sure the departmentId of the phone number is part of the companyId of the phone number. Having a third aggregate root here would be complete overkill here. When every object is its own aggregate root; you're basically not working with aggregates at that point.
    – Flater
    Mar 7 at 14:04
  • You're right. It seems like the only way is to have Company have all phone numbers and Department totally devoid of its phone number. It does seem weird because that's not how it works in real life, but it is what it is. I do, however, have to constantly query for a specific department given a phone number. Perhaps I'll build a view model (PhoneNumber -> Department) which is eventually consistent but hopefully fast enough. Would you do the same if you were tasked with that requirement? Thanks.
    – kibe
    Mar 7 at 14:21
  • 1
    @kibe Your conclusion is not what this answer suggests. The suggestion here is to store department phone numbers in departments, and any non-department phone numbers in the company. When you need a list of all of the company's phone numbers, you can combine the sources.
    – Flater
    Mar 7 at 15:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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