DateStart + DateEnd
posted in design on • by Wouter Van SchandevijlValueObjects
A ramble against objects containing DateStart
& DateEnd
pairs of properties.
Some of their equally evil relatives:
DateStart
&TimeSpan
orDay/Month only in DateTime
, etc.decimal Money
&string Currency
decimal X/Y
and/orWidth/Height
This is the case for the ValueObject:
A small simple object, like money or a date
range, whose equality isn’t based on identity.
— Martin Fowler
Basic Example
Consider the following class:
class Person {
public string FirstName;
public string LastName;
public string Initials;
public string Address;
public string Street;
public int HouseNumber;
public string Telephone;
public string Mobile;
public string Email;
public string WorkAddress;
public string WorkStreet;
public string WorkHouseNumber;
public DateTime ActiveStart;
public DateTime? ActiveEnd;
// ... and many more?
}
And how beautiful it could’ve been:
class Person {
// The first version was quite obfuscated, while in this version it's
// quite obvious what's in it by merely glancing at the declaration.
public Name Name;
public Contact Contact;
public Address HomeAddress;
public Address WorkAddress;
public DateRange Active;
}
class Name {
// Please don't repeat the Name for each property here
// Considering the current class, we know the context!
public string First;
public string Last;
public string Initials;
}
Advantages
For the IDE
Instead of a long list of properties that may be difficult to navigate in your IDE,
where it is difficult to spot that one property you need,
one could instead split it up in things that logically belong together to keep intellisense small and focused.
Keep data that belongs together - together!
For the UI
Such stuff is usually displayed together. All the way down to the frontend, passing
an Address
to an AddressComponent
is very preferable over passing those properties
in separately.
For Expressiveness
Without our precious ValueObject
, we would end up creating all sorts of helper classes
for often sought after functionality, all scattered around the codebase, some perhaps
getting duplicated by different programmers or even for different UserStories.
Some of these helper methods would be much better off being members of a ValueObject
instead. We get better IDE discoverability and rather than having to pass some or all properties of an entity into an algorithm,
we invoke a well-named method on the ValueObject in true TellDontAsk style.
And isn’t that what Object Oriented Design is all about? 😇
Recognition
If you look, ValueObject
opportunities will present themselves.
Technical ValueObjects
Classes are very technical in nature and so there’ll be purely technical candidates
like, for example, a (still?) popular one:
CreatedOn + CreatedBy
and UpdatedOn + UpdatedBy
.
Why clutter (all) your entities with 4 superfluous properties if you can hide them behind
a single Audit
ValueObject?
Domain ValueObjects
It is the ValueObjects
derived from the Business Domain that often offer the
most value however. Helper functions for a DateRange
only go as long as a IsIn(DateTime)
.
For Domain ValueObjects, this can be whatever rabbit Marketing pulls out of their hat
depending on the current lunar phase, so the possibilities are endless!
Not Invented Here Syndrome
I never thought I’d ever say this: Do not use a package. Yes, there is that well-known and fully-fledged DateRange
package out there.
80% of its features are probably not useful for your problem domain though. Again you get a long list of methods that basically
clutter your IDE. Super handy features that are applicable only in your domain will most likely not be available, as your business
probably has very particular requirements.
Business Domain Example
// A domain where we work with measured stuff
class MeasurementResults {
public decimal Value1;
public int Value1Precision;
public decimal Value2;
public int Value2Precision;
public decimal Value3;
public int Value3Precision;
}
// Could be turned into
class MeasurementResults {
public Measurement Result1;
public Measurement Result2;
public Measurement Result3;
}
class Measurement {
public decimal Value;
public int Precision;
}
Random Thoughts
The True Enemy?
Well I guess that missing ValueObjects
are often an unfortunate side-effect of using a relational database.
Your SQL database simply likes things flat. Your ORM probably has something like Custom Types (Hibernate),
Complex Types (EF) or Owned Entities (EF Core). If you use an ORM it can probably do the ValueObject creation
for you.
Derive, Don’t Store
If you can calculate something from existing state, do so. Once stored separately, it will, at some point be out of sync, I guarantee it.
class Person {
public string FirstName;
public string LastName;
// This simply does not get out of sync
// Otherwise each day is trying to remember if the
// FullName starts or ends with the FirstName
// +You have less state to manage, so hurray!
public string FullName => $"{FirstName} {LastName}";
}
Conclusions
Not creating a value object for coherent pairs of value types is a missed opportunity? 😂