[This is the third episode in my CQRS Overview Series - Series Contents]
In the last episode in this series, I delved into the importance of valuing behavior rather than data as of primary importance when modeling a business process in a software application. This imperative approach involves defining Commands which represent the behaviors which can be performed in the domain model. Outside the domain, commands are simple message objects which are dispatched to the domain for processing. In the domain itself, a command is simply a method on an Aggregate Root or Entity within the context of the domain. Keep in mind that commands represent an imperative - the client code tells a domain object to do something. While the domain object may say "yes" or "no", it does not answer any other questions since this would represent a query, violating the separation of commands and queries inherent to the CQRS approach.
Okay, so we're ready to move past a data-centric approach and laser focus on behavior. We'll make some classes called "Entities" and we'll put a bunch of properties and methods on these classes. Then we'll use a mapping tool such as an ORM to map these Entities onto a database schema for the sake of persistence. At first glance this appears to be a marked improvement over a procedural approach. However, it fails to take full advantage of some critical benefits of truly object oriented design. There are many issues which could be addressed regarding the burdens put on such a beast which so often passes for a "domain model." I'll focus on three in particular.
First, mapping entities onto a database schema invariably leads to leaky abstractions. Even when the database is built from the domain model, persistence specific concerns often leak into the domain objects. Some have made monumental attempts to prevent entities from having knowledge of the fact that they are to be persisted to a database. Others have embraced persistence concerns in the domain as with the Active Record pattern. But with domain objects so tightly related to database schemas, aren't we still focused on data? Isn't the proverbial database-tail still wagging the domain-dog? Must such compromises be made in the name of expediency or sticking with familiar tools and approaches?
Second, in the context of a domain model, the benefits of encapsulation can hardly be overstated. Put in simpler terms, property getters are evil. "But wait," you say, "how can a system even function if I have no exposed properties from which to determine the state of objects in my application?" To be clear, this requires a fairly drastic mind shift, especially for developers who are used to frameworks and tools full of property getters and setters (WinForms, WPF, Entity Framework, ASP.NET etc.). But such frameworks and tools by definition do not represent a business domain. Yet we often use such code as an example of "good code" for any and all circumstances. While such code may fit the context of a framework or toolkit, it has no place in a true, object-oriented domain model.
[Side bar on encapsulation for over achievers]
Developers who are used to exposing property getters on aggregates or entities often struggle to get their heads around such radical encapsulation. How can a class be useful when its innards are isolated from contact with the outside world? Aha! This is, in fact, a misnomer. It's not that such a class has no contact with the outside world, but that instead it maintains control over the way in which it communicates with the world outside its bounded context. In Episode 6 I will go into more detail regarding Domain Events, but for now the basic concept boils down to a simple idea - your domain classes should not allow direct manipulation of, or direct exposure to, their inner state. Instead, they will provide access to--or publish--events which represent the internal state changes which have occurred within their encapsulated boundary.
In case you missed it before, I did in fact say that getters are evil. While setters can be evil in their own right, many developers believe they are achieving encapsulation when they allow for public getters but use private setters. This simply does not count as encapsulation. Any time you directly expose the internal state of a domain object, you increase the fragility of the overall system. Exposed property getters make domain objects difficult to change. It also can cause rippling effects when such changes radiate out to all other classes which directly depend on the domain object. One more thing before we move on - the incessant need to check the state of objects through properties is symptomatic of the data focus creeping back into a place where we ought to be focused on behavior.
Finally, when we ask our domain to fulfill multiple roles, we violate the Single Responsibility Principle. What many developers refer to as a domain model fulfills multiple roles - enforcing referential constraints, performing data validation, invoking business logic, answering query requests, etc. These are not minor infractions of SRP but full blown felonies. In contrast, domain driven design principles allow us to create a true domain model which performs a singular function - representing the business domain in terms of behavior and messages. Domain objects perform commands, while keeping their internal state hidden from the client code. This allows for ideal levels of cohesion and greatly simplifies the domain objects, allowing them to focus on implementing the business specific behavior of the system.
How do you give your domain a break? You remove any and all direct or indirect reliance on the leaky abstractions of persistence from creeping into your domain model. You radically conform to the basic object oriented principle of encapsulation, avoiding all getters and setters, and only exposing command methods on your domain objects. Finally, you remember to adhere to the Single Responsibility Principle all along the way. Tempted to make that domain object fulfill multiple responsibilities? Something's not right, so take the time to think about what's wrong and what you can do about it besides exposing internal state to client code.
What if leaky abstractions could be completely removed from domain objects, not for the sake of some purist's notion of Persistence Ignorance, but for the sake of true OOP goodness, and all the benefits thereof? What if domain objects could focus on behavior and become freed from querying concerns? What if objects in your model fell within the parameters of the SRP? You'd have a far more maintainable and comprehensible system. In addition, you'd open yourself up to intriguing characterstics which would be extremely difficult to attain in a typical ORM-backed modelling approach. In the next episode, Beyond CAP, we'll dig deeper into one particularly interesting characteristic which becomes an option when working with a truly encapsulated domain - Eventual consistency. See you next time!