DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)

Trending

  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  • Why SAP S/4HANA Landscape Design Impacts Cloud TCO More Than Compute Costs
  • Bridging Gaps in SOC Maturity Using Detection Engineering and Automation
  • AI Agents in Java: Architecting Intelligent Health Data Systems
  1. DZone
  2. Coding
  3. Frameworks
  4. Protect Your Invariants!

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!

By 
Oresztesz Margaritisz user avatar
Oresztesz Margaritisz
DZone Core CORE ·
Jan. 07, 22 · Tutorial
Likes (38)
Comment
Save
Tweet
Share
8.1K Views

Join the DZone community and get the full member experience.

Join For Free

How 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:

Java
 
@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.

Illustration.

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.

Spares.

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). 

Java
 
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:

Hierarchy illustration.

So, how do we implement the Game class that encapsulates all its invariants? 

Java
 
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:

Java
 
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.
Java
 
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: 

Java
 
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:

Java
 
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?

Java
 
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:

Java
 
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:

Java
 
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:

Java
 
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>.

Java
 
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).
Java
 
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:

  1. Get the possible moves from that position.
  2. Remove all the possible moves which we've already seen.
  3. Update our stepsNeeded collection with the newly discovered possible moves.
  4. Update the set of already seen positions.
  5. Update the traversable candidates with the newly discovered possible moves (for the subsequent iterations).
  6. Of course, return the result if we reached the end position.

And, this is how it should look:

Java
 
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 them static 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. 

Invariant (computer science) Frame (networking) Java (programming language) Domain-driven design Design Concept (generic programming) Object-oriented programming application Implementation Spring Framework

Opinions expressed by DZone contributors are their own.

Related

  • Architectural Evidence in Enterprise Java: Making Domain-Driven Design Visible
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook