Giter VIP home page Giter VIP logo

di.weatherapp's Introduction

DI.WeatherApp

[SOLID] Dependency Inversion. What? How?

.net build workflow License: MIT Generic badge

๐Ÿง‘โ€๐ŸŽ“ Disclaimer:

This summary on DI is based on my understanding of the DI principle (after research) and is for learning purposes. It's open for discussion and improvements. You can check out the demo source code below.

Dependency Inversion

๐Ÿง  Definition

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

The ๐ŸŽฏ of dependency inversion principle

Is to avoid this highly coupled distribution with the mediation of an abstract layer and to increase the re-usability of higher/policy layers. It promotes loosely coupled architecture, flexibility, pluggability within our code.

๐Ÿฅ‡ Golden rule

High-level (client) modules should own the abstraction otherwise the dependency is not inverted!

๐Ÿš€ Summary

  • Dependency Inversion Principle- Higher-level component owns the interface. Lower-level implements.
  • Dependency Injection Pattern - Frequently, developers confuse IoC with DI. As mentioned previously, IoC deals with object creation being inverted, whereas DI is a pattern for supplying the dependencies since the control is inverted.
  • Inversion of Control - is theย techniqueย for inverting the control of object creation andย notย the actual patternย for making it happen.
  • Dependency Inversion promotes loosely coupled architecture, flexibility, pluggability within our code
  • Without Dependency injection, there is no Dependency inversion

๐Ÿงฐ Demo

This demo is for future references if/when I don't feel confident enough that I understand in practice what DI is all about. I think dependency inversion is best explained in simple N-layered architecture so I'll try doing just that.

console-ui

DI.WheatherApp is a simple demo project. It's organized like this:

User Interface Layer

DI.WeatherApp.ConsoleClient

A simple console client to display dummy weather data. This represents the UI layer and orchestrates the dependency injection.

Startup.cs - Adds services to the DI container. Entry point for the console app.
static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
          .ConfigureServices((_, services) =>
              services.AddScoped<IWeatherService, WeatherService>()
                  .AddScoped<IDummyWeatherForecastRepository, DummyWeatherForecastRepository>()
                  .AddScoped<WeatherDataConsumer>());

      static async Task Main(string[] args)
      {
          using IHost host = CreateHostBuilder(args).Build();

          var weatherDataConsumer = host.Services.GetRequiredService<WeatherDataConsumer>();
          weatherDataConsumer.Display();

          await host.RunAsync();
      }
WeatherDataConsumer.cs - simple console UI build with ConsoleTables and Humanize
 public class WeatherDataConsumer
 {
      private readonly IWeatherService weatherService;

      /// <summary>
      /// Initializes a new instance of the <see cref="WeatherDataConsumer"/> class.
      /// </summary>
      /// <param name="weatherService">The weather service.</param>
      public WeatherDataConsumer(IWeatherService weatherService)
      {
          this.weatherService = weatherService;
      }

      /// <summary>
      /// Displays data on the console with the ConsoleTable and Humanize libraries
      /// </summary>
      public void Display()
      {
          var table = new ConsoleTable(
              nameof(WeatherForecast.CityName).Humanize(),
              nameof(WeatherForecast.Date).Humanize(),
              nameof(WeatherForecast.TemperatureC).Humanize(),
              nameof(WeatherForecast.TemperatureF).Humanize(),
              nameof(WeatherForecast.Summary).Humanize());

          foreach (var forecast in this.weatherService.Get())
          {
              table.AddRow(forecast.CityName,
                  forecast.Date.ToString("ddd, dd MMM yyy"),
                  forecast.TemperatureC,
                  forecast.TemperatureF,
                  forecast.Summary);
          }

          table.Write();
      }
  }
At the moment the UI is only referencing 
<ProjectReference Include="..\DI.WeatherApp.Services\DI.WeatherApp.Services.csproj" />
Business Logic Layer

DI.WeatherApp.Services

This represents the business layer.

WeatherService.cs - weather service that uses the dummy weather forecast repository to return data
  public class WeatherService : IWeatherService
  {
      private readonly IDummyWeatherForecastRepository weatherForecastRepository;

      /// <summary>
      /// Initializes a new instance of the <see cref="WeatherService"/> class.
      /// </summary>
      /// <param name="weatherForecastRepository">The weather forecast repository.</param>
      public WeatherService(IDummyWeatherForecastRepository weatherForecastRepository)
      {
          this.weatherForecastRepository = weatherForecastRepository;
      }

      /// <inheritdoc/>
      public IEnumerable<WeatherForecast> Get()
      {
          return this.weatherForecastRepository.Get().Select(w => new WeatherForecast()
          {
              CityName = w.CityName,
              Date = w.Date,
              Summary = w.Summary,
              TemperatureC = w.TemperatureC
          });
      }
  }
IWeatherService.cs - abstraction over the WeatherService class
WeatherForecast.cs - POCO holding weather data
public class WeatherForecast
{
    /// <summary>
    /// Gets or sets the name of the city.
    /// </summary>
    public string CityName { get; set; }

