Dependency Injection… a SOLID Best Practice

Kendrick Park, a nice little detour on our way to the Grand Canyon

Who’s this post for? I wrote this for You!

Maybe you are a Sr Developer? Maybe you conduct Interviews? Maybe you are Semi-Technical, Quasi-Technical or perhaps, you JUST want to impress people and be the Life of the Party. Either way, you have come to the right place!

Dependency Injection, a reflection

Wait For it…

My grandfather was a Handy Man. He worked in construction and could build ANYTHING. He also would work on cars and sometimes, he’d let me “help”. As a young kid, I didn’t exactly fully appreciate what a GREAT opportunity this was for me. A highly skilled craftsman was putting on a Master Class and there I was, not being present in the moment, just daydreaming about who knows what? Playing? Psssssssh

Every so often I’d hear a request for some item and THAT was my call to action! Whenever he was done with whatever it was he requested, he’d hand it back and I’d put it away properly:

Request: Bring me 3/4 Wrench
Me: Oh, that’s in the blue cabinet

Request: Bring me a 7/8 long socket
Me: Sockets are in the LEFT drawer but Sockets are useless by themselves, they must be snapped into Ratchets and Ratchets were in the RIGHT drawer.

Request: Bring me the flashlight
Me: The dreaded flashlight (shudder), that was always on the counter BUT it needed to be turned ON first to be useful

Little did I realize, that my grandfather was teaching me a Foundational Software concept… Dependency Injection. (I also learned about Latency whenever he’d say, Hurry Up! but that’s a topic for another day)

What? You don’t believe me? Well, here’s what Microsoft has to say about Dependency Injection

“dependency injection (DI) software design pattern, [is] a technique for achieving Inversion of Control (IoC) between classes and their dependencies. A dependency is an object that another object depends on.”

-Microsoft

I hate to say I Told Ya So

All you have to do is ask

My grandfather knew nothing about how his tool was procured, assembled or what happened to it when he no longer needed it, he merely asked for it and it arrived; THAT, my friends, is Inversion of Control.

and IOC is JUST LIKE…

Dependency Injection IS MAGIC!

You Do You

I can hear the skeptics now: This Sounds complicated! Why does this even matter? Who says we can’t just create whatever service we need, right?

It’s Super Easy, It decreases coupling and Uncle Bob Martin says so, what’s who!

Ok, so that last one is an “Appeal to Authority” and Logical Fallacies are off limits around here but Uncle Bob is credited for creating the SOLID principles and they are tried and true. Not only does Dependency Injection increase code readability but it also promotes reusability, testability and maintainability.

Tying it all together…

Effectively, JUST like my grandfather asking his helper for tools, Dependency Injection gives your classes all the tools it needs so your class can focus on doing what it does best.

This is especially helpful when it comes to Generating Credentials, as you’ll soon see.

If I may Constructively Inject, for a moment

Ok Preston, so how can we achieve this same level of magic in our code?

Well, I’m glad you asked bc C# .Net has made this incredibly easy for us via a process called Constructor Injection. There are 3 methods that define the lifetime of the service being created and I’m going to show you an example each: AddSingleton, AddTransient, AddScoped.

Nerd Alert!

Time to Talk Tech

Singleton: Created once, the First time it is requested. Think of this as a type of Global Service, used across all Requests. Be Aware! Because this is created once, this MUST be a Stateless Service in order to be Thread safe; This is Perfectly suited for Loggers.

Transient: Created once per Request from the Service Container. Which means. EVERY time you create an instance of this, you’ll get a new one. I’ve added the Public property “Id => Guid.NewGuid().ToString();” to SQLRepoService so that you can you test this out for yourself. Just add additional IRepoServices to your Endpoint.cs Constructor ie: (IRepoService repoService2, IRepoService repoService3, …) and then look at the different auto generated Id’s. One thing to note, these are best suited for lightweight Stateless Services, so keep ’em lean and Thread Safe.

