Protect Your Invariants!
This tutorial shows you how to handle constraints and validation inside your application. Read below to find out how this way can help you!
Join the DZone community and get the full member experience.
Join For FreeHow do you handle constraints and validation inside your application? Most developers put this logic somewhere close to their application's boundary. Actually, this prevents them from having a rich domain model that could ensure consistency.
Developers tend to get confused when they need to find a good place for their business logic. I suspect that most probably the reasons are related to all those bad examples circulating in the documentations of popular frameworks and bad habits from coding in old-fashioned enterprise platforms like J2EE. Most of the time, they're afraid to keep these vital pieces of information in the language elements most relevant to the target domain: I'm talking about the simple classes reflecting the "nouns" of the business logic. Often, you see something like the example below:
@Entity
public class ShoppingCart {
@OneToMany
private List<LineItems> lineItems;
// getters and setters
}
@Component
public class ShoppingCartService {
public void addToCart(ShoppingCart cart, LineItem item) {
// Logic related to adding a line item to the shopping cart:
// Preconditions to check, like availability of the item.
// Operations, like really adding the item to the cart.
// Postconditions and cleanup, like changing item's availabiltiy and transaction management.
}
}
There's an entity, which is just a placeholder for the database state. It's also reflecting the object-database mapping with some sort of a meta-language, done by various annotations. There is nothing else here, just getters and setters. The real operations located in another class suffixed as a "Service" "Processor" or a "Manager" (these name suffixes are some kind of an anti-pattern by themselves according to the book "Clean Code"). But, from the framework's point of view, the class lifecycle is often controlled by the framework itself. So, it's preferable not to hold state in the instances of the class: Either because the framework is keeping a single instance per class in a managed bean (simply as a singleton) or because multiple threads can access the instance's state at the same time, so thread safety is not guaranteed.
Note that I have not talked about anything related to the target domain. Nothing was mentioned about the cart: the capacity of it, the possibility of putting duplicated items in it, etc. But many things were explained about framework concepts, like thread management, ORM, bean lifecycles, and so on. So, it's important to mention that I suspect the framework documentation is intentionally keeping their examples like the one above. They need to minimize the amount of code representing the "hard facts" of the domain in their examples because it's irrelevant!
So, what's wrong with all of this? Simply domain logic is not in focus, and it's often scattered around in various places. Stateless operations are managing your domain by some sort of a "transaction script" leading us to an "anemic domain model." There's no place for the real power of Object-Oriented Programming in the form of polymorphism, nor for powerful design patterns, like composites, visitors, strategies, observers, and so on.
Sadly, this is especially true if we want to implement an invariant of the domain. These pieces of code are essential for keeping things in place: Essential for keeping our defect density low by catching programming errors early on. The invariants of our domain should be part of a domain layer, unit-tested, and easy to understand. Missing them from your domain will cause more complicated test cases as you try to tailor together all your business logic from different layers or modules of your application. These tests will either run slower or have a complex mocking mechanism and a lot of plumbing code to make the application run without all the clutter. This is not ideal if you aim to understand functional behavior by reading the test cases.
Should we put these invariants into our application boundaries in some kind of validation logic? Maybe we could use another framework or library for our aid? I say we should put these implementations very close to our domain instead. Capture them with simple language elements verifiable with a simple unit test.
Invariants VS. validation: Often, validation is placed on layer boundaries, while invariant located at the heart of your software
Defensive Programming; Design By Contract
To be honest, when I read about "Design by Contract" in the book "The Pragmatic Programmer," I became a great fan of the subject. Unfortunately, I didn't find good support for it in Java, so I just stopped experimenting with it. Later, I dropped the burden of finding a suitable library for all the work and started implementing invariants and preconditions with simple language elements. Sometimes I formulated these as assertions, and, sometimes, I just used some simplistic functions, but it always kept this kind of code close to the target domain.
Later, I got familiar with Domain-Driven Design by reading Eric Evans' book. Inside he talked about "Making Implicit Concepts Explicit." That was the point when I realized that invariants related to the domain should be kept with the target domain and should be treated separately from possible programmer errors or misformatted input. For instance, a date format is something from the latter while handling what happens if duplicated line items are in a shopping cart, are the former.
Two Examples: Bowling Game and Knight in a Chessboard
Okay, now let's look at two practical examples. In the first one, we will implement score evaluation software for a bowling game. The scoring rules can be explained in a few sentences, so I will copy them over to here:
Bowling Game Rules
The Game
The game consists of 10 frames. In each frame, the player has two rolls to knock down 10 pins.
The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.
Spares
A spare is when the player knocks down all 10 pins in two rolls.
The bonus for that frame is the number of pins knocked down by the next roll.
Strikes
A strike is when the player knocks down all 10 pins on his first roll.
The frame is then completed with a single roll. The bonus for that frame is the value of the next two rolls.
Tenth frame
In the tenth frame, a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame.
However, no more than three balls can be rolled in the tenth frame.
For those with poor imaginations, here's a bowling scorecard. Spares are shown with black triangles and strikes with rectangles.
The rules seem simplistic, so we can start with an int array counting the number of scores. We can factor in the rest of the mentioned rules later (at least we think we can do it relatively easily).
public class Game {
private int[] rolls = new int[21];
private int turn = 0;
public void roll(int pins) {
rolls[turn++] = pins;
}
public int score() {
return Arrays.stream(rolls).sum();
}
}
Now, let's collect the preconditions and invariants for the roll()
method.
- We can't roll more than 10 pins at once (physically impossible as there are only 10 pins at maximum on the field).
- The sum of pins we hit can be only 10 in each frame.
There's an implicit concept not mentioned in the code above, and it makes the implementation extremely hard to handle, and that's the frame. Now let's see preconditions and invariants related to frames in our business logic:
- On the tenth frame, we can have either 2 or 3 rolls depending on the current score in that frame.
- In the first nine frames, we can have 2 rolls at maximum.
- All of the above mentioned for the
roll()
method is also true since we capture our current score in each frame in our scorecard.
The game should just ensure one thing:
- A game consists of 10 frames. (this is captured implicitly with the magic number 21).
How should we represent frame in our code? We simply should make it explicit! This has the following positive effect on our implementation:
- Eliminates the hidden "Single Responsibility Principle" violation starting to appear, as we try to do everything in a single
Game
class. - Immediately eliminates the magic number
21
in our code. - Helps readability by explicitly phrasing another "noun" of our domain, called a frame.
- But most importantly it's decomposing the problem into smaller subproblems, each one easier to solve!
Here's a diagram that shows how our class hierarchy should look:
So, how do we implement the Game
class that encapsulates all its invariants?
public class Game {
private Frame[] frames = new Frame[10];
private int turn = 0;
public Game() {
// ...
}
public void roll(int pins) throws NoMoreRollsException, IllegalRollException {
frames[turn].roll(pins);
if (frames[turn].noMoreRolls()) {
turn++;
}
}
public int score() {
return Arrays.stream(frames).mapToInt(Frame::score).sum();
}
}
The third line above is a pure representation of our first requirement: "The game consists of 10 frames." The Game
class does not need to know much more about anything else. The roll()
and score()
methods are just delegating functionalities to the appropriate Frame
subclass.
Let's see how the roll()
method in the Frame
implementation deals with preconditions and invariants:
public class IntermediateFrame extends BaseFrame {
private static final int FIRST_TRY = 0;
private static final int MAXIMUM_TRIES = 2;
// ...
@Override
public void roll(int pins) throws NoMoreRollsException, IllegalRollException {
verifyNumberOfTriesIsLessThanMaximum(tries, MAXIMUM_TRIES);
verifyNumberOfPinsIsLessThanMaximum(getFirstRoll() + pins);
if (tries == FIRST_TRY) {
setFirstRoll(pins);
} else {
setSecondRoll(pins);
}
tries++;
}
// ...
}
Just to recap, we have to ensure that:
- We can't roll more than 10 pins at once (physically impossible as there are only 10 pins at maximum on the field).
- The sum of pins we hit can be only 10 in each frame.
- In the first nine frames, we can have 2 rolls at maximum.
All of the above is encapsulated in just two lines.
Knight on a Chessboard
I once saw the following difficult interview assignment. If you get an exercise like that and you are inexperienced, you will probably block because of the overwhelming complexity of the problem (then you probably start right in the middle and end up troubleshooting all your bugs in an overcomplicated multi-nested loop in the last 20 minutes of the interview).
Object-Oriented thinking should be on our aid! The only thing that we have to do is to split the problem into feasible subproblems and protect our invariants (you can imagine it as some sort of a synonym to encapsulation). It ensures good OOP design from the bottom up and saves a lot of time.
Working With What We Know
Let's brainstorm together a couple of invariants:
- A chess piece should remain on the board after each step.
- A knight is allowed to move two squares vertically and one square horizontally, or two squares horizontally and one square vertically (from Wikipedia).
These are still too high-level and not simplistic enough to work with. How should we express the first one with multiple invariants?
- A position in the chessboard is formed of ranks and files.
- A chess piece on the board occupies one single position.
public final class Position {
private final Rank rank;
private final File file;
public Position(Rank rank, File file) {
this.rank = rank;
this.file = file;
}
// ...
}
public final class Knight {
private final Position currentPosition;
public Knight(Position position) {
this.position = position;
}
}
Now, for the sake of simplicity, let's assume that we're playing with a standard 8x8 chessboard. Each rank and file can be represented as an enum in this case:
public enum Rank {
A, B, C, D, E, F, G, H;
// ...
}
public enum File {
ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT;
// ...
}
To guarantee the first invariant, all that we have to do is to implement a method, which will move our piece to a new location:
class Position {
// ...
public Position advance(int rankChange, int fileChange) throws IllegalMoveException {
return new Position(getRank().advance(rankChange),
getFile().advance(fileChange));
}
// ...
}
class Rank {
// ..
public Rank advance(int steps) throws IllegalMoveException {
try {
return Rank.values()[this.ordinal() + steps];
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalMoveException();
}
}
}
We're changing the existing interface of the Java enum to align it with our domain. We have introduced the advance
and the IllegalMoveException
terms.
Why Aren't We Using Coordinates?
As a side note let's discuss less powerful options. At least less powerful from the scope of forcing our invariants.
What if we use just two int
inside our Knight
class?
public class Knight {
private final int file;
private final int rank;
public Knight(int file, int rank) {
this.file = file;
this.rank = rank;
}
// ...
}
In this case, we need to check if the file
and rank
fields are between 1 and 8 (or 0 - 7) after moving our piece. This logic can't be inside the Knight
class because it will break the "Single Responsibility Principle": What if we need to extend our codebase with additional pieces? The invariant has to be enforced in each and every step for each and every piece. We can't just copy over the validation code to other pieces, and neither can we extend int
functionalities. So, there has to be a new class encapsulating this logic that's associated with all the chess pieces somehow. Let's put the coordinates into the Position
class:
public class Position {
private final int file;
private final int rank;
public Position(int file, int rank) {
this.file = file;
this.rank = rank;
}
// ...
}
This style is a bit better. We need to guarantee that each Position
object is well constructed, meaning both file
and rank
values are in range. We could do something like the example below, but this would mean that every constructor call will possibly throw an exception:
public class Position {
// ...
public Position(int rank, int file) throws IllegalMoveException {
assertWithingRange(rank);
assertWithingRange(file);
this.rank = rank;
this.file = file;
}
// ...
}
Enums offer a possible range of values definable with a declarative style. It relieves us of the burden of exception handling in every object creation.
Coding the Algorithm
A Knight
should tell the set of positions it can visit. We need to implement this method relying on the existing mechanism. Something like the snippet below should work:
public class Knight {
// ...
Set<Position> getPossibleMoves() {
var result = new HashSet<Position>();
addPositionIfPossible(result, 1, 2);
addPositionIfPossible(result, 1, -2);
addPositionIfPossible(result, 2, 1);
addPositionIfPossible(result, 2, -1);
addPositionIfPossible(result, -1, 2);
addPositionIfPossible(result, -1, -2);
addPositionIfPossible(result, -2, 1);
addPositionIfPossible(result, -2, -1);
return result;
}
private void addPositionIfPossible(HashSet<Position> result, int rankChange, int fileChange) {
try {
result.add(position.advance(rankChange, fileChange));
} catch (IllegalMoveException e) {
}
}
// ...
}
The method addPositionIfPossible
is not quite nice, because it swallows an exception and modifies the passed parameter's state. If you prefer to eliminate these issues in method implementations, you can refactor the snippet above after changing Position.advance()
to return an Optional<Position>
.
public class Knight {
// ...
Set<Position> getPossibleMoves() {
var result = new HashSet<Position>();
position.advance(1, 2).ifPresent(result::add);
position.advance(1, -2).ifPresent(result::add);
position.advance(2, 1).ifPresent(result::add);
position.advance(2, -1).ifPresent(result::add);
position.advance(-1, 2).ifPresent(result::add);
position.advance(-1, -2).ifPresent(result::add);
position.advance(-2, 1).ifPresent(result::add);
position.advance(-2, -1).ifPresent(result::add);
return result;
}
// ...
}
OK, now let's see the implementation of the algorithm after we've solved all the subproblems above. We need some Java collections to track our current progress of the traversal. Let's try to nail them and implement them by doing the following:
- We need to track the positions we've already traversed with our Knight (can be a set).
- All possible moves should be enqueued to investigate them one by one (ideal candidate for a queue).
- We track the steps required in a separate collection, which maps each position on the chessboard with an integer (a map, of course).
public class KnightSolver {
private final Knight knight;
private final Set<Position> seen = new HashSet<>();
private final Queue<Position> candidates = new LinkedList<>();
private final Map<Position, Integer> stepsNeeded = new HashMap<>();
// ...
}
In each iteration, we're visiting the first element of the candidates
queue. Let's list the steps we need to do:
- Get the possible moves from that position.
- Remove all the possible moves which we've already seen.
- Update our
stepsNeeded
collection with the newly discovered possible moves. - Update the set of already
seen
positions. - Update the traversable
candidates
with the newly discovered possible moves (for the subsequent iterations). - Of course, return the result if we reached the end position.
And, this is how it should look:
public class KnightSolver {
//...
public int maxStepsFor(Position endPosition) {
initialize();
while (!candidates.isEmpty()) {
var currentKnight = new Knight(candidates.poll());
var positions = currentKnight.getPossibleMoves();
positions.removeAll(seen);
for (var position : positions) {
stepsNeeded.put(position, stepsNeeded.get(currentKnight.getPosition()) + 1);
if (endPosition.equals(position)) {
return stepsNeeded.get(endPosition);
}
seen.addAll(positions);
candidates.add(position);
}
}
throw new RuntimeException("Should not be possible");
}
//...
}
You can view the complete code example over here.
Conclusion
Domain-Driven Design has a great number of elements in its vocabulary to implement the concepts above. These include specification, validation, assertions, and constraints, to name a few. I encourage you to look at "Chapter 10: Supple Design" and "How to Model Less Obvious Kinds of Concepts" from the original book Domain-Driven Design if you're interested in finding out more. It also implies that these concepts should be part of your domain layer and not sitting somewhere else, or even worse, scattered all over your code.
Flaws and Limitations of This Technique
Java is doing poorly in providing language support for designing by contract. Clojure, for instance, offers pre-conditions and post-conditions for their functions. In Java, we can somewhat replace the precondition of a method by simply implementing guard clauses or assertions inside the method body. The problem with this approach comes when we need to implement inheritance and begin to override methods. A few of the possible options to overcome this issue are the following:
- Simplify the reusability of your preconditions: Extract them into their separated utility methods and make them
final
. Making themstatic
will help these utility methods not to rely on the current object's state. - * Implement a Template method design pattern. I encourage you to make all those methods
public final
that is not meant to be overridden by the subclasses.
Other Options
There are some cases where it might seem better not to aim for a robust design. Microservices with simple logic or with a shallow domain can be one area I can imagine. Would it sound good to have multiple layers and modularity in this case? It might look beneficial to simplify everything to the ground for the sake of reaching production early. But this is a slippery slope. Note that good design is really hard to factor in later, similarly to security or automated tests.
If you're interested, you can watch me on YouTube doing the bowling game implementation step-by-step using TDD.
Opinions expressed by DZone contributors are their own.
Comments