Real-World DDD: Structuring the Domain Layer
I am trying to make the jump from data-centric design and development into DDD and have read Evans and Nillson but am still having trouble wrapping my head around how I should structure my Domain Layer. I'm sure the nature of my current project isn't helping!
A little background
The application is an internal solution to manage personnel assessments. HR personnel will create assessment "templates" that consist of a set of questions that team leads and managers are to complete for each of their direct reports. The answers are persisted for auditing and review. These assessments can be for a wide variety of things such as feedback for company initiatives, performance reviews, etc.
The data-centric side of me
Not to influence the solution but highlighting my data-centric mindset, I already have a vision for the database schema and include it here only for reference (since a picture says a thousand words):
The schema is, as would be expected, normalized and does not match how the data is handled in my application. And, I've left out lookup tables and the like to try and keep it to a minimum for the problem at hand.
The use cases
The first use case is to retrieve a开发者_开发问答nd display a list of assessments that a user needs to completed. This will be displayed when the user first signs into the application and at first it seems like it would be relatively easy, but there are two wrinkles: 1 - assessments are time-based so they may be required monthly, annually or every 'x' number of years based on the employee's Anniversary Date; and, 2 - users can save an assessment in-progress and complete them later. As a result, the list should contain assessments that are due as well as any that are in-progress.
Next, when the user selects an assessment to perform, I need to retrieve all of the questions for that assessment (the current version) so that I can display them to the user. At any point during the assessment, the user may save the current results. Only after the entire assessment has been completed may it actually be 'submitted' - or committed.
Third, HR needs a way to re-generate the assessment with the responses provided by the supervisor.
Finally, HR is able to create and modify assessments - and they are versioned. So whenever someone modifies an assessment, a new version is created and that one becomes the template for any NEW assessments that are performed (any in-progress assessments continue using their original template).
The Domain Model
Working out of order, it makes sense to me that I will have an Assessment entity that is an Aggregate Root to satisfy the fourth use case. It will have a child collection of Section entities that will, in turn, have a child collection of Question entities. They are all entities because they have identity (yes?). The Assessment is the object that consuming code uses for persistence, validation, etc (although the Section and Question entities validate themselves and roll-up the status to the root Assessment). My goal is to make the versioning abstract from the consumer and implement it in the data persistance layer (good or bad idea?)
This means that I will also have an AssessmentRepository that handles persistence for me and, possibly, an AssessmentFactory that creates a new Assessment when needed.
The bigger issue comes with the other use cases. Do I have an EmployeeAssessment Aggregate root as well? Or is it simply an entity?
Looking at the use cases, I need to use this information a couple of ways. First, when I am generating the list of assessments to display to the user, I have to not only evaluate the list of direct reports against the assessment frequency, but I also need to know if I've already started and/or completed an assessment for that employee. And that comes from the EmployeeAssessments table. The other case is when a user actually performs the assessment in which case, I am interacting with the EmployeeAssessments and Responses tables.
From the UI perspective, when a user is performing the assessment, they know nothing of the internal data structure, etc. I need to supply the UI with the list of questions for that assessment to display and accept the list of responses to persist. Does this lead to a second root with accompanying repository, etc?
The third use case is similar in that HR wants to be able to re-generate the assessment with responses at a later date. However, I'm thinking the same process used when performing the assessment can be used here because resuming an existing assessment would require the same data with the only difference being read/write capability versus read-only for HR.
Wrap it up already!
Okay, I've rambled on enough and think I've cleared my head of the cob webs. I appreciate any direction, suggestions, critiques, etc. As I said, I'm trying to make the jump and think I understand the concepts, now it's a matter of applying them. Thanks!!!
I made the same jump as yourself a few years ago. I am now currently making the jump from plain vanilla DDD to CQRS (see cqrsinfo.com/).
I would approach this the CQRS way ie use event store and completely separate reads from writes at an architectuaral level. However I think the question you refer to is more inline with the plain vanilla DDD way - so I will answer it in this context.
You need to completely free yourself from thinking "data driven". Start with the primary workflow. The first and third use are essentially just get operations. I would first concentrate on the use cases where the aggregate roots state is changed. So use case 2 i.e. "Perform Assessment" would be a good place to start.
As you correctly pointed out, the aggregate root would be Assessment. A "PerformAssessmentService" class could be created (equivalent to a domain service) and your perform assessment workflow would exist here. This workflow would be completely under test where all dependencies such as repositories are simply stubbed out.
You could end up writing the whole perform assessment workflow without any concrete database/UI implementation etc. All business logic is orchestrated in this domain service and all logic exists in your Assessment entity and other associated entities.
Move onto the next use case - maybe use case 4 - modify assessments (do the same as above again)
Leave peripheral things such as repositories/database, UI etc until as late as possible in your implementation. Its important to lock up all the business logic in your domain first - and then drive your peripheral concerns from the domain - its much cheaper/more efficient (in my experience)
Note that there is no right way to do this, this is just a synopsis of how in general I would approach the above project. The key here is that the domain is really the driver behind everything that is implemented...
I would start off with a quick and dirty prototype that uses in-memory repositories. Then you'll get a better idea if you're going down the right path.
精彩评论