Even though I knew about value objects for a long time, it was only recently that I decided to get my hands dirty and test their full potential. Interestingly, none of the big projects I have worked on before utilized this simple but incredibly useful concept. Moreover, most of the developers I happened to work with never used value objects in their practice too.
“In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object” – Wikipedia
However, the wikipedia article only describes the implementation details, while providing zero information on use-cases and benefits of using value objects. Also, for some reason the article is biased towards mutable value objects that are copied on assignment. I prefer the definition from Martin Fowler’s book “Patterns of Enterprise Applications“, which presents value objects as usually being entirely immutable, never copied and freely assigned by reference.
“A general heuristic is that value objects should be entirely immutable. If you want to change a value object you should replace the object with a new one and not be allowed to update the values of the value object itself – updatable value objects lead to aliasing problems” – Martin Fowler
Anyway, value objects are objects, as in OOP and as in “pieces of data bundled with related methods”, however, value objects are specialized to contain tiny bits of domain data and related domain logic. I will emphasize the important part, that not only value objects wrap some piece of data, but they also allow to constraint that data and define the ways that data can be interacted with. Value objects effectively provide you a place to put tiny elusive bits of logic or constraints that would have become implicit (and easy to forget) otherwise. Moreover, value objects are free to use any of the domain terminology (see: Ubiquitous Language), which makes the code much easier to understand.
To clarify this concept, let’s walk through some examples from my own experience, on how refactoring tiny bits of domain concepts into value objects resulted in code much cleaner than it was originally.
It’s Date Ranges, Date Ranges all the way down!
Recently I was working on a custom telecom BSS platform development project. Most of domain logic in this system operated on date ranges: fixed term contracts, pricing strategies, discount validity, billed period range, etc. Those ranges were always mapped to tuples of database columns like dateStart
and dateEnd
, and in absolutely all cases dateStart
was not allowed to be null, while dateEnd
being null represented “infinity” or “open-ended contract”. The concept of open-ended range was implicit, transferred verbally from developer to developer. Basically, you had to just remember what dateEnd = null
represents. However, even though there is a clear (while very tiny) bit of domain logic there, nobody deemed it worthwhile to introduce a separate class to encapsulate this concept.
Extracting the concept
The first, simple version of the introduced class was a simple wrapper for the two fields, with two builder methods and a private constructor:
class
ContractDateRange
{
private
final
LocalDate start;
private
final
Optional<LocalDate> end;
public
static
ContractDateRange
fixedTerm
(LocalDate start, LocalDate end) {
return
new
ContractDateRange(start, Optional.of(end));
}
public
static
ContractDateRange
openEnded
(LocalDate start) {
return
new
ContractDateRange(start, Optional.empty());
}
private
ContractDateRange
(LocalDate start, Optional<LocalDate> end) {
this
.start = start;
this
.end = end;
}
public
LocalDate
getStart
() {
return
start;
}
public
Optional<LocalDate>
getEnd
() {
return
end;
}
}
Thus, these previously hidden concepts and constraints became clearly visible and enforced:
- The two fields are related and can not exist one without the other.
- These fields together represent a concept of “contract validity range”.
- Contract validity ranges can be of two kinds: fixedTerm and openEnded.
Another benefit is the safety of working with this class. The enforced constraints in this class’s API limit the ways one can interact with these “contract ranges”, therefore making it easier to use compared with two bland LocalDate
fields. Moreover, limiting the API highly decreases the chance of accidental errors.
Cultivating the concept
Later I noticed chunks of similar code in different places in the codebase. These chunks of code were performing some operations for each day of the given month covered by the given range, which is essentially an intersection of ContractDateRange
and YearMonth
. This is an example of what those chunks of code looked like:
//Some given month
YearMonth month = ...;
//Either first day of month, or first day of contract
ContractDateRange range = contract.getDates();
LocalDate startDay = max(month.toLocalDate(1), range.getStart());
//Either last day of month, or last day of contract
LocalDate endDay = !range.getEnd().isPresent()
? month.lastDay()
: min(month.lastDay(), range.getEnd().get());
//Loop through all the days between two datesfor
(LocalDate curDay = startDay;
curDay.compareTo(endDay) <= 0;
curDay = curDay.plusDays(1)) {
...
doSomeOperation(curDay);
...
}
Underlined are the variables derived from either month
or range
(or both), feature envy anyone? Moreover, it’s quite logical for any concept based on ranges to encompass some kind of range intersection operation. Given that the introduced value object existed to encapsulate the underlying concept of contract date ranges in its entirety, I’ve decided to extend it with a new method:
public
ContractDateRange
intersection
(YearMonth month) {
LocalDate startDay = max(month.toLocalDate(1), this
.getStart());
LocalDate endDay =
this
.getEnd()
.map(it -> min(month.lastDay(), it))
.orElse(month.lastDay());
return
ContractDateRange.fixedTerm(startDay, endDay);
}
To show the flexibility of value objects, we could even go as crazy as adding an iterator to the mix:
public
class
RangeIterator
implements
Iterator
<
LocalDate
> {
private
final
ContractDateRange range;
private
LocalDate current;
public
RangeIterator
(ContractDateRange range) {
this
.current = range.getStart();
this
.range = range;
}
@Override
public
boolean
hasNext
() {
return
!range.getEnd().isPresent()
|| current.compareTo(range.getEnd()) <= 0;
}
@Override
public
LocalDate
next
() {
if
(!hasNext()) {
throw
new
NoSuchElementException();
}
LocalDate result = current;
current = current.plusDays(1);
return
result;
}
@Override
public
void
remove
() {
throw
new
UnsupportedOperationException();
}
}public
class
ContractDateRange
implements
Iterable
<
LocalDate
> {
....
@Override
public
Iterator<LocalDate>
iterator
() {
return
new
RangeIterator(
this
);
}
....
}
Let’s try it out in action now by rewriting the old chunk of code using the new features added to the value object:
YearMonth month = ...;
for
(LocalDate day : contract.getValidRange().intersection(month)) {
...
doSomeOperation(curDay);
...
}
Notice how clear and easy to understand the domain logic is compared to the initial version:
YearMonth month = ...;
ContractDateRange range = contract.getDates();
LocalDate startDay = max(month.toLocalDate(1), range.getStart());
LocalDate endDay = !range.getEnd().isPresent()
? month.lastDay()
: min(month.lastDay(), range.getEnd().get());for
(LocalDate curDay = startDay;
curDay.compareTo(endDay) <= 0;
curDay = curDay.plusDays(1)) {
...
doSomeOperation(curDay);
...
}
So, to sum it all up: code is clearer and shorter; feature envy is no more; and to top it all – tests are much easier to write as they can be more fine-grained now.
Reinventing the wheel, or isn’t it?
The thought crawled into my brain that performing different “range operations” is probably a very common task. What if someone already wrote a library to do those? Guava was an answer with its two classes: Range and ContiguousSet.
Range
is designed to operate on data ranges of any type implementing Comparable
interface, including LocalDate used in my case.
ContiguousSet
is a bridge between Range
and Set
classes. It presents a view of given Range
object as a Set
. However, Comparable
interface is not enough to produce such a view, therefore it requires an implementation of DiscreteDomain interface, which acts as a factory for intermediate range values.
Nevertheless, the two classes can be combined together to perform same operations:
YearMonth month = ...;
Range<LocalDate> month = Range.closedOpen(
month.toLocalDate(1), month.plusMonths(1).toLocalDate(1));
Range<LocalDate> range = contract.getValidRange();
Set<LocalDate> days = ContiguousSet.create(
range.intersection(month), LocalDateDomain.INSTANCE);for
(LocalDate day : days) {
...
doSomeOperation(curDay);
...
}
After extracting this code into helper methods, it can be further simplified to:
YearMonth month = ...;
for
(LocalDate day : contract.validDaysIn(month)) {
...
doSomeOperation(curDay);
...
}
So, had we “reinvented the wheel” by introducing our value object, the ContractDateRange
? Should we just admit our defeat and refactor it all to use Guava’s Range
class instead?
The answer is a definite NO. The ContractDateRange
class and Range
class exist for different purposes and have different responsibilities. ContractDateRange
is a quantified piece of domain logic. Its intent to encapsulate a piece of domain and to make code clearer by using specific domain terms like ‘openEnded’, ‘fixedTerm’, and whatever else our domain may need in the future. On the other hand, Range
class is an implementation of generic range logic. It was created with intent to make operations with different kinds of ranges easier for programmers, and that’s it.
Essentially, we have two distinct pieces that can fit the definition of a API and implementation of a tiny imaginary module. And we can combine both of them to get the best of two worlds, a strict domain-driven API and a clean Guava-driven implementation:
class
ContractDateRange
implements
Iterable
<
LocalDate
> {
private
final
Range<LocalDate> range;
public
static
ContractDateRange
fixedTerm
(LocalDate start, LocalDate end) {
return
new
ContractDateRange(Range.closed(start, end));
}
public
static
ContractDateRange
openEnded
(LocalDate start) {
return
new
ContractDateRange(Range.atLeast(start));
}
private
ContractDateRange
(Range<LocalDate> range) {
this
.range = range;
}
public
ContractDateRange
inMonth
(YearMonth month) {
return
new
ContractDateRange(range.intersection(RangeUtils.fromMonth(month));
}
public
ContractDateRange
intersection
(ContractDateRange other) {
return
new
ContractDateRange(range.intersection(other.range));
}
public
Iterator<LocalDate>
iterator
() {
return
ContiguousSet.build(range, LocalDateDomain.INSTANCE).iterator();
}
...
}
Another example: Monthly prices
The project also operated on prices a lot, and one of the key concepts was “monthly price”. Entities with “monthly price” used it to calculate a fee or discount proportionally to factual days they were “active” during a month. For example, service with monthly price of 5 Eur should generate a bill of only 2.5 Eur if it was active for only half a month. Discount of 3 Eur/month should be scaled down to 1 Eur if applied to 10 out of 30 days, etc.
These “monthly prices” were stored in plain BigDecimal
fields. However, a brief analysis of the concept clarified these properties and constraints:
- Monthly price can be only positive or zero (while BigDecimal is allowed to be negative)
- Monthly price can only have precision of 2 digits (while BigDecimal is allowed to be of any precision)
- Monthly price is never used by itself. It is always scaled to the amount of days in some kind of date range.
We can see an obvious mismatch between the properties of BigDecimal
class and “monthly price” concept. Similarly to “date range” problem, BigDecimal should be a part of Implementation layer but not an API layer. Therefore, we ended up wrapping it into its own class:
public
class
MonthlyPrice
{
private
final
BigDecimal amount;
public
static
MonthlyPrice
free
() {
return
new
MonthlyPrice(BigDecimal.ZERO);
}
public
static
MonthlyPrice
fromPositive
(BigDecimal amount) {
Preconditions.checkState(amount.signum() >= 0);
//Enforce the scale, or maybe even throw the IllegalArgumentException if the scale is invalid
return
new
MonthlyPrice(amount.setScale(2, BigDecimal.ROUND_HALF_UP));
}
private
MonthlyPrice
(BigDecimal amount) {
this
.amount = amount;
}
public
boolean
isFree
() {
return
amount.signum() == 0;
}
public
BigDecimal
calculate
(Range<LocalDate> range) {
Preconditions.checkState(range.hasLowerBound() && range.hasUpperBound());
if
(isFree()) {
return
ImpreciseMoney.ZERO;
}
... Calculations ...
}
}
Such “MonthlyPrice” class clearly states its intent and constraints, no longer it is just “some number of arbitrary precision”. Moreover, it hides the underlying implementation (the BigDecimal
field) and only exposes and meaningful calculation over the range of dates.
Value Objects, or not?
Applying domain driven design is not an easy task and requires good understanding of the domain, sharp analysis skills and some modeling experience. However, simple concepts like “money” or “date ranges” are the perfect candidates for experimentation in this direction. Small concepts are easy to notice, analyze, extract into value objects and refactor, and in the worst case scenario – they are easy to revert. Try to notice bland fields or pieces of code that have some larger concept behind them, usually those can be extracted into separate reasonable classes. For an untrained eye such bland pieces with implicit rules are easy to miss, especially as programmers get used to them over time. Nevertheless, take some code and try extracting the tiny hidden domain concepts and see what happens as the results can be stunning.