    /// <summary>
    /// Gets or sets the date.
    /// </summary>
    public DateTime Date { get; set; }

    /// <summary>
    /// Gets or sets the temperature Celsius.
    /// </summary>
    public int TemperatureC { get; set; }

    /// <summary>
    /// Gets the temperature Fahrenheit.
    /// </summary>
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    /// <summary>
    /// Gets or sets the summary.
    /// </summary>
    public string Summary { get; set; }
}
At the moment the Business Layer is only referencing 
<ProjectReference Include="..\DI.WeatherApp.Services\DI.WeatherApp.Data.csproj" />
Data access Layer

DI.WeatherApp.Data

Represents the data access layer.

DummyWeatherForecastRepository.cs - dummy weather data repository
public class DummyWeatherForecastRepository : IDummyWeatherForecastRepository
{
    #region Private fields

    private static readonly string[] Summaries = new[]
    {
        "Warm", "Bring an umbrella", "Chilly", "Freezing"
    };

    private static readonly int[] Temperatures = new[]
    {
        20, 10, 5, -4
    };

    private static readonly string[] CityNames = new[]
    {
        "Sofia", "London", "New York", "Brisbane", "Novosibirsk"
    };

    #endregion

    /// <inheritdoc/>
    public IEnumerable<WeatherForecastDbo> Get()
    {
        var random = new Random();

        return Enumerable.Range(1, CityNames.Length - 1)
            .Select(i =>
            {
                var randomIndex = random.Next(Summaries.Length);

                return new WeatherForecastDbo
                {
                    CityName = CityNames[i],
                    Date = DateTime.Now.AddDays(1),
                    Summary = Summaries[randomIndex],
                    TemperatureC = Temperatures[randomIndex]
                };
            })
            .ToArray();
    }
}
IDummyWeatherForecastRepository.cs - abstraction over the DummyWeatherForecastRepository class
WeatherForecastDbo.cs - Weather Data Dbo
public class WeatherForecastDbo
{
    /// <summary>
    /// Gets or sets the name of the city.
    /// </summary>
    public string CityName { get; set; }

    /// <summary>
    /// Gets or sets the date.
    /// </summary>
    public DateTime Date { get; set; }

    /// <summary>
    /// Gets or sets the temperature in Celsius.
    /// </summary>
    public int TemperatureC { get; set; }

    /// <summary>
    /// Gets or sets the summary.
    /// </summary>
    public string Summary { get; set; }
}

Altho we have some dependency injection in this setup, we are not inverting any dependencies thus not implementing the dependency inversion principle.

not-inverted-dependency

As shown in the graph, the dependency is not inverted and the UI is depending on the Business layer, which depends on Data layer. This means that the higher-level layer depends on the implementation details of the lower-level layer. We're trying to use an interface to decouple this dependency but this is not enough.

So what?!

To demonstrate an issue with not inverting dependencies we could introduce a change in the Data layer. Let's say that we need to change a property name in the WeatherForecastDbo.cs

changed-property-name

When we build the project we get the following errors.

build-errors

The issue here is that this error is now reflecting an outside project in the business layer. This means, that if we need to rename a property in our database, this would affect the business logic. We could think, that after we're using an interface (abstraction) we're safe from such things, but we didn't actually invert the dependencies.

To fix this, we need to follow the simple rule - the client (higher modules) own the interface.

  1. โœ”๏ธ Change interface ownership.

    The IDummyWeatherForecastRepository should be owned by the DI.WeatherApp.Services instead of the DI.WeatherApp.Data Also the IEnumerable<WeatherForecastDbo> Get(); should now return IEnumerable<WeatherForecast> Get(); because this is all we need in the business layer.

    change-interface-ownership

  2. โœ”๏ธ Invert the dependency.

    Remove project reference to DI.WeatherApp.Data from DI.WeatherApp.Services and add a reference to DI.WeatherApp.Services in DI.WeatherApp.Data to start using the IDummyWeatherForecastRepository interface. We also need to move the WeatherForecastDbo mapping to WeatherForecast in the DummyWeatherForecastRepository because now it is responsible for returning WeatherForecast data.

    invert-project-dependencies

    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    interface-implementation

  3. โœ”๏ธ Register all dependencies in the UI layer.

    In our case, the UI layer is the composition route for all dependencies.

    register-data-layer-in-ui

    โš–๏ธ Without dependency injection we can not achieve dependency inversion.

    dependency-injection-configuration

๐ŸŽ‰ Did we invert the dependency? ๐ŸŽ‰

To verify that dependency was indeed inverted, we will introduce a change in the Data layer like before. ๐Ÿš€ This time the error will be contained only in the DI.WeatherApp.Data and we don't need to update higher-level modules.

inverted-dependency

This is what the dependency flow looks like

inverted-dependency-flow-chart

๐ŸŽ‰ We can make changes to the lower level DI.WeatherApp.Data project, and this won't affect higher

๐Ÿ”— Check out this PR to see what was changed so that the project is following dependency inversion principle

di.weatherapp's People

Contributors

rostech avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.