No model is perfect, including the model of software behavior that we know as Object-Oriented. One of the most significant flaws in OO thinking is that everything is an object.
OO has certainly brought us forward in reasoning about machines. During that time, we’ve learned to model identity, interfaces, ownership, contracts, and many other useful concepts. We’ve also learned the drawbacks of shared state, remote objects, and persistence mapping, just to name a few challenges.
The original definitions of Object-Orientation do not necessarily insist that everything is an object. The founders simply defined the behavior of an object in software. Nevertheless, modern OO languages -- like Java, C#, and Ruby -- encourage us to make everything an object. If you ask an average developer if everything is an object, he will most likely say yes.
Entities and values
Domain Driven Design draws a line between entities and values. This distinction predates DDD, but the practitioners (most notably Eric Evans) are very explicit on this point. Entities have identity; values do not.
Identity is the property of an object that allows us to distinguish it from other objects. Even if two objects have exactly the same state, they are two different objects.
Identity implies consistency. Two observers looking at the same object will see the same behavior. If one of the observers acts upon the object and changes its behavior, the other one sees that change as well. Peers can use consistency to collaborate through a shared object.
Values, on the other hand, do not have identity. When you pass a value, the recipient does not receive the same object as you. They receive a copy.
Since values do not have identity, they do not have consistency. You cannot change a value to collaborate with a peer. Taken to the extreme, you cannot even rely upon self-consistency. If you change a value, you cannot be sure that you yourself will observe the change in behavior. Therefore, values are usually treated as immutable.
Toward a complete system of behavior
While we as an industry have found the distinction between entities and values to be necessary, we have not found it to be sufficient. Problems still arise in real systems when we have just these two classifications.
The identity of entities allows peers to collaborate via shared state. But when those two peers are on different threads, different processes, or different machines, shared state becomes a challenge. Consistency is promised, but cannot be guaranteed. Entities are not sufficient to solve distributed computing problems. Just look to DCOM if you need convincing.
Persistence is also a problem. The well-documented Object-Relational Impedance Mismatch plagues OO systems backed by Relational databases. Network, Object, and Document databases map better, since these systems treat objects themselves as persistent. Nevertheless, persistent entities pose a challenge. Two peers can read the same entity, but yet still end up with two copies. Unless the database management system is careful about maintaining consistency, the version of the entity in memory is stale once it is read from the database.
Mapping and change propagation are desirable features of software, but not directly satisfied by either entities or values. Consider declarative UI frameworks, such as HTML and XAML. These are mappings of object behavior into visible structure. Entities, having identity, will change. When those changes occur, the visible structure should update. Features such as HTML templating and XAML data binding have been created to propagate changes to the UI, but these are not part of a complete OO model. Additional imperative constructs -- message busses or AJAX calls -- are required to make these solutions work. Functional programming languages have typically been much better at mapping than OO languages have.
The entity-value distinction is insufficient because it considers only one dimension of behavior: identity. If we add the dimensions of persistence and consistency, we can define a system that is both necessary and sufficient for describing software behavior.
Facts
To solve the problem of shared state, let us add the classification of “fact”. Two peers changing a shared entity are relying upon consistency to collaborate. But consistency cannot be guaranteed in a distributed system. We therefore define a fact to be immutable.
Since a fact is immutable, two peers observing the same fact will observe the same state. But this alone is not enough to facilitate collaboration. Each peer must be able to make changes by expressing related facts. We cannot guarantee that other peers will see these new facts immediately. But we can guarantee that they will see these new facts eventually. “Eventually” might be a very long time if peers remain disconnected, but facts will flow once the connection is reestablished.
Now that we have facts to solve the shared state problem, we can take that responsibility away from entities. So we mandate that an entity is no longer consistent in a large scale. It is only consistent within a narrow scope. Ideally, that scope is a single thread, but it is certainly a single process on a single machine.
Since the scope of an entity has been constrained, we must also mandate that it is not persistent. The narrow scope must end not only at a thread or process boundary, but also at a temporal boundary. A new process started at a later point in time cannot load that same entity from a database. It can only load facts.
Projections
To solve the problems of mapping and change propagation, let us add the classification of “projection”. A projection is an object that has no state of its own. It merely delegates all of its behavior to other objects. Changes to those objects are immediately visible in the projection.
Views such as HTML pages or XAML controls are projections. They simply transform the behavior of other objects into a structure that the user can observe. Any actions of the user are applied to the underlying objects. Similarly, changes to the underlying objects are immediately propagated to the structure that the user sees.
The MVVM pattern has emerged in the XAML space as a way of projecting Model behavior onto the View. XAML views can only data bind through properties. Models don’t always expose the properties that the view requires. It is therefore necessary to project all visible behavior through properties of an intermediate object: the View Model. So a View Model, like the View that it serves, is a projection.
in summary, the classifications of object are:
Entity |
Has identity |
Transient |
Mutable |
Constrained consistency |
Value |
Has no identity |
Transient |
Immutable |
No consistency |
Fact |
Has identity |
Persistent |
Immutable |
Eventually consistency |
Projection |
Has identity |
Transient |
Immutable |
Constrained consistency |
This classification taxonomy is necessary and sufficient to model the behavior of distributed collaborative software systems. There are certainly systems that fall outside of this set. This taxonomy may not be necessary and sufficient for those systems.
What about dependency?
Would it be safe to say then that Entities are independent data, while Values and Projections are Dependent data? And I guess Facts would be categorized as dependent, definitive state.
Dependency
Great question.
You can describe properties of an object as dependent or independent. But you cannot necessarily describe the entire object in that way. For example, a CheckingAccount -- an immutable fact -- might have a dependent Balance property.
That said, the kinds of properties that an object can have are constrained by its classification.
Projections, though they are immutable, can have dependent properties. These properties reflect the behavior of other objects. A projection cannot have independent properties.
Entities can have both independent and dependent properties.
Facts, as illustrated above, can have dependent properties. They will always reflect the behavior of related facts. A fact can simulate an independent property by creating related facts.
Values can have neither dependent nor independent properties. That would require identity. A value is simply a record produced by some other object, one that does have identity. You have to look to that source object to determine where it lives in the dependency chain.