Apr 242009
 
Glove

Glove: supple and protective

“Nothing endures but change.”

Heraclitus (540-480 BC)

“A well-worn glove becomes supple at the points where the fingers bend, while other parts are stiff and protective.”

Domain-Driven Design, by Eric Evans

In our design and refactoring efforts, we probably can’t afford to join the epistemological quest for Heraclitus’ supreme Logos – the deep rational principle that rules all changes in the universe.

We can and should, however, reason about how change affects consistency in our software designs. For stateful programs, such as object-oriented software, this necessarily brings us to understand its variants and invariants and how they are translated to design flexibility and coherence in the face of contextual variance.

Our objective is to reflect on how to design software that fits our domain like a well-worn glove. Simple, flexible and durable. Easy to work with. Capable of bending without breaking where we most need. Consistent and adaptive in the face of change.

Program’s Invariants in the Face of Contextual Variance

An invariant is a logical expression that remains true throughout a sequence of transformations. Invariant analysis is traditionally employed for software design on a textual basis. Invariants are defined or identified and then verified for representation structures, iterations, objects and classes.  Here, we want to go beyond what is present in the software texts (the programs themselves).

This post resumes the Revisiting Fowler’s Video Store series. After making the relevant domain concepts explicit, we focus on another aspect of domain semantics, studying contextual variance under the perspective of the following issues:

  1. The Passage of Time;
  2. Changes in the Video Classification;
  3. Changes in Rental Prices and Terms.

The Passage of Time

The first variance in the context of any software system is the inexorable passage of time. In our case, it affects directly the video store rental periods, which are represented by a record of the number of days a video has been rented. This representation is not persistent: it needs to be incremented at the beginning of each business day, otherwise it would become invalid. Therefore, the representation is not consistent because it is incoherent with the program’s context.

We propose to replace the number of days by the initial rental date and to calculate the rental period through date arithmetic. The refactoring is internal to the Rental class, so there is no need to change its specification (or published interface).

The consistent rental period calculation can be readily implemented using the LocalDate class from the swift Joda Time, that applies the time point pattern with day granularity. The alternative Rental class constructors and getDaysRented method are shown in the figure below.

public Rental(Video video, LocalDate initialDate) {

    _video       = video;
    _initialDate = initialDate;
}

public Rental(Video video, int daysRented) {

    LocalDate today = new LocalDate();

    _video       = video;
    _initialDate = today.minusDays(daysRented);
}

public int getDaysRented() {

    LocalDate today = new LocalDate();

    return Days.daysBetween(_initialDate, today).getDays();
}

Two additional reflections on this refactoring:

  1. the initial rental date is another relevant domain concept that we made explicit in our program;
  2. it confirms again an obvious truth: we should only persist what is persistent.

Changes in the Video Classification

Both the initial Video Store dicdatic program and its purely syntactical refactoring use a static classification scheme for pricing information. In the initial program, each video is  associated with a price code; purely syntactical refactoring replaces this association with an association to a price subclass.

There is an apparent contradiction in this model that should be questioned and clarified:

“Do New Releases stay in this category for ever?”

If the domain expert answers “No. Of course not.”, then the next question should be:

“If a video is reclassified, how does it affect the calculations for its current rentals?”

Programs must be perfectly adherent to all relevant business rules. If the domain experts affirm the calculations should remain the same as when the customer rented the video, we should transfer the pricing information from the  Video class to the Rental class, because it is transient to Video and persistent to Rental.

In the previous post, we introduced the RentalConditions class, created to represent the domain concepts that regulate the price and frequent renter points. Now we just need to adapt the Rental class constructors and its getCharge method, as shown in the following program fragment.

public Rental(Video video,RentalConditions conditions,LocalDate initialDate) {

    _video       = video;
    _conditions  = conditions;
    _initialDate = initialDate;
}

public Rental(Video video, RentalConditions conditions, int daysRented) {

    LocalDate today = new LocalDate();

    _video       = video;
    _conditions  = conditions;
    _initialDate = today.minusDays(daysRented);
}

public int getCharge() {

    return _conditions.getCharge(this.getDaysRented());
}

Changes in Rental Prices and Terms

I enjoy watching a video every week at home. I usually rent two or three for my family at my local video store. This week I saw something that made me rent seven videos! Look yourself at the picture below.

Simultaneous Rentals Promotion

Simultaneous Rentals Promotion

I think the store owner finally understood my needs and the result is more business and satisfaction. For us, here, this is a good example of change in rental prices and terms. For regular and children’s videos, the rental conditions now depend on the number of simultaneous rentals. Each additional video rent extends the rental term by one day. So, the initial term is 3 days for 3 videos, 4 days for 4 videos and so on up to 7 videos.

In the original didactic program the prices and terms of rental for each type of video are expressed as literals embedded in the calculation formulas. Standard object-oriented design practice refactors the conditional expressions into a specialisation solution based on inheritance and polymorphism that implements the strategy design pattern through a class hierarchy.

We replaced the conditional expressions by parameterised unified expressions, where the parameters are domain relevant concepts, originally implicit and then explicitly articulated as rental conditions. The program fragment below shows how easily the rental conditions value object can be moulded to face the changes in rental terms.

public Collection<Rental>
simultaneousPromotion (Collection<Video> videoCollection,
                       LocalDate initialDate) {

    int simultaneous = videoCollection.size();
    List<Rental> rentalCollection = new LinkedList<Rental>();

    if (simultaneous >= MIN_RENTALS && simultaneous <= MAX_RENTALS) {

        int initialTerm = simultaneous;
        RentalConditions conditions = new RentalConditions(initialTerm,
                                                           3.00, 1.50, 1);
        for (Video eachVideo : videoCollection) {

            Rental rental = new Rental(eachVideo, initialDate, conditions);
            rentalCollection.add(rental);
        }
    }

    return rentalCollection;
}

As we can see, there was no need to change the calculation logic. The refactoring solution based on making relevant concepts explicit is not only simpler and more expressive, but also and more flexible and consistent in the face of contextual variance.

Conclusions

Confronting the program invariants with relevant contextual variances illuminates the refactoring process. As usual, we should always listen the domain experts, strive for simplicity and use a maintenance need as an opportunity for refactoring.

In this post, we brought contextual variance to our refactoring process. Working through the program invariants in the face of contextual variance generated two contributions for our refactoring of the Video Store program:

  1. it made apparent the need to refactor the representation for the rental periods;
  2. it confirmed the value object based refactoring of the program’s conditional expressions.

Refactoring for coherence with relevant contextual variances improves the software design in terms of consistency and flexibility. Just like an well worn glove, our refactored program is now more consistent and flexible exactly where it makes most sense.

Design coherence at the syntactical level is achieved by collocating data and functions responsibilities. At semantic level, coherence means consistency and flexibility in the face of domain dynamics. This requires deeper thinking about the relevant contextual variance and the relevant domain concepts.

  2 Responses to “Revisiting Fowler’s Video Store: Variants and Invariants”

Comments (2)
  1. I guess this line:

    if (simultaneous >= 3 && simultaneous >= 7) {

    Should read:

    if (simultaneous >= 3 && simultaneous <= 7) {

    Interesting ideas though.

    • Thanks, Mark

      You are completely right! I made the mistake by manual intervention when moving the blog content from its former location. All the code in the blog was supposed to be invariant to this refactoring. Didn’t have proper tests in place! 😉

      Cheers, Rafael

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)