I am trying to make the jump from data-centric design and development into more of a DDD approach (using CSLA for my domain objects, of course!) 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 and 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 (business 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?) but still implented as child objects/collections. 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!!!
Some rules of thumb, in my view anyway:
Rocky, I've used your framework since 2003 and am currently using it now (still on 3.8 but I bought the 4 e-book and am getting ready to upgrade).
There is a big black hole in my understanding of your writings. The hole exists between:
#1 You say "Entity objects are data containers and business objects are behavior containers" and you always talk of behavior driven thinking
and
#3 Your business objects in Project Tracker look an awful lot like your data tables
What is #2 such that you can make sense out of #3. To us outsiders, your Project Tracker objects look like entity objects...most of the behaviors are the ones that come BUILT INTO THE FRAMEWORK:
- editable or read-only
- bind-able
- authorize/validate etc.
- easy CRUD plug-able
- easy UI changeable (mono, silverlight, etc)
Are those the behaviors you are talking about? If yes then the "behaviors" are mostly already there and abstracted into the framework. All that is left is to mix in our entity shapes and authorization/validation stuff? That feels awfully data-driven to me...but only because you already took care of most of the behaviors common to most business apps.
I feel like the kid in "Enter the Dragon" who is looking for Master Sum-Dum-Guy!!!
Rocky, another way to approach this question would be to answer this. What behaviors did I build into my code so that I am behavior driven not data driven:
1. as Rocky the framework developer
2. as Rocky the Project Tracker developer
The answer to #1 is obvious to me...it is why we all love CSLA. The answer to #2 is a mystery to me.
Let me clarify because I sense confusion. I used the term 'entity' in the DDD sense which is all about encapsulating Domain Logic (Business Logic) in the Domain Layer and, in DDD terms, an Entity is an object that has Identity. I realize that in the world of Entity Framework, etc. that the term can also be applied to more of a table-driven design with objects that possess only state. However, having been a long an avid CSLA user and evangelist (and Tech. Ed. on your 2008 books), I am all about making sure that our business objects are more than just state bags.
In my opinion, the more I get to understand Domain Driven Design the more it looks like CSLA - or that CSLA is the perfect framework for developing DDD applications. Our "Business Objects" are our Domain objects. Those with identity are DDD "entities", our root objects are their "root" objects, parents are their "aggregates" and so on. Maybe I'm wrong, but I see these as very complimentary topics.
I think the only difference may be how we instantiate and load our objects but I know there are some blog posts about implementing the Repository patten with CSLA that I will look at.
So my goal with my current project (described above), is to apply a DDD approach using CSLA as the framework for my domain layer (business objects).
I will take a stab at walking through each use case as an independent path and see what materializes. Any other suggestions or guidance is appreciated.
>> In my opinion, the more I get to understand Domain Driven Design the more it looks like CSLA - or that CSLA is the perfect framework for developing DDD applications. <<
Jeje, I came to the same conclusion when I read those books and people were (are) all in to DDD. I feel like I've been doing some form of DDD for the last 10 years with CSLA and that DDD solves some of the same problems CSLA has solved me for the same time .
>> In my opinion, the more I get to understand Domain Driven Design the more it looks like CSLA - or that CSLA is the perfect framework for developing DDD applications. <<
Jeje, I came to the same conclusion when I read those books and people were (are) all in to DDD. I feel like I've been doing some form of DDD for the last 10 years with CSLA and that DDD solves some of the same problems CSLA has solved me for the same time .
I agree with this as well; unfortunately I'm not very good at conviencing my coworkers of the same!
The problem with ProjectTracker is that it needs to be simple enough to work within the context of a book, and that makes it hard to simulate the sorts of things real apps deal with.
Real apps have lots of screens that are data focused. In my experience, a business app is 80% maintenance screens, 15% slightly complex data entry screens, and 5% "interesting" screens.
The maintenance screens are generally data-centric, and so the screens, and their domain objects, and the data entity objects in the DAL all look like the table being edited.
The slightly complex data entry screens usually are also data focused, but use 2-3 tables instead of one. But specific regions on the screen generally correspond to a table. So the domain objects, and the data entity objects in the DAL all look like tables.
What's confusing then, is that you'd get the same (or very similar) domain objects if you use actual OOD, or if you do data-centric design. The reason is that the user task - the use case - is inherently focused on the data.
Nothing wrong with that at all - and these types of screens/objects/entities/tables can often be created by code generation tools - and that's awesome!
It is the 5% of the screens that are "interesting" where things are really fun. And this is where CSLA is really valuable (imo).
The order entry screen, purchasing screen, invoicing screen, QA simulation screen, insurance quote generation screen - these are interesting.
The reason they are interesting is that they tend to interact (in whole or part) with many tables. Not just 2-3, but maybe 12-15 or more. And almost nothing on these screens is a 1:1 match with a table. Many regions of the screen interact with domain objects containing data from several tables. And the rules are complex and highly interactive.
Almost every app has some of these screens. Even small apps usually have at least one interesting screen - unless they are truly boring apps (which do exist of course).
In my view, the fact that you can apply code generation and very standard, repetative patterns to build the 80%, and often the other 15%, is excellent. That means you can whip out those screens with little thought, time, or effort - leaving more time to focus on the interesting screens.
And that's good, because the users almost certainly spend most of their time in the interesting screens too - so that's where you should be spending time and really thinking deeply about the experience and the underlying domain objects.
The sad fact is that ProjectTracker is one of those boring apps. Nothing in PTracker rises to the level of "interesting". It demonstrates the 95% case.
I started writing the InventoryDemo (samples\silverlight\cs) to overcome this - to have at least one interesting screen. Around 18 years ago I worked for a bio-medical manufacturing firm where inventory was amazingly complex, so I thought it would be "fun" to revisit that problem domain. The problem with that idea is that I'm so busy keeping up with Microsoft's changes to the platforms and technologies - and working at Magenic of course - that I've been simply unable to put the time into InventoryDemo to get it to the point where it is interesting.
You can find several threads on the forum over the past few months where people are begging for a ProjectTracker update. I'm soooooo done with ProjectTracker - but the demand is very high, so that's what I'm doing right now actually - updating ProjectTracker to use all the 4.1 techniques described in the Using CSLA 4 ebook series.
Because this includes reasonable amounts of unit tests for the business types, and a pluggable DAL, and all the other goodies people want to see - it is pretty time consuming...
(yes, I really do know that having unit tests saves time over the lifetime of an application (I wouldn't live without them for CSLA) - but building unit tests balloons the up-front dev time - and so does implementing 2+ DALs)
I should also clarify terminology.
I make a distinction between "domain" and "entity" objects that not everyone agrees with.
All objects are (or should be) defined by their behavior or responsibility.
Some objects have one responsibility: to contain data.
Of those data container objects, there are two general (and similar) types:
Domain objects, or business objects, or business domain objects, don't exist to meet the needs of a service contract (like a DTO) or a data mapping technology or data entity (like an entity object). Domain objects exist to meet the needs of a user story, user scenario, use case, or whatever term you are using in your object-oriented design process.
The fact that a domain object contains data is secondary to the fact that it contains behavior. The responsibility of the object is to be an actor in the user scenario, and to act it requires some data.
I have a 60-90 minute presentation I often give at Visual Studio Live where I walk through the design process I use for domain objects - and that's really not enough time, because what's really needed is a hands-on lab...
I use a modified CRC (class-responsibility-collaboration) design approach. This is far, far superior to typical class diagram based approaches or UML schemes - because those almost always get people thinking about properties (data) before responsibility or behavior.
You don't need anything fancy.
Once all this is done you should have a domain model for your one use case or user scenario.
In real life, this is usually the point where you have to identify the DAL entities that will be used to persist the data for these domain objects, and you'll need to implement them.
Normally you don't get to go on to design other user scenarios without first implementing the one you already have.
And that's a double-edged sword. It is far more agile to do what I just described and then to implement the scenario - at least unit tests, domain objects, DAL entities, and data storage.
For a whole lot of user scenarios this whole thing can be done in a sprint. If the user scenario is in that 80% of screens that's data focused you can probably do several in a sprint.
The one drawback to this, is that you don't have the opportunity to look for reuse of domain types across user scenarios early on. You need to remember to insert a step 7 where you look at existing domain types to see if you can use them without changing them in your new user scenario - thus avoiding duplicate implementations.
I have a 60-90 minute presentation I often give at Visual Studio Live where I walk through the design process I use for domain objects - and that's really not enough time, because what's really needed is a hands-on lab...
I use a modified CRC (class-responsibility-collaboration) design approach. This is far, far superior to typical class diagram based approaches or UML schemes - because those almost always get people thinking about properties (data) before responsibility or behavior.
You don't need anything fancy.
Rocky, is this presentation available anywhere? You have articulated the exact approach I have always used and even introduced me to an acronym I am ashamed to say I didn't know of. I am sure there is plenty I can google but, of course, I am specifically interested in how you work it all out.
Real apps have lots of screens that are data focused. In my experience, a business app is 80% maintenance screens, 15% slightly complex data entry screens, and 5% "interesting" screens.
The maintenance screens are generally data-centric, and so the screens, and their domain objects, and the data entity objects in the DAL all look like the table being edited.
The slightly complex data entry screens usually are also data focused, but use 2-3 tables instead of one. But specific regions on the screen generally correspond to a table. So the domain objects, and the data entity objects in the DAL all look like tables.
What's confusing then, is that you'd get the same (or very similar) domain objects if you use actual OOD, or if you do data-centric design. The reason is that the user task - the use case - is inherently focused on the data.
AHA MOMENT! I never heard you break it down like that before. In a perpetual state of self doubt and shame, 80+ percent of my coding seemed to be very data-centric matching those unavoidable and boring majority of use-cases. I would end up shrugging my shoulders and thinking to myself..."Oh well, I am just thick headed and don't get the behavior driven thing." The last application I was architect on, there were only about two screens/scenarios that were "interesting" and they fit the description you mentioned of being complex with many interacting rules, validations, on demand properties, and at least seven different tables.
Now I feel validated as if you were telling me..."Hey Sean, don't feel weird if +/- 80% of your code/screens/BO's seem very data centric because the behavior of simple CRUD matches most of the simple use-cases. But don't forget Sean to be on the lookout for those few interesting and complex screens/scenarios where you need to be careful. Remember to start from the behavior point of view instead of the database point of view." Peace and harmony returns like the THX intro sound.
The inventory demo, showing an "interesting" behavior driven/designed object, is a thing I wait for with great anticipation!! :-)
Side note: All that being said, I think we take for granted the behaviors you have abstracted into the framework when we think of our use-cases. The complexity you have hidden behind the veil makes these behaviors look simple...heck they might even be forgotten all together. But throw out CSLA and you realize very quickly the need for those first class behaviors in your use-cases. So what I am saying is, because of CSLA, even the 80+ have a whole host of behaviors built in. This is what cracks me up about these "young whipper snappers" touting lean SOA with DTO's as the solution for everything. They whip out a data entry screen, some interfaces, a dumb DTO, and some tests in two seconds and are proud of themselves. I ask them...well what about "business rules, n-level undo, grid-friendly data binding, Silverlight ability, validation, etc etc..." and they say in a slightly annoyed tone as if I am getting in the way of progress with all that 'heavy' thinking..."Oh that will get done next sprint :-) Just put it on the backlog."
IMO as an official arm-chair quarterback...this same problem is at the core of Ken's metaphor of iterative regrowing of a forest after a clear cut (weeds -> plants -> rapid confers -> cedars & tamaracks). He uses this simple slide deck in his presentation on Agile to justify low-no design/architecture up front. He suggests a quick and simple layering of subsequently complex iterations and refactoring. There is some simple truth here and a just critique of overly heavy up front architecture. But the glaring omission is the lack of real accounting for the underlying VAST complexity of systems and frameworks that enable seemingly simple process to work. Started in Darwin's Black Box and further explored in Debating Design, From Darwin to DNA, Behe helps us understand this concept by introducing us to some of the mind blowing architecture and complexity that is abstracted underneath the surface of life. This architecture and design needs to be accounted for. Back in the software world the best compromise I ever heard of was a team at Microsoft that was having an architect (from Asia I think) work several iterations ahead of the main team. He was preparing things ahead of time such that they had what they needed when they came to a sprint. They seemed to think that worked nicely. I assume that the architect, if good, works herself out of a job eventually...but maybe they should go onto a retainer :-) I can't remember the name of the way old sci-fi movie I saw on a reel in my basement as a kid about some space travelers that visit a seemingly abandoned futuristic planet where the missing inhabitants had built machines that did everything for them. Unfortunately over successive generations, because the machines did it all, they forgot how the machines worked and when the machines broke and started making peoples nightmares come true, they couldn't fix them and everyone died at the horror of the monsters they each dreamed up.
Back to the problem at hand, I'm working through the first of my use cases and have had an 'aha' moment of sorts. To spare you from scrolling up and re-reading the use case, I've repeated it here:
The first use case is to retrieve and display a list of assessments that a user needs to complete. 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.
To generate this list, I have to apply three criteria:
1. Any AssessmentVersion that has an associated EmployeeAssessment with DateComplete = null (meaning the assessment is in-progress).
2. The AssessmentVersion with the maximum Version for any AssessmentVersion that has no associated EmployeeAssessments (meaning the assessment is new).
3. Any AssessmentVersion with all associated EmployeeAssessments having a DateComplete value (meaning they are done) that are "due again", meaning that the Frequency field is such that the assessment needs to be performed again (such as every 30 days).
It appears to me that it is actually the AssessmentVersion class that would be my root and not Assessment. However, list items should contain the ID and Title fields from Assessment as well as the Version field from AssessmentVersion.
Where I am blocked is looking at the fourth use-case, creating a new Assessment. In this case I am actually creating two objects: Assessment and AssessmentVersion (with Version = 1). Or would it be better (and more DDD-like) to have a NewAssessment class that encapsulates both objects and is responsible for the logic required to create a new assessment?
If the latter is the better route, would it then also be appropriate to have something like an ActiveAssessment class that encapsulates the first use case? Or perhaps I'm way off and Assessment and AssessmentVersion are one and the same in the Domain Layer?
My problem is that I still see Assessment as the start of my navigation. For instance, list all Assessments, then view all of the versions of a single Assessment. List all "active" Assessments then start a new Assessment for a given employee. And so on. I think this is where I'm hung up.
Anything you can advise to break my analysis paralysis?
I think, for purposes of this use case, you should combine Assessment and AssessmentVersion - after all, the use case considers data from both pieces. Something like an "AssessmentInfo" class that contains properties from both tables. That becomes the start of your navigation process.
Ultimately, based on what you've laid out to date, I'm betting that most of your app is going to consider Assessment and AssessmentVersion to be a single... well, entity. Your database structure breaks out the versioning information, but I haven't seen anything in your use cases that suggests that the version information needs to be handled separately from the assessment within the app.
Also, I wouldn't create specific classes that represent an object and that object's state. I know some people advocate that, and in the cases where there's significant functionality that's different based on the state, I can see the argument. But I'd much rather encapsulate the state within the object and use some kind of state pattern if I really needed it. An object may do different things if it's in different states, but they are all things the object still has to do.
HTH
- Scott
I suggested Scotts answer because I think he's right on. Your use cases don't mention versions anywhere. You may have another use case where you can see the history of Assements, but users aren't creating them (directly) in your use cases. If you need such a history, create it when a new assement is created. Based on your description, it sounds like the "start of navigation" would be whatever you're users are assessing.
Copyright (c) Marimer LLC