In a project I am currently involved with, a core part of the system involves a couple of fairly complicated (at least to me :)) computations involving time, power, energy, fuel, volumes etc.
At first, we just implemented the computations “as specified”, i.e. we went ahead and did stuff like this:
1 2 3 4 5 6 |
public double GetRemainingCapacity() { double remainingRuntimeHours = remainingRuntimeSeconds / 3600; double remainingCapacityMWh = currentProductionKW * remainingRuntimeHours / 1000; return remainingCapacityMWh; } |
to calculate how many MHhs a given device can deliver. This is just an example, we have a lot of this stuff going on, and this will be a major extensibility point in the system in the future.
I don’t know about you, but I got a tiny headache every time I looked at code like this, part from trying to understand what was going on, part because I knew errors could hide in there forever.
To remedy my headache (and the other team members’ headaches), we started migrating all that funky double stuff to some types, that do a better job at representing – i.e. Power for power, Energy for energy, and so on!
All of them, of course, as proper immutable value types (even though we use classes for that).
And then we utilized C#’s ability to supply operator overloading on all our domain types, allowing stuff like this to happen:
1 2 3 4 |
public Energy GetRemainingCapacity() { return currentProduction * remainingRuntime; } |
where currentProduction is an instance of Power and remainingRuntime is a TimeSpan. And whoever gets the Energy that comes out of this, will never have to doubt whether its Whs, KWhs, or MWhs – it’s just pure energy!
Now, this may seem like a small change, but it has already proven to have huge ramifications for the way we implement our computations:
- There is no such thing as multiplying two powers by accident, or subtracting two values that cannot be subtracted and still make sense, etc.
- We have no errors due to wrong factors, e.g. like KWs mistakenly being treated as MWs
- We have gained a cleaner, more intuitive core API, which is just friggin’ sweet!
In retrospective, I have done so much clumsy work in the past that could have been avoided by introducing proper value types for core domain concepts, like e.g. money, percentages, probabilities, etc.
I usually call myself “pretty domain-driven”, but now I realize that there’s an entire aspect of being just that, that I have overseen.
Do you think you’re domain-driven?