You're Doing Interfaces Wrong

It feels like interfaces are the vogue thing in programming today, almost an automatic reflex for some, and it tends towards systems where there are a million interfaces with single implementations that aren't very useful.

Unit Tests

There is one obvious consideration here - unit tests. Unit tests almost require everything to be an interface, because an interface can be simulated. When you can simulate all the parts that a class needs, you can test that class in isolation. So, for this reason alone, an interface can be pretty useful.

But taking that out of the picture - if the need to unit test was not a consideration - do the interfaces actually offer any useful utility?

Dependency Inversion

There is a further, more powerful utility in using interfaces that is for the most part, underappreciated in programming - dependency inversion.

Most programmers think they're creating dependency inverted code, with loose coupling when they require an interface instead of the implementation - but this is not automatically true.

Dependency inversion means that the flow of dependency is against the flow of control. This means, the business logic has to be independent of other considerations about how things should be done. In practice, it's very common to see business logic very much concerned with how things have to be stored and retrieved from storage/databases, or how to visually present the information, a report, etc.

In fact, real isolation of business logic from everything else almost never happens. What we do see (especially when EntityFramework is used) are interfaces to the database are passed into the business logic, and implemented by a DBContext class of some kind, or stored procedure calls or similar - BUT CRITICALLY these interfaces are still organised around the structure of what is convenient to the database.

This means that the business logic layer has to do the work of mapping the database objects into something that is useful to itself and back again. If it wants to save something that effects multiple tables, it has to call multiple saves to those multiple tables - so, even though it may only be calling interfaces, not implementations to access the database - it's so heavily tied to the database structure that we could replace the interface calls with the implementation and not have any kind of issues, but replacing the database layer with something like flat file storage, or non-relational storage would be a massive headache.

Far from being independent from the database - if the database structure were to change, the business logic (and all it's unit tests that simulate the database) would also need to change.

Therefore - coupling cannot be meaningfully reduced by merely creating the interface around the database. This only makes simulating the database in unit tests easier, or replacing the database with something that is structurally identical.

A business layer that depends on a database interface is just as tightly coupled as a business layer that depends on a database implementation.

You could not, for example, easily replace the database with disk access without changing the business layer, unless you were willing to make the disc format closely resemble the database, and/or have other mapping code in place that makes the disk look like a database.

So how do we do true dependency inversion?

It's very simple - define the interfaces on the other side of the flow of control. But don't just move the existing interface - write the interface in terms of what the business logic wants to do - make the interface make sense to belong on that other side of flow.

For example, if your software has to create an invoice, a typical flow may look like this:

  • Start Transaction / Unit of Work
  • Create Invoice DB record
  • Add Item 1 to invoice in DB
  • Add Item 2 to invoice in DB
  • Update status of Invoice
  • Save to DB

This is the business layer calling an interface to make the database "do stuff".

But flip the flow of control around and it looks like this:

  • Call "SaveInvoice" method on interface, accepting the InvoiceCreate object (with children item objects), and receiving a (completed) Invoice object back, if you need it.

Your business layer no longer cares about how an invoice is made, transactions or other DB concerns - you may not even be talking to a DB - the interface implementation takes over the entire responsibility of making that work.

Doing this over and over makes your Implementation layer very busy and will probably have a lot of repetition - but that's kind of the point - all the repetition gets moved to the other side of control where it's more relevant. It also makes your business logic a lot less flexible, because you can't just quickly add something in to the transaction/unit of work. That's also the point - if you need to change what happens when an invoice is saved it probably affects other things that save invoices. If it doesn't, then it's a related requirement of the business model that should be explicitly defined in the database.

Let's suppose you have now done all the work of reversing the flow of control and now you want to replace the database.

You delete the Database implementation and create a new implementation inheriting from the same Interfaces, and auto-fill the implementation.

Now you just have to fill in the blanks. It's a big job - yes - but you don't need to worry about how it may impact the upflow business logic when it's isolated away- as long as you fulfil the contract and business logic didn't creep into the DB implementation, it's a simple case of "fill in the blank", with not a lot of thinking required.

But entity framework...

This does mean you probably will have different entity classes - this is a good thing! Don't reuse the database entity classes for the business logic if they're not a good fit - it just makes the business logic cumbersome anyway.

Plus - we all know EF classes are a pain to change/refactor because they need to match the DB, and major DB refactoring just doesn't happen that often because it's fiddly and risky. Why saddle your business logic with this same restriction? Separating it away lets you make those changes a lot more easily, and then deal with the mapping to existing DB structures as a separate, contained problem.

And in more good news - this even makes your unit tests easier to write - you don't have to simulate what a database is doing anymore, and you don't have to think about dummy data for a bunch of irrelevant things that happen to be present in the entities but aren't relevant in the tested situation - you just provide the end outcome that's actually relevant to the logic you're testing.

But better still - dependency inversion facilitates cleaner code and gives you a really clear description about what the business logic needs to get it's job done. And, you could create new implementations that the business could use without changing it at all, and you can make those implementations deployment specific.

For instance, later down the line your web server code may want to offer a fat client and you may want to use disk storage instead of database storage, or you may want to develop a mobile app and you want to call the business logic to validate requests before going to some kind of API, but you don't want the app to actually save anything (must go to API first to do that).

Rinse and Repeat

This is applicable not just between the business logic layer and database - it's also true for many other layer-to-layer communications, but it's most often the business logic to database layer that has dependency in the wrong direction. But what makes that flow different to the others?

For instance, the web-site/presentation layer is usually dependent on the business logic. Imagine if it was the other way around! Business logic starts outputting HTML and CSS, and then you face the challenge of implementing a mobile app, or an API endpoint instead of a website, or even just wanted to do a redesign? Suddenly it's a massive headache.

Go against the flow.