I have three entities inside one bounded context:
- Company
- Department
- Agent
A few business requirements:
- 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.
- 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:
- 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))
}
- 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:
- The agent has to know about the company's internals. Maybe have the Agent call the domain service?
- 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.
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