Use interfaces to hedge against uncertainty

This is more of a risk management pattern than a clean-coding one.

Often in business programming you’ll have an implementation decision to make before you really have enough information to decide, due to schedule pressures or [whatever.] You want to hedge against some stakeholder input that’s not coming for awhile, or you want to save time by building a basic version of a feature with the option of upgrading to an advanced one later.

Either way, you’ve got an uncertain decision coming down in the future and, it not being a perfect world, you can’t afford to stall progress until you’ve got it. This is a great use of abstraction in general, and OOP interfaces in particular.

You’ll save yourself a lot of pain if you take the time now to abstract the operation and its output types. The goal is to be able to make the later substitution as painless as possible, while saving maximum time now.

Reserving the option to upgrade

Consider a search feature. Is your project going to grow to a size requiring a full Elasticsearch cluster? You could make a lot of progress now by simply using full-text search on your database as an 80/20 solution, and upgrading later only if/when you need to.

So you introduce something like this:

class SearchCriteria { ... }

class SearchResults { ... }

interface ISearch {
  public SearchResults Search(SearchCriteria criteria);
}

class DatabaseFullTextSearch implements ISearch { ... }

Write your code in terms of ISearch, probably introducing a factory. (The indexing side of things works the same way, omitted for brevity.)

When the time comes for Elasticsearch, all you’ve got to do is write a new implementation of ISearch. Then you’ve got one spot in the code to change to flip over to the new search engine.