Can you reference other aggregates in a factory when implementing domain driven design?

Can you reference other aggregates in a factory when implementing domain driven design?

I have two aggregates, Employee and Company. An Employee stores a reference to the Company via it’s UUID.
If I want to create an employee, I need to provide it with the company ID:
new Employee(name, companyId)

What I can’t get my head around is how to get the id of the Company if the client provides only the company name. In other words, I see this happening:
Employee buildEmployee(String name, String companyName) {
Company company = companyRepository.findByName()
return new Employeee(name, company.getGUID())

Something feels wrong to me, as now I’ve introduced a dependency on the Company aggregate in order to create an Employee. Even worse would be that if these were two were separate microservices, as I’d have to make a rest call to get the company name.
Is there a way to avoid this coupling, or are my entities modelled incorrectly?


Solution 1:

What your model currently expresses is

Every employee works for exactly one Company.

With that, it’s absolutely clear that there must be a Company before you can create an Employee.

I see two different solutions that could fit here:

  • Pass the Company into buildEmployee. This communicates the fact creating an Employee requires a Company best.

  • Pass in the GUID of the company. This may be suitable for cases where you already have this information, but you don’t have the whole Company object available (e.g. creating a co-worker for an existing employee).

No matter which approach you take, you should avoid loading data through a repository from within the factory. It’s better to leave this for the calling app service, because the app service might require the Company anyway (e.g. to do some kind of input validation).

Keeping all repository interaction in the app service will make your application more robust and easier to reason about. After all, repository access usually involves network interaction and delay.

So to sum up, there’s nothing wrong with having a dependency on Company from buildEmployee, but you should avoid calling the repository.

If depending on Company still feels wrong, then you should reconsider your model.

Solution 2:

Using a Repository inside a Factory is generally not a good idea since it hides away from consuming code (i.e. the Application Service) the fact that another Aggregate is brought to the table and used in the current transaction.

But that doesn’t mean bringing in another aggregate is always bad. You have to make an educated decision :

  • You could actually need Company to be part of the business transaction that creates a new Employee. You could want that because Company holds domain invariants about employees (such as a list of unique employee emails) or because creating an Employee changes things inside the Company itself and you want to prevent concurrent Employee creations to mess things up in the company.

    There are a few ways to do this but it might be a good idea to have a createEmployee() method in the Company itself since the two are tied together.

  • Or, you don’t care about the Company (other than its ID) when creating an Employee. You could consider that you don’t need immediate consistency between an existing company and the employee. Or, if you have foreign keys in your database, that they already provide enough security to prevent orphan Employees from being created.

    In that case, just keep companyID as the only pointer to the company all along, from UI to domain. You don’t even need to use CompanyRepository to load the company because it is not involved in the process.

Solution 3:

First, some thought on your model: are you sure that an Employee works for exactly one company? And that this company may never change?

For your real question, I don’t see a real problem; so let me quote from “DDD quickly“:

There are times when the construction of an object is more complex.

It seems appropriate to use a special Factory class which is given the task of creating the entire Aggregate, and which will contain the rules, the constraints and the invariants which have to be enforced for the Aggregate to be valid. The objects will remain simple and will serve their specific purpose without the clutter of complex construction logic.

One solution might be add another layer – one that does that lookup for you; and calls your buildEmployee() method directly with the UUID of the company.