9

For the purpose of separating different functionality into different classes, I have two following Aggregates:

  1. ActiveEmployee

    • AssignTask ()
    • ReassignManager ()
    • Deactivate (string reason)
  2. InactiveEmployee

    • GetReasonForDeactivation ()
    • Reinstate ()

Both aggregates share the same root, dbo.Employee.

EmployeeID servers as a primary key for both Aggregates.

The repository instantiates the first one, or the second one based on a boolean flag dbo.Employee.IsActive.

Since I follow the rule that only one aggregate can be modified in one transaction, I will never encounter a situation where I load both aggregates with same ID, ending up with InactiveEmployee with ID 3 and ActiveEmployee with ID 3 in same transaction.

Is this good DDD? Does DDD allow for having two aggregates with same root? Can we differentiate Aggregate types based on a flag?

UPDATE 1:

When I said that both aggregates share the same root, what I meant was that both Aggregates represent a row in dbo.Employee table.

In my understanding, a root of the Aggregate is the entity which is uniquely defined in our domain. It does not mean that that entity has to exist as a real class, it's identity is what matters. Every other Aggregate that wants to reference Inactive/ActiveEmployee will hold a reference to the EmployeeID, not the reference to object instance.

omittones
  • 193

4 Answers4

13

You need to understand that DDD is not about primary keys, rows or tables - these are just means to implement it.

Aggregate root is usually implemented as a class, because you are expected to access all the functionality and data of the aggregate through the root. It therefore needs to have some behavior, something which primary key of the table can't have. Primary benefit of this is the encapsulation of aggregates - its logic is completely contained inside the aggregate and doesn't leak outside which greatly reduces the coupling (and as a consequence complexity) of the application.

It follows that each aggregate has its own aggregate root, so two aggregates can't have the same root (if they have, there is effectively just one aggregate).

In your case, you probably want to have two independent aggregates (ActiveEmployee, InactiveEmployee) which are backed by the same table (which is fine because it's totally out of DDD's scope). But then remember that there's actually no dbo.Employee entity in your domain model, it's just a table on a lower (persistence) layer.

qbd
  • 2,936
4

If Employee is an aggregate root, it means that all the outside objects can reference only the Employee (and not the ActiveEmployee/InactiveEmployee).

Any references from outside the aggregate should only go to the aggregate root. DDD aggregate by Martin Fowler

I don't think that's your intent.
I think ActiveEmployee and InactiveEmployee are both independent aggregate roots in your example.
These two are completely different. They expose different APIs and require different handling. They do share some information (Employee table), but that aside they have no interaction with one another. They cannot substitute one another and their APIs can't be invoked without checking the Employee status first

This situation looks a lot like violation of the Liskov Substitution principle to me.

DanielS
  • 383
3

My opinion, you can have multiple Aggregate root who actually map the same persistence record (e.g. Rows in a table). But as the other replies have pointed out, you'd better avoid inheritence.

For you specific case, I think there are a few DDD principals violated:

  1. A single domain should have its boundary decided by the business natural instead of the technology concerns. Employee is obviously an Aggregate root for its problem domain, I don't think there are two separately Roles in a company dedicate for Active Employee manangement and Inactive Employee management. This design mixed the Entity and the behavior can be applied on an Entity, I can smell database driven design.

  2. This design violated the layered architecture promoted by DDD. The layered architecture has the principal that higher level layer should only depends on the interface exposed by lower level layers and has zero knowledge about the internal state of the lower layer. But the class ActiveEmployee and InactiveEmployee obviously leak the SQL query logic into domain layer and even worse it will leak into application layer by using these classes in it. Make the code tight coupled into these two states. It leads to two major issues:

    • Think about if you want to add one new state InTransition for an Employee to denote the special period between an Active Employee and becoming an Inactive Employee. You will have a new Emplopee subclass InTransitionEmployee and you have to change your code from UI, Application layer, domain layer to persistence layer.
    • You hard coded the state into the class declaration. What will happend when you call an InactiveEmployee instance's Reinstate() method to make it active again? The instance of InactiveEmployee is obviously inconsistent with the real state Active. It will definitely leads to hard to detect runtime bugs.

By following the DDD principal, I think your case can be refactored into:

  1. A single Employee class as the Aggregate root. Active, Inactive is just two values of the Employe.state property.

  2. A Employee Repository which provide the Repo method findActiveEmployees() and findInactiveEmployees(). But we may not need these two specific methods and a generic query method is enough.

  3. Refactor the methods in your Employee class design to extract them to services class, e.g.

    • assignTask() should be a service function of somethign like TaskService:
    public class TaskService {
        public void assignTask(Task task, Employee employee) {
            if(!employee.isActive()) threw new UnsupportedOperationExeption("You cannot assign a task to an INACTIVE employee.");
            ......
        }
    }
    
    • reassignManager() should be extracted into OrganizationService:
    public class OrganizationService {
         public void reassignManager(Employee employee, Employee manager) {
             if(!employee.isActive()) threw new UnsupportedOperationExeption("You cannot reassign the manager of an INACTIVE employee.");
             if(!manager.isActive()) threw new UnsupportedOperationExeption("You cannot assign an INACTIVE employee to be the manager of other employees.");
    
         employee.setManager(manager);
         ......
     }
    

    }

ehe888
  • 147
1

So, I think what the OP has done is create 2 Aggregate Roots called ActiveCustomer and InactiveCustomer, and in those has added a property of type Customer. This is an entity, which represents a Customer in his system.

This is where I think the confusion/issue has arisen. Effectively this has made the aggregate root a wrapper around the entity, but it should be much more.

You need to stop thinking about the database, tables, anything to do with persistence. That stuff doesn't exist in your domain. Instead, just see your ActiveCustomer and InactiveCustomer. They may have many of the same properties, but they have different behavior. So build that behavior.

When it comes to persistence, what I do is have an Infrastructure project which contains my DB access functionality, whether it be EF, NHIbernate etc. Within that you can map your objects to tables. What I have started to do is create an internal data state class within my Aggregates which is what EF maps to. So, you might have something like CustomerDataState which has all your public getters and setters and identity on it. You use your EF or NHibernate to populate that state object, which in turn sets up your aggregate. That state class isn't exposed through your aggregate, so as far as the users of the domain model are concerned they are just working with your ActiveCustomer or InactiveCustomer.

So your Active/InactiveCustomer aggregate is loaded up from or rehydrated from a CustomerDataState which can be loaded from some persistence medium.

I find the best way to work with DDD is to ignore the database, forget about it, pretend it doesn't exist. As soon as you start thinking about how your Aggregate will be persisted you have lost your way. So as soon as a thought about rows or tables pops into your head, give yourself a shake and step back.