I have a confession: I am not that good of a developer. I have a very high level understanding about how applications work for the most part, specializing in the types of applications I am asked to create for my job. As such, I’m totally able to make applications that will impress those who use them, but would cause many other developers to shake their heads. Still, I’m not sure if this is actually a case of me pretending to be a developer, or of being heavily influenced by reading articles written by other developers about technologies and practices that seem to be more advanced than the ones I’m using or which go out of their way to make a point to let me know that what I’m doing and how I’m doing them is wrong (as the Internet has been known to do on occasion).
Interfaces are one of those things. In C#, an interface is a blueprint for a class that is used to “validate” a more specific class. For example, if I were to create a class to access a database, my interface would lay down the methods that my actual, working data access class must have (it can have more than the interface defines, but it has to provide the contents of the interface blueprint).
For the longest time whenever I came across talk about interfaces, I always thought that it seemed to be an unnecessary step. “I’m writing this data access class,” I’d think. “I know what the class needs to have, so why write the structure twice, especially if I find later I have to add on to it?” I try and avoid having to do the same thing multiple times, which is why my development tasks are usually built from templates and methods that I’ve created over the years. It may not be the “cutting edge tech” that the hipster-dev Internet is boosting this week, but it always accomplishes the task.
And while I’ve know about dependency injection and inversion of control for some time now, it’s been that templated method of working that’s prevented me from actually implementing this pattern. In my work, I don’t have to worry about the possibility that some of my classes will need to be swapped out at some point in the future, as my apps are all intranet apps dedicated to specific tasks on specific platforms. It wasn’t until I started looking into creating a game server at home that I took an honest side trip into DI and IoT territory with interfaces.
I’ll try and make this brief and painless: working without interfaces, DI, or IoT, a class does exactly what it’s supposed to do. If I need a data access class in my application, I’ll write it and call it from my main application when I need to access the database. The air-quote problem air-quote that hipster-dev Internet will tell you with this is that this makes things “tightly coupled”. My main app can only ever do what the data access class exposes, and can only do it in the way the data access class allows. For my day to day work, this is actually OK; my apps are usually small and very specific, and aren’t going to throw any curveballs in the future.
Let’s say, though, that we anticipate moving to a new database back end platform in the next year. Right now if I have an application that needs to move, one option would be to branch the project, create a whole new data access layer, and replace the old one with the new one. Now I’ve got two branches, one for each database, that both need to be maintained until we make the complete switch over and retire the original. That’s duplicating work, and I hate duplicating work!
Instead, I can use interfaces. I’d create an interface for the data access layer that defines the methods that I need. I’d then build a data access class from that interface to work with the old platform, and one that works with the new platform.
In the application where the data access layer is called, I’d need to define my data access layer by interface, and not by the specific implementation of the data access layer. I can do this by passing the reference to the interface to the calling class’ constructor. Within the constructor, the instance that was passed in is assigned to a private variable of the interface type as well. When I need to access the methods in the data access layer, I can, because while the interface only provides the blueprint of the methods that I need to implement in the actual working class, I have a specific implementation of the interface passed into the constructor, so the work gets done.
///Define the interface "blueprint"
public interface IDatabaseProvider
{
//Here's a method I expect my data access
//layer to have.
List<MyDataObject> GetData(int ByID);
}
///Define a class which implements the interface
public class SQLServerAccess: IDatabaseProvider
{
//If I didn't define this, the app would error.
public List<MyDataObject> GetData(int ByID){
//Do stuff here and return my List<MyDataObject>
}
}
///Using the interface-sourced provider
public class BusinessLayer
{
private readonly IDatabaseProvider _dal;
//Our class constructor
public BusinessLayer(IDatabaseProvider dal)
{
_dal = dal;
}
}
///Implement the business layer in the main app
BusinessLayer _bl = new BusinessLayer(new SQLServerAccess());
///Switch to another data provider using IDatabaseProvider
BusinessLayer _bl = new BusinessLayer(new OracleServerAccess());
Now, when instantiating the business layer class from the main app, I can pass in an instance of whatever data access provider I need. I only need to maintain different classes of data access providers that attach to their respective data store, and so long as they return the expected objects to the instance caller, the app doesn’t care what the specific implementation of the data access layer does, or how it does it. Popular wisdom says that in this case, the business layer should also implement an interface, though wholesale changing business logic for an entire app is a serious undertaking, and I can’t personally see it happening enough to warrant a DI/IoT need, but if you want to keep everything on the same level, it wouldn’t hurt to create an IBusinessLogic interface and derive the actual implementation from it.
I don’t expect this to be important to my day job at this point, but in working with this game server, I realized that my current approach was pretty specific…maybe more specific than might be the case should I ever formalize the implementation, specifically when talking about using a database. I fell down a tutorial rabbit-hole and landed on “using postgreSQL with NodeJS”, so that’s what I set up to work with. Then I figured I might need a way to manage my specific data, and how a custom admin application might make sense. In starting that I decided that I might not stick with postgreSQL, and that the best way to ensure that I can switch out would be to use interfaces and DI/IoT. I can write for postgreSQL right now, and later if I need to switch to another database, I can write a new data access class that implements the IDatabaseProvider interface, and just swap out the old instance for the new one.
The moral of the story, for me, is that using interfaces just as blueprints doesn’t seem to really buy me any significant advantage if the project is laser-focused, like 99% of my day-job projects are. However, when DI/IoT comes into play, interfaces become completely necessary. Even at a high level, working with interfaces and DI/IoT doesn’t cost very much, is considered to be a hot practice, and might even surprise me in the future should I need to actually swap out one class implementation for another.
3 Comments
Tipa
August 17, 2020 - 9:25 AMWe call them “contracts” at work. It lets the UI and the API be developed independently, since once both sides agree on an interface or contract, then both can develop things independently. In one person teams writing one-offs, it’s probably harder to see the benefit.
Scopique
August 17, 2020 - 4:13 PM.NET’s WebAPI uses contracts as well, though we don’t use WebAPI because I can’t convince anyone that we should : /
Interfaces Again: Separation of Concerns and Dependency Injection – Scopique's
November 13, 2020 - 11:53 PM[…] Most of my sites are small enough that I don’t bother with a high level of SoC. The logic is specific to that app, and it’s easier to contain UI, business logic, and data manipulation in one project for purposes of maintenance and portability. But with this personal project, I wanted to learn to do things “the Core way”, which is where Dependency Injection comes in. I won’t rehash what DI means, so if you want to know you can revisit my epiphany on the subject. […]