Scoped: Created once per Request from the Client. Which means every time the API Endpoint “GetAllEntities” is called, a new connection object will be created in the service collection. You can test this by putting a breakpoint on the first line. This is a Great Use for Generating Access Tokens because this process takes a little time so we can start the process early and we can reuse this token for the duration of the connection and not have to worry about expiration.

[assembly: FunctionsStartup(typeof(Microsoft.FAIRS.API.Startup))]
namespace Microsoft.FAIRS.API
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var config = new ConfigurationBuilder()
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();
            
            builder.Services.AddSingleton<IConfiguration>(config);

            builder.Services.AddTransient<IRepoService, SQLRepoService>();

            builder.Services.AddScoped(Provider => {
                var conn = new SqlConnection(config["sqlConnectionString"]) {
                    AccessToken = new DefaultAzureCredential(false)
                                      .GetToken(new TokenRequestContext(new[] { 
                                                "https://database.windows.net/" }))
                                      .Token
                };
                return conn;
            });

        }
    }
}

*“sqlConnectionString” : “Server=tcp:{servername}.database.windows.net,1433;MultipleActiveResultSets=True;Initial Catalog={catalog};”

Startup.cs

FAIRS (Function App Identity REST SQL ) that I’ll be using in this and future blog posts.

*Disclaimer: I know what you’re thinking and in Production code, I would create a class for this connection and dispose of it properly using .Dispose()

Now for the implementation. As you can see we have a 1 property model (FairEntity), an Interface and the implementation; which calls the Azure SQL Database PaaS service and gets a list of entity Id’s.

The beauty of this is the simplicity! The implementation doesn’t have to create a logger, find a connection string in a config or get an authorization token. The implementation gets to stay focused on doing only what it was created to do (which is the Single Responsibility Principle but that’s a topic for a different day too)

namespace Microsoft.FAIRS.Service.Repository
{

    public class FairEntity
    {
        public string Id { get; set; }
    }
     
    public interface IRepoService
    {
        Task<IList<IFairEntity>> GetAll();
    }

    public class SQLRepoService : IRepoService
    {

        public string Id => Guid.NewGuid().ToString();

        private readonly SqlConnection _conn;
        private readonly ILogger<SQLRepoService> _logger;

        public SQLRepoService(SqlConnection connection, ILogger<SQLRepoService> logger)
        {

            _conn = connection;
            _logger = logger;
        }

        public async Task<IList<FairEntity>> GetAll()
        {
            _logger.LogInformation("Entered SQLRepoService.GetAll");

            if(_conn.State != ConnectionState.Open)
                _conn.Open();

            using var command = new SqlCommand("SELECT * FROM [dbo].[Nodes]", _conn) 
                                                { CommandType = CommandType.Text };

            return await Getvalues(await command.ExecuteReaderAsync());
        }

        private async Task<List<FairEntity>> Getvalues(SqlDataReader reader)
        {

            var retVal = new List<FairEntity>();

            if (reader.HasRows)
                while (await reader.ReadAsync())
                    retVal.Add(new FairEntity() { Id = reader["id"].ToString() });

            await reader.CloseAsync();

            return retVal;
        }

    }
}

RepoService.cs

And now, the easy part, exposing the API via HTTPTrigger and we have a working end-to-end solution!

namespace Microsoft.FAIRS.API
{
    public class RESTEndpoint
    {
        private readonly IRepoService _repoService;

        public RESTEndpoint(IRepoService repoService)
        {
            _repoService = repoService;
        }


        [FunctionName("GetAllEntities")]
        public async Task<ActionResult> Get([HttpTrigger(AuthorizationLevel.Function, "get", Route = "product")] HttpRequest req)
        {            
            return new OkObjectResult(await _repoService.GetAll());
        }
    }
}



RESTEndpoint.cs

Ok, let’s bring this home

In your future travels, If you’re writing code or reviewing code and you happen to see “= new SomeService() ” ask if this might be a good candidate to inject. Sometimes, it won’t be; but if it can, it probably should.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s