domain-driven-designaggregateroot

Domain Driven Design - How do I model a Company and Employee use case?


I've been digging into DDD for the first time the last few weeks. I'm developing an HR application and am really struggling with the idea of Aggregate Roots.

So the current entities would be:

Now given the lifecycle of an Employee, it makes sense at first to include it as a child entity on Company. An Employee can be added to a Company (new hire or existing) and then that Employee could resign or be fired - at which point that Employee record would likely be updated with some kind of status, so it can be differentiated from active Employees.

In regards to domain logic, every Employee at a Company must have a unique email. So if a Company always contained a list of all its Employees, I imagined that could be modeled as:

company.AddEmployee(employee) - This method would contain logic that makes sure the email is unique. In addition, it would update the size property of the Company, based on how many Employees they have. (<100 is small, <500 is medium, etc)

Now the biggest issue I have seen people discuss is concerning large Aggregates. I think this use case would fall under that concern, as a Company could have 10k+ Employees in this HR application. If I'm adding a single Employee at a time, it seems really wasteful to gather all 10k+, even if it's just their emails.

Am I doing the right thing here by making the Company the Aggregate Root, or is there a better way?


Solution

  • Modeling aggregates is not so much about parent-child hierarchies. As already been stated it is about transaction boundaries. Also, consider aggregates provide the public APIs for performing transactions to your domain model.

    It is easier to set aside parent-child hierarchy thinking and, at first also performance considerations. But rather to think:

    Are there use cases to perform transactions (changes) on this entity without the need to apply domain rules that can only be adhered by some encapsulating root entity? That means, can this entity contain all domain logic (business rules) on its own to perform transactions on it?

    Applying this kind of thinking in your case could have the following reasoning:

    There are use cases where I want to modify an Employee entity where it does not make sense for the Company to check business invariants. Like, the main phone number contact information of an employee must never be empty or have invalid format.

    This is of course an artificial example but it shows that by that reasoning such a transaction does not require business invariants that would be rather located in the Company entity. By that logic it can make sense to make Employee an aggregate on its own.

    Also, for me, one crucial key learning from tactical DDD (or basically also from object-oriented programming) is to put the logic where the data is. If employee has all the data to execute logic that is required to keep transactional consistency without the need to ask Company, it can be a good candidate for an aggregate on its own.

    Note: Of course, making an entity which is part of an aggregate an aggregate on its own can also make sense based on performance requirements (in case child entity collections would get too large). But I would not start modeling aggregates based on performance requirements, but rather ask yourself the questions outlined above.

    Now to the topic of checking for e-mail uniqueness.

    Where is the data for that? You could say if the Company knows about all the employees it would also know about all the emails of employees in that company so far. But just making the Employee a child of the Company does not seem to be the right reason.

    If you model an Employee as an aggregate on its own, there is usually a special domain service that maintains collections of employee aggregates and provides access capabilities for retrieval and modification of such aggregates that make sense in the domain of your business - the aggregate repository.

    This is also a good place to put logic for questioning, is there already an employee with that email address? Because the repository has the data to provide that logic.

    The remaining question would of course be what code/component should than call this logic on the repository?. You could, e.g. have a special employee service that orchestrates employee creation. But depending on the situation that might also not be suitable. To help with this kind of decisions it is good to understand the implications of the DDD trilemma.

    To get further hints concerning such decisions you can have a look at this Q&A on stackoverflow which also discusses domain services for a similar problem.