Is Dependency Injection the Missing Technique That's Holding Your Career Back?
Tags: csharp, design, oop
Published: 2024-02-12 19:57:51

## Get The Code
As you read this article, you are going to see a lot of code snippets.  You can get the source code to the entire project and run it using .NET Core.  All source code is at my GitHub: [https://github.com/raddevus/InterfaceStudy](https://github.com/raddevus/InterfaceStudy)

## Introduction
Yes, the title of this article was an attempt to get your attention.  But, I hope you'll stay with me, because I've been learning & using OOP (Object Oriented Programming) for 30 years and Dependency Injection (DI) still sometimes alludes me and annoys me.  I'm very interested in what you think of DI.

## Dependency Injection Has Taken Over OOP?
I understand the benefit of using DI but there are places where it feels as if it obscures the clarity of code.  At this moment in "Software Development History", it seems that Dependency Injection has taken over almost every other idea in OOP.  I want to talk about that and hopefully get feedback from you about how you feel about it.

### DI: Promises, Promises
The promise of Dependency Injection (DI) is that code is more loosely coupled.
Loosely coupled code means you should be able to switch out pieces more easily. 

That should mean that extending your code and changing it is easier.

## Large Projects With DI Architecture?
However, I don't know if you've been on any larg(er) projects where much of the architecture is based upon Dependency Injection, because they can be very odd to debug and deal with.  Especially if there is a IoC (Inversion of Control) Container involved -- the company I work for uses Microsoft Unity (see UnityContainer.org for more) -- this is not the Unity game engine.

## Background
In the past 4 - 6 years the company I work for has pushed DI to the forefront: all code that is created must use DI, there are no exceptions.  

## Write / Log Anywhere
I've also often thought about a specific solution that I'd like to see which allows me to write code debugging statements to a file, to a database or even post them to a WebAPI.  

## Request For Comments (RFC)
I finally created a good start the contains full implementation of my idea which is:

  * Built on full Dependency Injection concept
  * Guides the implementor to use the code to build their implementation

I'm really hoping that you'll take a look at my design and tell me what you think. 

I'm hoping that if you've never really dealt with Dependency Injection it'll open your eyes and make you consider where you might use it.  

But I'm also hoping that some of you will give the counterpoint (why you don't like it) also.

Now, let's get started as I try to explain what I'm trying to get there and why I'm using Dependency Injection for this design.

## It All Starts With Behaviors
Interfaces are really just contracts of behavior.   They have no implementation, but instead are just a promise of some future behavior that will be provided by the classes.

### What I Want
I want a way to track the activity that is going on in my code.  I want a way to quickly and easily add a line of code which :

   * Logs a message that I provide
   * timestamps the message
   * WriteActivity Method

To me, that sounds like a `WriteActivity` method.

I'm going to add that to an Interface and here it is:

```
public interface ITrackable
{
    bool WriteActivity(String message);
}
```
That's it.  The method will take a `String message` that will be stored and it will return a `bool` (true if successful, otherwise false).

## Forcing the Implementor
Now, keep in mind that this contract of `WriteActivity` is really a way to force the implementor (developer who uses Interface) to implement this method. 

### Next Question: Where Will It Write the Message?
We need a way to tell it to write to Storage of some kind.  In this case I'm thinking of three possible choices (there could be more in the future):

1. **file** (need a path and file name)
2. **database** (need a database and table)
3. **WebAPI** endpoint (need a URL)

### Different Storage Types: Different Configurations
In each case I need a different type of configuration.  

Well, the first thing I'm thinking here is that I can store the file, the database connection or the URL as a string.  Okay, let's force the implementor to also implement a place to hold the string which will represent the target storage.  Plus we need a `Configure()` call to always be called to insure that the `StorageTarget` is set, so let's add those now.
```
public interface ITrackable {

  bool WriteActivity(String message);

   // StorageTarget is one of the following:
   // 1. full-filename (including path)
   // 2. DB Connection string
   // 3. URI to location where data will be posted.

   // StorageTarget will wrap the private var 
   // used to contain the filepath,
   // connectionString, URI, etc.

   String StorageTarget { get; }
   // We need to insure that the Configure call is always made before the 
   // implementation class is instantiated.  We'll see how to force this.
   bool Configure();
}
```
## So Little, Just An Idea Really
It's odd, because so far, it is not much of anything.  There's no implementation and barely anything here.  As a matter of fact, the new `StorageTarget` variable is read-only and can't even be set.  

## Very Generic
That's because I want the user to define a String variable name of their own to hold the `StorageTarget`.  You see the `StorageTarget` may be a file path or it may be a connection string to a database.  So I need this thing to be very generic.  
Let's keep going and see if it makes any sense.  

