Always Valid — A Myth or a Tangible Reality?
max-arshinov
Posted on September 11, 2023
In the vast landscape of object-oriented programming (OOP), the division into "rich" and "anemic" domain models has crafted distinct pathways for developers. Unlike in functional programming (FP), where this bifurcation is less pertinent due to the paradigm’s distinctive approach to handling data and behavior, OOP offers a rich ground for this exploration. This piece aims to journey through the intricacies of OOP, with occasional references to insights from the FP domain.
Rich Model
Envisioned by Martin Fowler and Erik Evans, the rich model emphasizes a holistic approach where:
- Data structure and behavior reside harmoniously in one class, fostering cohesion.
- The code embodies the business domain's structure, promoting class names that resonate with the business logic rather than adhering strictly to design patterns or frameworks.
- Maintaining object invariants is central, facilitating non-contradictory states and lending a robust foundation to the architecture.
Anemic Model
Conversely, the anemic model, often perceived as an anti-pattern by critics, delineates data and behavior, resulting in:
- Separate entities and services to house data structures and their operations respectively.
- A focus on patterns and frameworks rather than mirroring the business domain through code, potentially making the deciphering of business rules through code analysis a demanding task.
- A relaxed approach to invariants, with a portion of practitioners negating the necessity to do so. Despite its criticisms, proponents argue for its alignment with SOLID principles, seeing it as a future-forward approach.
Despite Fowler and Evans labeling the anemic model an anti-pattern, suggesting that development teams failed to model the domain in an OOP style correctly, many programmers are convinced that it is precisely what is needed — some even see it as the future. Moreover, it strictly adheres to SOLID principles.
Unraveling the Origins of the Split
Tracing the genesis of this split is complex. A pivotal role might have been played by:
- Overdominance of ORM: The prevalent use of ORM, underscored by the notable download statistics of Entity Framework, hints at ORM’s overwhelming presence in web applications.
- ORM conventions are not always rich-model-friendly, so ORM users tend to implement anemic models.
- Simplicity of Implementation: The anemic model often becomes a go-to due to its straightforward implementation, despite the rich model holding theoretical allure.
Always Valid — A Myth or a Tangible Reality?
In domain modeling, "always valid" refers to a principle or strategy where the objects or entities within the domain are always in a valid state. It is a means of maintaining the integrity of a system by ensuring that changes to an entity always result in a valid state for that entity.
Invariants are conditions that are always true for a particular entity. Under the "always valid" strategy, invariants are rigorously maintained, meaning an entity cannot exist in an invalid state with respect to its defined invariants.
Maintaining object invariants is essential when implementing the domain the "rich way". The contentious debate surrounding the viability of maintaining “always valid” states veers towards a skeptical lens. Despite its conceptual appeal promoting error detection at the compilation stage, it meets practical hurdles.
To ensure the correctness of the state of any object in a mutable environment, we will have to equip any setter with a protective structure, for example, like this:
public class User
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new Exception("Name is required");
}
_name = value;
}
}
}
This approach not only heavily clutters and noisifies the code but also really does not get along with SOLID, because such classes have two reasons for changes: data storage and validation. To not violate the principle of single responsibility, we could declare a separate validator interface, implement it, and transfer the validation logic to the corresponding implementation.
public interface IValidator<T>
{
ValidationResult Validate(T obj);
}
Moreover, sometimes it may simply be impossible to ensure the correct state of objects. Business people have realized a long time ago that the likelihood of making a purchase in an online store decreases depending on the number of required forms to fill out. Ideally, there should only be one huge "buy" button and some way to contact the user. The call center can fill out other "necessary" fields after receiving payment.
On the other hand, the more the store knows about the customer, the better it can set up marketing campaigns to show more relevant ads, which are more likely to make them go to the store's website to buy something again and again.
Thus, in some cases, there should be a minimum of required fields, and in others — a maximum. This means that in different contexts, the rules for the "mandatory" fields of a class can be different, and the class instance cannot always be in the "right" state, because "correctness" depends on the current context.
The Problem of Universals
It is amazing that long before the invention of computers, similar questions were first raised in known human history by Plato and Aristotle. I will not delve too deeply into ontology. Hopefully, the ancient Greek philosophers forgive me for a quite free interpretation of their ideas...
Imagine a unicorn. Not a specific one, but an abstract concept. Now imagine an object that belongs to the “unicorns” class. Here I interpret the terms object and class broadly: not in the sense of OOP terminology, but as a category that includes all possible unicorns and one representative of this category. Do you think the toy on the picture below is a unicorn?
Ok, what about this one?
This is a one-horned goat named Lucky.
Lucky, is a character from the cartoon "Despicable Me 3," throughout which a little girl was looking for a live unicorn because the unicorn on the first picture is her favorite toy... and overall, well..., she found it. What do the ancient Greek philosophers and modern cartoons have to do with contextual validation? Surprisingly, the link is quite straightforward.
Contextual Validation and Invariant
Qualities necessary for an object to belong to a certain class: a unicorn is still a horse with one horn, not a sheep with two horns, one of which broke off.
Depending on the context, there are other types of validation that work in addition to the invariant. In the sales context, it is enough for the user to have contact details, and in the delivery context, the first and last name becomes mandatory, and possibly other necessary fields.
At first glance, such a division allows you to keep the model in a consistent state. In theory, yes, but in practice, there are pesky corner cases.
Stay tuned...
Posted on September 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.