When talking about dependency, many will think of Dependency Injection, think of Interface, think of Container, … Things go like if you have a Controller, that controller consumes a Repository, then you should inject them with dependency injection, most of the time via the constructor.
And we just stop at that level, at that thinking. Not so many ask for how to detect dependencies. Why do we have to think so much when things just work? No, we do not. However, it would be better if we dig deeper and understand more.
To identify dependencies, we have to define boundaries. Boundaries are the hardest thing to find, even for experts. There is no silver bullet. But there are some specific areas that we can identify and make a good decision.
Infrastructure and Application are 2 boundaries that I made up. They are too general, to be honest. Let’s start exploring with examples.
Application Settings
From my previous post, I have created an appsettings.json with .NET Core
{ "AppSettings": { "Message": "Setting from app settings" } }
And I have consumer code in AppSettingsController
public class AppSettingsController { private readonly IConfigurationRoot _configuration; private readonly AppSettings _appSettings; private readonly GlobalSettings _globalSettings; public AppSettingsController( IOptions<AppSettings> appSettingsOptions, IOptions<GlobalSettings> globalSettingsOptions, IConfigurationRoot configuration) { _configuration = configuration; _appSettings = appSettingsOptions.Value; _globalSettings = globalSettingsOptions.Value; } public Task GetAppSettings(HttpContext context) { return context.Response.WriteAsync($"Thie is AppSettingsController: {_appSettings.Message}; " + $"EF Connection String: {_globalSettings.ConnectionStrings.Ef}; " + $"Author: {_globalSettings.Author.Name}"); } public Task GetDirectAppSettings(HttpContext context) { return context.Response.WriteAsync($"Direct app setting: {_configuration["AppSettings:Message"]}"); } }
On the GetDirectAppSettings method, it accesses the setting from _configuration[“AppSetings:Message”]. It works. And look like we have Dependency Injection with IConfigurationRoot.
Then what are problems? What are differences between GetAppSettings and GetDirectAppSetings methods in term of accessing configuration values?
Problem
IConfigurationRoot, even an Interface, is Infrastructure Code, Infrastructure Concern. Whereas Controller is Application Code.
Let’s ask these questions:
- Does the AppSettingsController need to know about the present of IConfigurationRoot?
- Does it need to know about how to access a configuration value (such as AppSettings->Message)?
Asking questions is a good way to find out dependencies.
Asking questions is a good way to find out dependencies, to define boundaries.
I do not find “Yes” for those questions. Ok then. What does it need? It needs application settings values. It only needs values. And we accidentally tell it how to get them.
If not working with ASP.NET Core, we have ConfigurationManager class. Same problem! If you use it in your application code, you create a dependency.
Solution
In the ASP.NET Core example, the perfect way is reading configuration values at Startup
public class Startup { private readonly IConfigurationRoot _configuration; public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json") .AddJsonFile("appComplexSettings.json"); _configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { // Register the ability to read options in configuration services.AddOptions(); services.Configure<AppSettings>(_configuration.GetSection("AppSettings")); services.Configure<GlobalSettings>(_configuration); } }
And controllers consume values via IOptions. Look at the AppSettingsController->GetAppSettings method.
A valid concern is that IOptions is also an Infrastructure code. In service code, we might not want to depend on IOptions to get the values. We might want to inject AppSettings directly. That should be a fairly easy task.
Impact
In a web application, for example, built with ASP.NET MVC, Controllers can access/accept that dependency with less impact. Because in fact, MVC itself is Infrastructure.
However, when it comes to service, domain, repository code, especially reusable components, it has a huge impact. Why? Because obviously, it depends on the infrastructure, the IConfigurationRoot for example. You cannot take those components and use other places where IConfigurationRoot does not exist.
It also creates another impact on developers mindset. Developers should develop a correct mindset while coding. They might have to think of dependencies, of boundaries, of portability. It is hard to get things right. But it is not hard to start thinking now.
Recap
Infrastructure and Application code is an example of identifying code boundaries. Asking questions is a good way to find them. I do not suggest you ask questions for any piece of code you write. I encourage you to start now. Create a habit of asking questions about your code. Start with methods, classes, then move on with Assembly level.
Try to look at things in boundaries, dependencies, instead of a big ball of mud.