## Forcing the Implementor To Initialize
There's one more thing I want to force the implementor to do when using this design.  I want the implementor to always run a `Configure()` method before any other code runs.

I want the implementor to do this because there will be different required configuration / initialization for each of the `TargetStorage` types.

## Example With A File
In the case of a file, we will need a target file path to be set so that when the `WriteActivity()` method calls it will write to the correct (expected) file.

However, it will be different for a database table.

## Example With A Database Table
When the `TargetStorage` location is a database connection string then the configuration will be different.  It will consist of a connection string which points to a database and a table and sometimes other information.  

**Note**: In our implementation I will be using Sqlite database because you will be able to run all of the code very easily -- no special database set up like sql server or mysql.

I can't force an implementor to call a method with an Interface.  However, I can do so with an abstract class.  

## Abstract Class: Part Virtual, Part Implementation
```
public abstract class Trackable, ITrackable {

  public Trackable(){
     Configure();
  }

  // Since we are derived from ITrackable 
  // we have to "implement" 
  // the ITrackable methods, but we 
  // want the implementing class to 
  // implement them so we are going 
  // to make them abstract here.
  public abstract bool Configure();
  public abstract bool WriteActivity(String);
  public abstract String StorageTarget{get;}

}
```
We have four methods, three abstract (unimplemented) and the other implemented (constructor):

## Trackable() constructor calls Configure()
The rest of the methods are the `ITrackable` methods that we are forced to "implement" but we've made them abstract so the target implementation class will have to write those implementations.
Now, let's finally see what an implementor will do to build on top of the `ITrackable` and `Trackable` classes.  After that, we can talk about whether or not it is valuable and helpful.

## Finally, A Realized Class: Implementation Rules!
Let's do this in steps.  First let's just add the class.

```
public class FileActivityTracker : Trackable
{

}
```

A `Trackable` is a `ITrackable` (via inheritance) so that means our `FileActivityTracker` is a `ITrackable`.  This will become important later.

If you do that and then try building the program you'll get some errors from the C# compiler that look like:

  * error CS0535: 'FileActivityTracker' **does not implement inherited abstract member** '`Trackable.Configure()`' 
  * error CS0535: 'FileActivityTracker' **does not implement interface member** '`ITrackable.StorageTarget`'
  * error CS0535: 'FileActivityTracker' **does not implement interface member** '`ITrackable.WriteActivity(string)`'

The compiler is warning us that we have not implemented the items which are defined in the abstract class and Interface.

Let's implement the `Configure()` method first.

#### Note: Linux or Windows
I wrote this code so it runs on both Linux and Windows so you'll see an odd thing where I get the `Path.DirectorySeparatorChar` for use in the path (on Windows it is backslash \ but on Linux it's slash /) but that is just overhead and not related to the true `Configure()` method.

In reality, you would probably just read the `FileName` from a configuration file and that'd be it.

I added some special code here so it will create a file in your `userprofile\temp` directory. 

  * On Windows that is \users\<user.name>\temp\ 
  * On Linux that is /home/<user.name>/temp/

Then, after that line I've added a `Console.WriteLine()` which will let us know when the Configure() method runs.  

This is just for demonstration purposes, because I want you to see that the method runs before the implementation class (`FileActivityTracker`) runs. 

## Real Implementation, Probably Uses App.Config
My point is that in the real implementation, the only code that would really be in the `Configure()` method is the code to set up the `FileName` that will be written to.  And that would probably come from some startup config (`app.config`) that is read when the app starts.

And, oh yeah, keep your finger on that `FileName` variable because it is really a backing field to the class `TargetStorage` Property that we need to add (below).  Hopefully, it'll make more sense in a moment.

```
public override bool Configure(){
   char pathSlash = Path.DirectorySeparatorChar;
   Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt")} : Configure() method called! ");

   // read values from configuration
   FileName = @$"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{pathSlash}temp{pathSlash}InterfaceStudy.log";

   return true;

}
```

We are also missing the implementation of StorageTarget so let's add that property now.

## StorageTarget: Just A Pass-thru Property
In an attempt to force the implementor to understand that she will need a place to store the file name, connection string etc. I've added this `StorageTarget` thing.  However, it is really just a common variable name that we use to get to the true value that holds the value of target.

The user will create a private backing field which has a more definitive name for what the `StorageTarget` is.  In my example I name that backing field `FileName`.  Here's the code that will be added to our `FileActivityTracker` class.

```
private overrid String FileName;

public override string StorageTarget
{
   get
   {
       return FileName;
   }
}
```

