Leaky abstractions and how to deal with them

Leaky abstractions is a term given to a faulty model, that is a model that fails to express some domain concepts. I found this to be a natural step on the process of creating a rich domain model. The problem comes when we stop refining the model, ending up with an incomplete work.

The school example

John was tasked to create a system to replace a legacy school administration system. His initial approach was to review the old systems database to extract the underlying entities. If not the code, at least he could reuse the abstractions. So, he ended up with the following abstractions:

Then he proceeded to work with the first use case/ user story: student registration. When finished, it looked something like:

“So far so good”- John thought – and he went to work on another use case. However, as he progressed he notice that the student object was over bloated: it contained info no only related to the student performance but also financial and historical data. Can you guess why?

Hunting a missing abstraction

Turns out that since every career has its own set of requirements, the student object had to accommodate all the data needed by every career prospect evaluator object.

The problem with John’s model it’s that is missing an abstraction. Thus, he is reusing another abstraction in place. Unfortunately, that’s a COMMON mistake. And one with a HUGE impact. The missing part here is the application the student submits. Let’s introduce this into the model.

This frees the student object to represent an actual student and nothing more. Now we can have the AccountingEvaluator evaluate an AccountingApplication.

By doing this we have:

  1. Reduced coupling since the student object is not dependent on the requirements of the program evaluators.
  2. Made the code SRP compliant, hence easier to maintain.

Keeping it simple

By this point some may be thinking that we increased the cyclomatic complexity since now we have to figure out the right evaluator for each application. Something like this:

 

Public void Submit (ProgramApplication app){
    var type = app.ToString();
    switch(type){
    …Code to select the right evaluator…
    }
}

But this can easily be fixed by putting the responsibility into the application objects themselves:

public interface ProgramApplication{
   bool IsApproved();
}

public class EngineeringApplication: ProgramApplication{
  decimal _mathScore;
  public EngineeringApplication(mathScore){
     _mathScore = mathScore;
  }
 public bool IsApproved(){ return _mathScore > 90} 
}
public class AccountingApplication: ProgramApplication{
  decimal _mathScore;
  public AccountingApplication(mathScore){
     _mathScore = mathScore;
  }

   public bool IsApproved(){ return _mathScore > 80}
}

//call on the client side

Public void Submit (ProgramApplication app){
 if(app.IsApproved()) ...
}

This is good design as it encapsulates the application evaluation details.

Closing thoughts

John’s is a typical scenario of leaking abstractions. The problem here is that he believed the domain model of the legacy system to be complete. This is a common mistake when starting a new project (either green or migrating any obsolete piece of code). We must remember that the moment we know less about the business is at the beginning. It’s naïve to expect the domain model to be complete on this stage. I learned at school that aversion to change is a human trait, but don’t hung up to faulty model. If it’s too complex, we’re doing it wrong.

Keep refining your model and have fun!