Agile Zone is brought to you in partnership with:

I am a Java expert with many years of developing applications and systems. I love learning new stuff and passing whatever I know to others. I love clean code, TDD and the simplest design possible. Eyal is a DZone MVB and is not an employee of DZone and has posted 23 posts at DZone. You can read more from them at their website. View Full User Profile

Why Abstraction is Really Important

04.03.2014
| 4134 views |
  • submit to reddit

Abstraction
Abstraction is one of the key elements of good software design.
It helps encapsulate behavior. It helps decouple software elements. It helps having more self-contained modules. And much more.

Abstraction makes the application extendable in much easier way. It makes refactoring much easier.
When developing with higher level of abstraction, you communicate the behavior and less the implementation.

General
In this post, I want to introduce a simple scenario that shows how, by choosing a simple solution, we can get into a situation of hard coupling and rigid design.

Then I will briefly describe how we can avoid situation like this.

Case study description
Let’s assume that we have a domain object called RawItem.

public class RawItem {
private final String originator;
private final String department;
private final String division;
private final Object[] moreParameters;
public RawItem(String originator, String department, String division, Object... moreParameters) {
this.originator = originator;
this.department = department;
this.division = division;
this.moreParameters = moreParameters;
}
}

The three first parameters represent the item’s key.
I.e. An item comes from an originator, a department and a division.
The “moreParameters” is just to emphasize the item has more parameters.

This triplet has two basic usages:
1. As key to store in the DB
2. As key in maps (key to RawItem)

Storing in DB based on the key
The DB tables are sharded in order to evenly distribute the items.
Sharding is done by a hash key modulo function.
This function works on a string.

Suppose we have N shards tables: (RAW_ITEM_REPOSITORY_00, RAW_ITEM_REPOSITORY_01,..,RAW_ITEM_REPOSITORY_NN),
then we’ll distribute the items based on some function and modulo:

String rawKey = originator + "_"  + department + "_" + division;
// func is String -> Integer function, N = # of shards
// Representation of the key is described below
int shard = func(key)%N;

Using the key in maps
The second usage for the triplet is mapping the items for fast lookup.
So, when NOT using abstraction, the maps will usually look like:

Map<String, RawItem> mapOfItems = new HashMap<>();
// Fill the map...

“Improving” the class
We see that we have common usage for the key as string, so we decide to put the string representation in the RawItem.

// new member
private final String key;
// in the constructor:
this.key = this.originator + "_" + this.department + "_"  + this.division;
// and a getter
public String getKey() {
return key;
}

Assessment of the design
There are two flows here:
1. Coupling between the sharding distribution and the items’ mapping
2. The mapping key is strict. any change forces change in the key, which might introduce hard to find bugs

And then comes a new requirement
Up until now, the triplet: originator, department and division made up a key of an item.
But now, a new requirement comes in.
A division can have subdivision.
It means that, unlike before, we can have two different items from the same triplet. The items will differ by the subdivision attribute.

Difficult to change
Regarding the DB distribution, we’ll need to keep the concatenated key of the triplet.
We must keep the modulo function the same. So distribution will remain using the triplets, but the schema will change and hava ‘subdivision’ column as well.
We’ll change the queries to use the subdivision together with original key.

In regard to the mapping, we’ll need to do a massive refactoring and to pass an ItemKey (see below) instead of just String.

Abstraction of the key
Let’s create ItemKey

public class ItemKey {
private final String originator;
private final String department;
private final String division;
private final String subdivision;
public ItemKey(String originator, String department, String division, String subdivision) {
this.originator = originator;
this.department = department;
this.division = division;
this.subdivision = subdivision;
}
public String asDistribution() {
return this.originator + "_" + this.department + "_"  + this.division;
}
}

And,

Map<ItemKey, RawItem> mapOfItems = new HashMap<>();
// Fill the map...
// new constructor for RawItem
public RawItem(ItemKey itemKey, Object... moreParameters) {
// fill the fields
}

Lesson Learned and conclusion
I wanted to show how a simple decision can really hurt.

And, how, by a small change, we made the key abstract.
In the future the key can have even more fields, but we’ll need to change only the inner implementation of it.
The logic and mapping usage should not be changed.

Regarding the change process,
I haven’t described how to do the refactoring, as it really depends on how the code looks like and how much is it tested.
In our case, some parts were easy, while others were really hard. The hard parts were around code that was looking deep in the implementation of the key (string) and the item.

This situation was real
We actually had this flow in our design.
Everything was fine for two years, until we had to change the key (add the subdivision).
Luckily all of our code is tested so we could see what breaks and fix it.
But it was painful.

There are two abstraction that we could have initially implement:
1. The more obvious is using a KEY class (as describe above). Even if it only has one String field
2. Any map usage need to be examined whether we’ll benefit by hiding it using abstraction

The second abstraction is harder to grasp and to fully understand and implement.

So,
do abstraction, tell a story and use the interfaces and don’t get into details while telling it.

Published at DZone with permission of Eyal Golan, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Barry Smith replied on Thu, 2014/04/03 - 5:54am


While I agree that abstraction is generally a good thing, you could say the same about titanium. Obviously, we should build cars out of titanium because it's stronger and lighter than steel and would result in a better product. Except that few could afford them.

Over enthusiastic abstraction is similar - it makes the system more expensive to build and more complex to understand and is, in many ways, the opposite of Agile. Futhermore, it only really helps if we are correct in forecasting what might change in future. If we are wrong we create even more work for ourselves when something unanticipated changes.

I think we should abstract as much as necessary to produce good software and where we can be fairly sure change will occur in the future. But otherwise we should concentrate on producing working code and refactor ruthlessly when change does come.

Eyal Golan replied on Thu, 2014/04/03 - 6:42am

The way I see things, you can hardly forecast the changes to be made. I really don't think that creating generalization in order to anticipate all changes in advance is a good approach.

But the way I see it, abstraction doesn't have to be over generalization.

My point was that we coupled the way we stored the records to our logic behavior. And that we didn't do a simple abstraction (for the key), which could have make our life much easier.
BTW, 2 years ago we did not know that the key would change, and that is why we decided to have a String key.

I totally agree with your last paragraph.
You should do as much necessary as needed. Not too much and not too little.

I will even support your point by mentioning the YAGNI acronym.
People sometime do generalization just in case that "we might need it some time..."
People will sometimes abstract stuff for the same reason.

In regard to "refactor ruthlessly", well we do that all the time.
I think that in the case I introduced, we could have seen it coming earlier. We had lot's of gun-shot effect, which we just fixed w/o thinking whether we should hide some stuff behind abstraction.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.