The `FileName` variable cannot be touched externally and can only be set from within the `FileActivityTracker` class

Finally, we get to see the implementation of the `WriteActivity(String message)` method and I believe that will all make it a bit clearer.

```
public override bool WriteActivity(String message)
{
  try
  {

    File.AppendAllText(StorageTarget, $"{DateTime.Now.ToLongTimeString()}: {message} {Environment.NewLine}");
    return true;
  }
  catch (Exception ex)
  {
    return false;
  }
}
```

### Keeping It Simple
The `WriteActivity` method is very simple.  It is just a call to `File.AppendAllText` (part of .NET libraries) to write a line of text to a text file. 

`File.AppendAllText` takes two parameters:

Full file path to the file the method will open and write to
An output string to write to the target file
The magic in our implementation is that the first parameter is our `StorageTarget`.  It is our generically named String which was configured to point to our target file.

The output just contains some additional information which is added to the user's message variable.   In this case we add a `DateTime` stamp and a newline so that each time we write to the file it will be on a new line.

## How Do We Use The Class?
Seeing how to use the class brings it all home. 

### Hiding the Details of the Implementation
One of the key features of seeing a simple usage is that we discover that the details of all of this implementation are hidden to the user and that is a huge benefit.  The user of the final implementation class doesn't have to think about all of this.

```
ITrackable fat = new FileActivityTracker();
fat.WriteActivity("The app is running.");
```

All the user needs to do is:

 1. new up a `FileActivityTracker`
 2. Call the `WriteActivity` method to write a message.

In our case with the example, the output will be stored in a file named `InterfaceStudy.log` and the output will look like:

> 5:26:38 PM:   The app is running.

## ITrackable: Our FileActivityTracker Is ITrackable
It is very important to notice that our `FileActivityTracker` is actually an `ITrackable`.  That means we could replace this `ITrackable` implementation with any other `ITrackable` implementation.  We will see how this is important when we create our `SqliteActivityTracker` and how this allows Dependency Injection.

## We Could End the Article Here
I could stop now and you'd have a full example of designing toward the idea of Dependency Injection except we haven't exactly injected anything have we?

To really understand why this technique of designing to an Interface is important we need to see how The `FileActivityTracker` could be easily replaced by another class.  We also need to see the actual concept  of injection. 

## Constructor Injection
What does injection mean anyways?  In our case it is going to mean injecting a particular type into a class that will use our type.  How will we inject the type?  We will send it into the class via the constructor.  This is called constructor injection.

## What Is the Point?
But, what is the point of all of this?  Let's say you have a class that has a method named `WriteActivity()` -- same name as the one that we have in our Interface.  Let's say that you've implemented the `WriteActivity()` method so it will write to a file.  Again, just as we have in our Interface.   Now, further imagine that your method is called all over the place so that users can write  messages to the file.  

### The Change Comes & Destroys Your Code
Then someone at a high level says, 

> "We can't write files to the user's drive.  We have to store that data in a database."  

Now, you'd have to go in and alter the class where you've written your `WriteActivity()` algorithm and you'd have to test all the places that call the code to make sure it works.

Let's say your class and class usage looks something like the following:

####  Bad Code Example
```
class FileWriter{
      String FileName {get;set;}
      WriteActivity(String message){
          File.AppendAllText(FileName, message);
      }

class Thing1 {
       private FileWriter fw = new FileWriter();

       Thing1(){
              fw.WriteActivity("In the Thing1 ctor...");
       }
}

class Thing2 {
       private FileWriter fw = new FileWriter();
       Thing2(){
              fw.WriteActivity("In the Thing1 ctor...");
       }
}
```

This is a very simple example but really take a look at it and think about it for a moment.

Imagine if you change the `FileWriter WriteActivity` method.  First of all, if you have other classes which are depending upon the class still writing to a file and you update the the method to write to a Database table then you are going to mess up all that other code.

Or, as another example, imagine if the `Thing2` class needs to write to the database but the `Thing1` should still write to the File.  What could you do then?  Maybe create an fake overloaded method for `WriteActivity` that looks like `WriteActivity(String message, bool useDB)` just so you have a different method signature.  That's not great.

## Design to An Interface: Design to Behavior
However, if we design to behaviors and then allow just the behaviors to change we make a much easier way forward. Let's see how we might use Dependency Injection with the classes above and our `ITrackable`, `Trackable` solution.

