I had to comment on Jeffrey Palermo’s recent post entitled ‘The fallacy of the always-valid entity’ as I both disagreed with what he was suggesting and wonder if he has missed the point. I thought I’d follow up here with an example that demonstrates how I implement an always valid entity. Many other comments on the post allude to similar approaches also. If you haven’t already, read Jeffrey’s post before reading on.
Jeffrey’s UserProfile class initially throws exceptions in property setters if null values are supplied. These are invariants. I think he’s arguing against this approach. He then evolves the example to present a validation framework that allows a collection of rules to be asserted against an instance of the entity and reports back failed rules. The rules are intended to be context specific validations, but the context is missing (at least from the example given).
He additionally talks about legacy data and the need to allow for invalid entities for unspecified reasons. He concludes with
“It is futile to attempt to keep entities always-valid. Let bad things happen to them, and then validate”.
No! I disagree.
If we can’t know an entity is valid anywhere we use it then we can’t afford to trust it. We end up with code where, before we use an entity, we go and check that it is in a suitable state for us. These ‘entities’ are just data structures. They are missing behaviour that enforces internal consistency.
So, how can we build entities in C# that are always valid?
- No public property setters. This stops the entity being used as a data structure. It is no longer possible to break the internal state of an entity by abusing setters.
- The instance constructor leaves the entity in a valid initial state. It sets sensible defaults for fields, it takes parameters to override defaults and other parameters to provide values for fields with no sensible default.
- Entities are updated by a context-specific ‘command’ method that acts atomically on the instance.
Let’s rework Jeffrey’s UserProfile class to make it an always valid entity, assuming that:
- The user’s Name must not be empty,
- The Gender can be one of male, female or unknown.
- The JoinedDate cannot be null, but can (for now) contain any valid date. You might argue that future dates should be invalid or that the JoinedDate be optional—let’s just ignore this at the moment; is is immaterial for this example.
- The LastLogin must not be earlier than the JoinedDate.
Whilst I personally wouldn’t want this code in production, lets look at what it has achieved.
- Gender is an enumeration. This restricts us to valid values at compile time. However, we explicitly recognise a business requirement that the gender may not be known, rather than allow an indeterminate null state.
- I prefer the entity name User rather then UserProfile. We are modeling a domain with Users. A user’s profile is the properties of the User entity. UserProfile screams data structure.
- I’ve opted for automatic properties with private setters for most properties. This means the presentation layer can read my entity to create a View Model or for direct binding, but updates through properties are not possible.
- Where I have an invariant I wish to enforce, I’ve opted to use the private property setter to enforce this. This is a compromise and we must trust our developers not to short-circuit the setter, either by malice or ignorance. This is a pretty weak form of validation (but good enough for this example). Bear with me, I’ll blog on this shortly.
- ‘Command’ methods update the entity in a controlled manner. They return void. They have atomic behaviour, by which I mean a logical update is made in one call to the instance.
- The business has asked that the user can update their profile. The name must not be empty.
- The business also wants to record the last login time for users. Note that we aren’t using DateTime.Now here, as this has two shortcomings: it hurts testability and explicitly ties the time of the execution of the RecordLogin method to the time of the executing server. RecordLogin might be at the end of an message queue that processed the message minutes after the login actually occurs.
- The constructor and the UpdateProfile method both use the Name setter and therefore the non-empty name invariant is enforced.
- Note that users of NHibernate or other ORM frameworks might require a default constructor. If so make the accessability as restrictive as possible (that’s ‘protected’ for NH).
- We can use a validation framework that implements a notify pattern (as in Jeffrey’s example) if we wish. I kept it simple for this example.
Given the rules above, it is not possible to get this entity into an invalid state. As for bulk loads and legacy data, this entity will insist that the external data is correct. Use a data cleansing tool to make sure the data meets your domain models requirements.