Not sure that there is a 'one true way' answer for a design approach that, to be fair, is still evolving. First, DDD and CQRS are not the same thing although the CQRS folks seem to have derived from a DDD-influenced starting point.
There's a lot going on in the DDD mindset and much of it has to do with properly defined boundaries of problems, communication among stakeholders, and interaction between systems, not necessarily a specific implementation in code, so I don't think being too hard-core is a virtue.
You are maybe seeing some of the debate around whether and how domain objects should be changeable, and what function a domain object serves in a system as a whole. CQRS splits a system into read and write pathways, so it's sensible to conclude that you don't actually need read access when you're on the write pathway. Reading then becomes something you do against the events raised by some domain objects and consumed (handled) by others. If you go back a bit in the CQRS history, you'll find arguments that domain objects shouldn't have setters, only getters and a single 'handler' method. The logic here is that only consuming events should result in state change, and that change is entirely handled internally by the domain object.
You show the results of change by treating them as separate artifacts of change, putting them into a separate, flat persistent structure (e.g., a table) and reading it as if you were just reading a report on the current and historical state of the system. For example, you could consume an event by extracting the data you need to read and saving it to a database table that maps closely to a single view (e.g. a screen) of your system.
If you do experiment with this style, be cognizant that other programmers are likely not going to be familiar with this approach, and that there are relatively few (but interesting) scenarios where it's justifiable as a design approach.
For unit testing, there are a couple of approaches that may work for you. The first, and most natural, is to verify the events you expect to see raised by your domain objects are correct. A domain object might raise a generic changed event holding information about the change. You could verify that. You could verify the fact that the event was raised at all, and that other events weren't raised.
Another approach is to use Test Spies that expose readable attributes on your domain object so you can verify state changes. For instance, you can inherit from your domain object, add some accessors to read what would otherwise be encapsulated state and verify that it's correct.
Again, you're confused because these approaches are confusing. If you're looking to adopt some good ideas into your programming, take the juicy bits first. DDD boundaries and making roles explicit are changes to your way of thinking about communicating with your code. CQRS at a minimum suggests that reading data and writing data are segregable operations. That insight leads you to think very clearly about what the role of the data you do need to present is, how much you really need, who's consuming it, how fresh does it need to be, etc... You don't need a full blown Event Sourcing implementation to adopt better encapsulation in your coding. You can start by just focusing on atomic operations within your object, "Tell, Don't Ask" approaches to object interface design, and Inversion of Control through events/handlers rather than strict control within procedural services.
Your understanding of the problem in terms of DDD is being obfuscated by your focus on your data model (database) instead of the behavior of the system (domain).
To reiterate, the purpose of DDD is to model the behavior of a system such that the result is a useful abstraction of its functional requirements. What the above means is that a properly designed domain is modeled according to the behavior of the system, not the data. The single biggest pitfall developers run into when embarking on DDD is to start by using a data model as a domain model, because the data is hardly ever a good starting point when trying to model a system’s functional requirements.
What does the above mean? It means that you need to slice up your data model vertically according to behavior. In practical terms, it means that it is unlikely that there will be a domain entity named Customer
. The reason is because Customer
doesn’t denote much behavior and likely contains too much knowledge. What do your Customers
do? Buy things? Buyer
. View things (this one’s for you)? Viewer
. Manage a website account? AccountManager
. You get the idea.
The point is that the properties of the above domain entities do not necessarily map directly to a single database table (and in some cases use fields from the same table). Each Buyer
might have a collection of previous OrderReceipts
. Each Viewer
has a collection of Screenings
containing details like MovieId
, Location
, Datetime
, DeviceId
, etc for each time they watched something. And AccountManager
might have Username
and Password
. And ALL of the above might have the same CustomerId
.
The most important thing to understand about the above entities is that each one represents the single and unambiguous authority responsible for protecting invariants that may apply to the data for which it holds. Follow that rule and you are golden.
In your case, a Viewer
is just an aggregate responsible for protecting your viewership rules. It contains just enough extra data necessary to enforce your business rules. In this case a collection of Screening
value objects from the last month (or maybe even less data ScreeningCount
). Does that make sense?
Best Answer
Typically, the domain components operate on in memory state, without integrating with the ports and adapters in your system. Think onion architecture.
A second important idea is to make sure that the domain model is as explicit as possible about the information that is needed. This is usually done with the abstraction of a repository, but one where the contract is specific to the domain use case. Udi Dahan (2007) touched on the idea.
So the domain asks the repository for the absolute minimum that it needs to satisfy its own requirements, and the implementation under the covers figures out how to retrieve the necessary state and from it create an in memory model of that state.
Yes, in the pathological case, you might end up with a lot of repository implementations that carve specific data out of Oracle. It happens.
DI framework really doesn't figure into it unless you are intending to run the domain model in a lot of different environments; you just need repository discovery, which is to say a factory that provides the correct implementation of the repository on demand (aka the "repository" repository).