Select Page

The constraints imposed by legacy systems and external systems often turn the situations very interesting to work with.

Here is one such interesting situation, where I accepted the behaviour of the legacy system as it is, but devised a layer of facade to alter and offer a varied behaviour.

The context

We are allowed to access a repository of Tasks through its service interfaces.

The repository provides two functional interfaces. One is to request a task from the repository and the other is to return the task back to the repository.

The task can be either in one of two states. An Assignable state or in Hold state.

The constraint

You can’t afford to ask for a task of your choice from the repository. It is more like you are forced to pick a random chocolate from an assortment of chocolates.

The requirement

The requirement is that when a user requests for a task, we need to offer the task that is in an Assignable state to the user. We should never give the user the task in a hold state. Meaning we should help the user to overcome the constraint.

I captured the context and requirement as an executable specification with Concordion.

Identification of Nouns

Later I wrote the companion fixture for the specification.

I started by stubbing the external TaskRepository by carving out the two functional capabilities.

The StubbedTaskRepository is the implementation of the TaskRepository.

The Allocator is the class that is expected to provide the required capability to the user.

The immediate benefits of translating the requirement into the executable specifications are:

  • Discovery of the prominent first layer classes
  • The collaboration of these classes of this layer
  • Faster execution of the tests to obtain rapid feedback
  • Flexibility to offer multitude of datasets by injecting different datasets into the Stub

Evolving the design

Unit tests are my favourite to unravel the design.

The First Test

The first step of my approach was maintaining a buffer to keep the preferred tasks — Assignable Tasks.

Then I would make the code to pass the test with minimal design elements and tried my best to avoid over-engineering.

Observations

The process of coding to the test created waves of ideas and memoirs to relish. Of course, it is a side-effect of coding to a test.

One of them is the hint of a bounded buffer problem that can be leveraged and the other is the memory of my childhood; when elders used to pamper me by giving the items I like from their plate. Now as a father; it is my turn to offer the items my daughter is fond of.

The Second Test

I proceeded to the next unit test to evolve the allocation logic.

The code-snippet that captures the allocation logic should:

  • return the Task from the head of the buffered queue
  • attempt to fill the buffer with assignable tasks from the task repository if there is no item in the buffered queue
  • indicate that the repository does not have any further assignable tasks, if the buffered queue is still empty,. Preferably by throwing an exception to inform the requestor with a message

Let me check if the refactored code meets the expectation of the Unit test.

Cool, Now it is time to revisit the fixture of the executable specification that I wrote before — The PreferredTasksFeature.java.

Let me execute the specification to see if the definition of done is really done.

Those Happy greens are indicating that our allocator is meeting the specification.

Code for the Article

https://github.com/krsmanian1972/TaskAllocation

Finally

I attempted to emphasis

  1. Specification by example is a means to capture the definition of done objectively
  2. Unit tests are instrumental in evolving the design
  3. TDD works well in the context of Legacy; by isolating the premise of change by introducing additional layers

In my next article I will show you how I further improvised the Allocator by adopting a Preferential Producer and Consumer strategy by leveraging the LinkedBlockingQueue.

Thanks for reading.

Raja Subramanian