I'm trying to create a Required Field Value Object class that will be reusable across Entities in my Domain Model. I'm still learning the C# syntax (been coding in VB.net forever). And I'm all new to DDD (but have at least read several books).
My goal is to design a Value Object called RequiredField<T>
that can accept just about any object (e.g. value type, reference type, or nullable value type (int, string, int?)) which can then be validated before allowing it to become part of an Entity's state. I would use this Value Object any time my entity has a required field (e.g. id, primary key, name, or any other piece of state deemed necessary to have a valid entity). So the Entity will define properties as RequiredField<T>
whenever I have a simple piece of data that is required in order to have a valid Entity.
So I've been fiddling around with this class, and I'm getting closer, but it seems like every time I think I've just about got it, I run in to another stumbling block. Here is what I would like the usage to look like, from my Entity class:
public class PersonEntity
{
public RequiredField<long> ID { get; private set; }
public RequiredField<string> Name { get; private set; }
public RequiredField<DateTime> DOB { get; private set; }
// define other non-required properties ...
public PersonEntity(PersonDTO dto)
{
ID = new RequiredField<long>(dto.ID);
Name = new RequiredField<string>(dto.Name);
DOB = new RequiredField<DateTime>(dto.DOB);
// set other non-required properties ...
}
}
The corresponding DTO that is used to construct the Entity (created in the repository, or application service from the UI, or from a WebService, etc):
public class PersonDTO
{
public long? ID { get; set; }
public string Name { get; set; }
public DateTime? DOB { get; set; }
}
Note that I really desire the ability to have the DTO be just a data bag (that's basically all a DTO is right?). If I don't allow nullable types here, then I have to do validation somewhere else, and the whole point is to let the Value Object in the Entity do the work (right?).
Finally, here is what I have so far on my RequiredField<T>
class. Note that this code doesn't compile.
public class RequiredField<T>
{
private T _value;
public T Value
{
get { return _value; }
set
{
// handle special case of empty string:
if (typeof(T) == typeof(string) && string.IsNullOrWhiteSpace((string)value))
// but (string)value doesn't work: "Can't convert type 'T' to 'string'"
{
throw new ArgumentException("A required string must be supplied.");
}
else if (value == null)
{
throw new ArgumentException("A required field must be supplied.");
}
// also need to handle Nullable<T>, but can't figure out how
else if (Nullable.GetUnderlyingType(typeof(T)) != null)
// need to check value, not T
{
throw new ArgumentException("A required field must be supplied.");
}
_value = value;
}
}
public RequiredField(T value)
{
Value = value;
}
// below is the start of failed attempt to accept a Nullable<T>
// don't like the fact that I have validation going on here AND in the setter
public RequiredField(object value)
{
if (!value.HasValue)
{
throw new ArgumentException("A required field must be supplied.");
}
Value = value.Value;
}
}
So I've gotten myself pretty far into a mess, and I started to question if I'm attempting to do the right thing here. But if I am on a good start, what gets me over the finish line?
I started to question if I'm attempting to do the right thing here.
Good, you should be questioning that -- the literature recommends that you to go the other way.
I'm trying to create a Required Field Value Object class that will be reusable across Entities in my Domain Model.
That's probably the wrong goal to have.
Evans Chapter 5 describes a number of tactical patterns for expressing the domain model, including the ValueObject pattern. The critical insight in the pattern is that it is important your software describes what the value represents, rather than how it is implemented in memory.
public RequiredField<DateTime> DOB { get; private set; }
So this declaration is trying to tell us that this field is part of the query api for this entity, that the value is required, that in memory the state is a handle to a data structure that supports the DateTime api.
What's missing is that the data is the DateOfBirth.
There are a couple of problems here -- first, RequiredField is not drawn from the ubiquitous language; it's artificial programming vocabulary that means nothing to your domain experts.
Furthermore, it fails to model DateOfBirth correctly (think about what date of birth is -- a local date as measured by a clock in the locale where the person was born). Time arithmetic on a DateOfBirth doesn't work.
What that means, among other things, is that you want to avoid confusing DateOfBirth with other time like things where date arithmetic does work.
So your constructor should look like
public PersonEntity(PersonDTO dto)
{
ID = new Identifier(dto.EY);
Name = new Name(dto.EID);
DOB = new DateOfBirth(dto.DOB);
// set other non-required properties ...
}
This gives us a natural place to put our data validation (in the constructors of the value types)
Furthermore, you probably want to mark the optional fields, rather than the explicit ones, when you are in the model. Compare C# with the usage of Optional in Java.
Spelling this point another way, RequiredField
is an algebraic data type, roughly corresponding to Unit
-- you've created a type of types that can only assume one type.
In messaging, you are more likely to want "optional" fields by default, because the flexibility to be forwards/backwards compatible with other implementations is valuable. You want to be able to read messages written by past versions of the model, and write messages that will be read by future versions of the model.
Same idea, different spelling -- the concerns at the boundary differ from those in the model
Effectively, it comes down to this; the state in the model is constrained, but the constraints live in the model itself -- once the state is extracted from the model (once you create the DTO), the constraints are gone. The data is just a byte array; reading the data back in, we re-apply the constraints so that the model doesn't have to constantly keep checking (in other words, the DRY principle shows up here).
the pragmatist in me didn't want to create a zillion different value objects when most of them just need to be required, and have no extra validation or behavior.
Even if there's no validation, and even if there's no "extra" validation, there's still the fact that substituting types for other types is an error, as far as the business is concerned -- we can represent both FamilyName and City as string, but that implies that they are interchangeable, which is not at all the case.
Put another way, nobody sane says int, strings, oh my god strings have encoding, its all too complicated, I'll just model everything as byte[].
See also
That said, the cost of being wrong may not exceed the work of being right. Composing boiler plate code is not fun, you may need to write your value types and primitive to value conversions in a language more suited to the task. Trade offs abound.
So my take-a-way from this is that I should define separate VOs, and have each of those use a RequiredField helper (or perhaps FluentValidation)? This would also make it easy to add differing validations or behaviors to individual VOs.
Common idioms
// constructors
new Value(...)
// factory methods
Value.of(...)
Value.from(...)
// Factories
api.newInstance(...)
// Builder
api.newBuilder()....build()
If that property is simply a description with no role in the domain logic/decisions, can it be a primitive and not a VO?
Note: if the property has no role in the domain logic/decisions, why include it at all?
It can be, yes, but it really shouldn't be. The value types are your corpus for a domain specific language you are using to model the business. Put another way, the domain behavior shouldn't depend at all on how the data is represented in memory.
Consider identities; they are an opaque value type. All you ever do is compare them. There is absolutely no reason that the model should ever need to peek through the veil to know if they have the same underlying data layout.
But even if I had
interface OpaqueValue extends ICanEqual {...}
I would still want
interface Whatzit {
interface Identity extends OpaqueValue {...}
}
interface Whoozit {
interface Identity extends OpaqueValue {...}
}
// CompileTimeError
Whatzit.Identifier target = source.getWhatzit().id