```
class Thing1 {

   private ITrackable fat;
 
   // Here's the actual Dependency Injection!
   // When we construct the Thing1 object 
   // we inject the tracker implementation 
   // which will be used later 
   Thing1(ITrackable tracker){
        fat = tracker;
    }

    int DoingWork(){

       // This code will write to a file 
       // when fat is a FileActivityTracker
       // and to Sqlite when we implement 
      //  SqliteActivityTracker.
      fat.WriteActivity("In the DoingWork() method...");
   }
}
```
So, if we inject our `FileActivityTracker` into `Thing1` it will write to a file.

Our calling code might look like:
```
Thing1 t = new Thing1(new FileActivityTracker());
```

We can change that at any time without changing any code.  That's the value of Dependency Injection.

## Probably Set Up In App Configuration
And, when you really carry this out, the call to new `FileActivityTracker()` would be set up in a app configuration so that when the app starts all of these types are set.

Let's implement a `ITrackable` that writes to Sqlite now so you can see this in action.

## SqliteActivityTracker Implementation Code
```
public SqliteActivityTracker()
{

   try{
   // ########### FYI THE DB is created (if doesn't exist) when it is OPENED ########
      connection.Open();
      File.AppendAllText(@$"{rootPath}{pathSlash}{tempDir}{pathSlash}InterfaceStudy.log", $"{DateTime.Now.ToLongTimeString()}: {Environment.CurrentDirectory} {Environment.NewLine}");
      FileInfo fi = new FileInfo(@$"{rootPath}{pathSlash}{tempDir}{pathSlash}tracker.db");

      if (fi.Length == 0){
         connectionString = fi.Name;

         foreach (String tableCreate in allTableCreation){
            command.CommandText = tableCreate;
            command.ExecuteNonQuery();
        }
     }

    Console.WriteLine(connection.DataSource);

   }
   finally{
    if (connection != null){
     connection.Close();
    }
   }
}

public override bool WriteActivity(String message)
{

   Command.CommandText = @"INSERT into Task (Description)values($message);select * from task where id =(SELECT last_insert_rowid())";
   Command.Parameters.AddWithValue("$message",message);


   try{
      Console.WriteLine("Saving...");
      connection.Open();
      Console.WriteLine("Opened.");

      // id should be last id inserted into table

      var id = Convert.ToInt64(command.ExecuteScalar());

      Console.WriteLine("inserted.");

      return true;
   }

   catch(Exception ex){
     Console.WriteLine($"Error: {ex.Message}");
     return false;
   }

   finally{
      if (connection != null){
      connection.Close();
   }
 }
}

  // This creates the table for the 
  // sample project so everything is
  // done for you and all the code will run properly.
  protected String [] allTableCreation = {

      @"CREATE TABLE Task  
      ( [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        [Description] NVARCHAR(1000) check(length(Description) <= 1000),
        [Created] NVARCHAR(30) default (datetime('now','localtime')) check(length(Created) <= 30)
      )"
  };
}
```
## This Is The Amazing Part: Change Without Changing Old Code
Now that we've implemented that code, all we have to make our `Thing1` use the `SqliteActivityTracker` is the following:
```
Thing1 t = new Thing1(new SqliteActivityTracker());
```

Now all the internal (to `Thing1`) calls to `WriteActivity` will write to the Sqlite database instead.

## Get The Source Code and Try It

You can get the source code and build it and try it.

You will see that the only calling code is the following four lines:
```
ITrackable fat = new FileActivityTracker();
fat.WriteActivity("The app is running.");

ITrackable sat = new SqliteActivityTracker();
sat.WriteActivity("This is from the app!");
```

We could've even made that a bit more generic to show that the same ITrackable can carry any ITrackable type by showing the following code:
```
ITrackable tracker = new FileActivityTracker();

// Writes to the file
tracker.WriteActivity("The app is running.");

// set tracker to carry a SqliteActivityTracker
tracker = new SqliteActivityTracker();

// writes to sqlite db
tracker.WriteActivity("This is from the app!");
```

## Dependency Injection Is Really About Design
As you can see, DI is really about Design and thinking about your classes in a more abstract way.

You have to think about shared behaviors and how you might create classes that support those similar behaviors that are implemented in different ways.

### Get The Source & Try It Out
If you get the code you will see that it will write:

  1. Write to a InterfaceStudy.log file
  2. Create and write to a Sqlite db (tracker.db in same location as file)

You'll see output something like the following:

> 2024-02-12 11:14:16.358 AM : Configure() method called! 
> 2024-02-12 11:14:16.394 AM : FileActivityTracker ctor...
> /home/<user-name>/temp/tracker.db
> /home/<user-name>/temp/tracker.db
> Saving...
> Opened.
> inserted.

## New Ideas & Comments?
I hope this has given you some new ideas about this concept and I hope you'll comment about what you like and don't like.