When should an Aggregate Root contain another AR (and when should it not)
-
Let me begin by first apologizing for the length of the post, but I really wanted to convey as much detail up-front so I don't take your time going back and forth in comments. I am designing an application following a DDD approach and am wondering what guidance I can follow to determine whether an Aggregate Root should contain another AR or if they should be left as separate, "free-standing" ARs. Take the case of a simple Time Clock application that allows Employees to clock themselves in or out for the day. The UI allows them to enter their employee ID and PIN which is then validated and the current state of the employee retrieved. If the employee is currently clocked-in, the UI displays a "Clock Out" button; and, conversely, if they are not clocked-in, the button reads "Clock In". The action taken by the button corresponds to the state of the employee as well. The application is a web client that calls a back-end server exposed via a RESTful service interface. My first pass at creating intuitive, readable URLs resulted in the following two endpoints: http://myhost/employees/{id}/clockin http://myhost/employees/{id}/clockout NOTE: These are used after the employee ID and PIN have been validated and a "token" representing the "user" is passed in a header. This is because there is a "manager-mode" that allows a manager or supervisor to clock-in or out another employee. But, for the sake of this discussion, I'm trying to keep it simple. On the server, I have an ApplicationService that provides the API. My initial idea for the ClockIn method is something like: public void ClockIn(String id) { var employee = EmployeeRepository.FindById(id); if (employee == null) throw SomeException(); employee.ClockIn(); EmployeeRepository.Save(); } This looks pretty straight-forward until we realize that the Employee's time card information is actually maintained as a list of transactions. That means each time I call ClockIn or ClockOut, I am not directly changing the state of the Employee but, instead, I am adding a new entry into the Employee's TimeSheet. The current state of the Employee (clocked in or not) is derived from the most recent entry in the TimeSheet. So, if I go with the code shown above, my repository has to recognize that the persistable properties of the Employee have not changed but that a new entry was added into the Employee's TimeSheet and perform an insert into the data store. On the other hand (and here's the ultimate question of the post), TimeSheet seems like it is an Aggregate Root as well as it has identity (the employee ID and period) and I could just as easily implement the same logic as TimeSheet.ClockIn(employeeId). I find myself debating the merits of the two approaches and, as stated in the opening paragraph, wonder what criteria I should be evaluating to determine which approach is more suited for the problem.
-
Answer:
I would be inclined to implement a service for time tracking: public interface ITimeSheetTrackingService { void TrackClockIn(Employee employee, Timesheet timesheet); void TrackClockOut(Employee employee, Timesheet timesheet); } I don't think that it's time sheet's responsibility to clock in or clock out, neither it's employee's responsibility.
SonOfPirate at Programmers Visit the source
Other answers
Evans said Cluster the entities and value objects into aggregates and define boundaries around each. Choose one entity to be the root of each aggregate, and allow external objects to hold references to the root only (references to internal members passed out for use within a singled operation only). Define properties and invariants for the aggregate as a whole and give enforcement responsibility to the root or some designated framework mechanism. Having said that, though; what you've described so far suggests using DDD would be an over-complication. You don't have any domain logic; you just turn a bit on or off. Using DDD this this is making things way more complicated than they are. Anything that needs to know if someone is clocked in or clocked out is simply a report--domain logic is not required and all the extra DDD abstractions to manage complexity that isn't there is just adding accidental complexity.
Peter Ritchie
Aggregate roots should not contain each other (though they may contain IDs to others). First, is TimeSheet really an aggregate root? The fact that it has identity makes it an entity, not necessarily an AR. Check out one of the rules: Root Entities have global identity. Entities inside the boundary have local identity, unique only within the Aggregate. You define the TimeSheet's identity as employee ID & time period, which suggests that TimeSheet is a part of Employee with time period being the local identity. Since this is a Time Clock application, is the main purpose of Employee to be a container for TimeSheets? Assuming you do have to ARs, ClockIn and ClockOut seem more like TimeSheet operations than Employee ones. Your service layer method could look something like this: public void ClockIn(String employeeId) { var timeSheet = TimeSheetRepository.FindByEmployeeAndTime(employeeId, DateTime.Now); timeSheet.ClockIn(); TimeSheetRepository.Save(); } If you really need to track clocked-in state in both Employee and TimeSheet, then have a look at Domain Events (I don't believe they are in Evans' book, but there are numerous articles online). It would look something like: Employee.ClockIn() raises EmployeeClockedIn event, which an event handler picks up and in turn calls TimeSheet.LogEmployeeClockIn().
Chris
Related Q & A:
- How can I restore Sharepoint web app when the DB is in another server?Best solution by SharePoint
- When will there be another nationwide disney talent search?Best solution by Yahoo! Answers
- What to do when your girlfriend likes another guy?Best solution by Yahoo! Answers
- Private or Public school when you want to study in another country?Best solution by Yahoo! Answers
- When will the Xbox 360 have another price drop?Best solution by Yahoo! Answers
Just Added Q & A:
- How many active mobile subscribers are there in China?Best solution by Quora
- How to find the right vacation?Best solution by bookit.com
- How To Make Your Own Primer?Best solution by thekrazycouponlady.com
- How do you get the domain & range?Best solution by ChaCha
- How do you open pop up blockers?Best solution by Yahoo! Answers
For every problem there is a solution! Proved by Solucija.
-
Got an issue and looking for advice?
-
Ask Solucija to search every corner of the Web for help.
-
Get workable solutions and helpful tips in a moment.
Just ask Solucija about an issue you face and immediately get a list of ready solutions, answers and tips from other Internet users. We always provide the most suitable and complete answer to your question at the top, along with a few good alternatives below.