Home
/
News
/
Clean code with Value Objects

Clean code with Value Objects

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.

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:

  1. The two fields are related and can not exist one without the other.
  2. These fields together represent a concept of “contract validity range”.
  3. 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 dates
for (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.

services

Our services fall into two principal groups

Strategy, UX and Product-centric services

Custom application development
User experience design
Development of new digital products and services
Strategies for digital transformation

Technology-centric services

Business intelligence and analytics
Migration and customisation
Application management
Enterprise application integration
Get in touch

Get in touch to discuss your ideas and needs

Aldas Kirvaitis
Business Development UK and Switzerland
+44 750 827 9430
aldas.kirvaitis@metasite.net
4 Princes Tower 
97
Rotherhithe st
London SE16 4NF UK
Arnoldas Dapkus
Business Development EU and U.S.
+370 612 82003
arnoldas.dapkus@metasite.net
Seimyniskiu st 19
Vilnius LT-09234
Lithuania, EU
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Get in touch

Let’s work together

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Aldas Kirvaitis
Business Development UK and Switzerland
+44 750 827 9430
aldas.kirvaitis@metasite.net
Arnoldas Dapkus
Business Development EU and U.S.
+370 612 82003
arnoldas.dapkus@metasite.net