20
Playing with Dependency injection in a Console app?
A few days ago I was asked to create some console app that lets you ask some question around weather. Its a pretty much standard easy peasy console app you can wip out in an hour or even half if you're feeling it and is on zone.
But its boring and bland, so I decided to play around with overkilling it with a controversial pattern in recent years, Dependency Injection!! cause why not?
So what's Dependency Injection anyway?
Uhmm its fairly nerdy but as a basketball fan too I have original way of explaining it (feel free to attack me if its plain wrong lol)
Imagine you go to the gym to do some shooting, Normally you go shoot the ball, rebound your own ball or even chase the ball, then go back to your position, finally able to shoot and repeat the process again.
Now in dependency injection, all you have to do really is just shoot the ball cause when you need the ball it will be passed to you automatically.
And compiler now is seems really smart about it....
Well enough of that talk let's get jump into it.
First thing first is making sure I have setup the dependency injection in the console app. For that I need the Microsoft.Extensions.DependencyInjection
and it will have the following setup code in here
// Build configuration
configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
.AddJsonFile("appsettings.json", false)
.Build();
// Boostrapping the app
var serviceProvider = new ServiceCollection()
.AddSingleton(configuration)
.AddSingleton<IWeatherstackService, WeatherstackService>()
.AddSingleton<IQuestionService, QuestionService>()
.AddSingleton<IUserInteractionService, UserInteractionService>()
.BuildServiceProvider();
var weatherApp = serviceProvider.GetService<IUserInteractionService>();
// Start the App
weatherApp.Start();
My acceptance criteria is to get user's zip code then answer few predefined questions that user can select. These questions are mostly related to weather so for this mini project we will use WeatherStack API.
Okay gameplan coach, WeatherStackService
will handle the http request to WeatherStack. I've used Flurl
as a http request client because of sugar syntax it provides as well as simplicity.
QuestionService
will handle the pre-defined questions, as well as validation or the actual answering of the question. So let's say its a question if "Is it raining?" some sort of that, then this service will try to answer that based on info we will get from WeatherService
.
Then these two will be injected to UserInteractionService
, this service will handle the grunt work. It will pretty much will look like this
private readonly IQuestionService _questionService;
private readonly IWeatherstackService _weatherstackService;
public UserInteractionService(IWeatherstackService weatherstackService,
IQuestionService questionService)
{
_weatherstackService = weatherstackService;
_questionService = questionService;
}
Now we are passing ehem injecting the two services into UserInteractionService
Now this where it gets interesting, we have both of our dependent injected and ready to use. We can easily visualize it this way
With DI, the main thing in order for this pattern to succeed is planning ahead, listing the services and visualizing how each of this will relates to each other is the key. Cause just slapping the service into each other will make the code messy and complexity arises exponentially. You can use something like graphviz
or PlantUML
to visualize each of your components.
Now to continue, Let's say the user already added the zip code, We will ask the WeatherStackService to get the weather info and store into a POCO.
private WeatherStackResponse AskZipCode()
{
....
....
var task = Task.Run(async () => await _weatherstackService.GetWeatherInfo(zipCode));
var result = task.Result;
if (result?.location != null)
{
Console.WriteLine(string.Format(Constants.ZipCodeValid,
result?.location.name, result?.location.country, result?.location.region));
return result;
}else{
....
}
}
Faily cool right? we don't need to set it up or even instantiate it. One of good things with DI Pattern is you can isolate things up more properly, which in turns also make the code more testable.
Cause instead of worrying about factory methods, constructors, you can just keep things much more simple. Though like anything else overused and abused it can be detrimental too.
Now back at it, we can finally at the last solve the problem which is answering a question
private void AskQuestion(WeatherStackResponse weatherInfo)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(Constants.SelectQuestionMessage);
var questions = _questionService.GetQuestions();
var selectedQuestion = UIUtils.PrintQuestion(questions);
// Generate Answer
var result = _questionService.AnswerQuestion(selectedQuestion, weatherInfo);
....
....
}
Anyway here how it looks like.
Now one thing I want to point out before ending this ramble of mine, is DI is pretty cool if it used correctly.
- DRY can be easily enforced. Hail Decoupling responsibilities!
- a much testable isolated code and many more.
- Its somewhat fairly maintainable? (subjective, for me yes)
But as great as it is, it's not for everyone.
- You need to write more interfaces, classes etc.
- It increases the complexity, mostly if you don't visualize the dependency tree beforehand and just slap the services to each other.
- And a small price to pay for overhead performance. (With machines nowadays, I reckon its noticeable enough but add this for the .ms seeksers)
You can look at the final code here
Anyway, I hope you find this entertaining, Its been years since I put my pen or rather keyboard to write something, so I am somewhat rusty but I totally had fun. Have a nice day!
20