SOLID Sitecore

D IS FOR DEPENDENCY INVERSION

D

What will we cover

  • Who the hell is this guy
    • A quick introduction
  • Summary of SOLID
    • with a bit of history
  • A close look at Dependency Inversion
  • A contrived example
  • Some Pros and Cons
  • Related concepts
  • How to use it with Sitecore
    • Controllers
    • Commands
    • Pipelines

Aaron

Gravypower

  • https://blog.gravypower.net
  • @gravypower
  • https://github.com/gravypower

 

UNCLE BOB

High-level policy should not depend on low-level detail, low-level detail should depend on high-level policy

SOLID principles 

  • Single Responsibility
  • Open Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

DEPENDENCY INVERSION

Depend upon abstractions, not concretions.

  • 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.

DEPENDENCY INVERSION

dependencies

object orientation

Class

polymorphism

 Compile-time dependencies

Class B

+ f()

Class A

Compiling Code

Run-time dependencies

Class B

+ f()

Class A

Running Code

turtles all the way down

 Compile-time dependencies

Class B

+ f()

Class A

C# Compiler 

https://xkcd.com/303/

COMPILE-TIME => Run-time

Interface

+ f()

Class A

Class B

+ f()

Class B

+ f()

Your living room

"Abstractions should not depend on details. Details should depend on abstractions"

Painting

+ show()

LivingRoom

Your living room

YOUR LIVING ROOM

public class LivingRoom
{
  public LivingRoom()
  {
  }
 
  public void EnterRoom()
  {
    //Painting is always there.
  }
}

YOUR LIVING ROOM

public class LivingRoom
{
  public LivingRoom()
  {
  }
 
  public void EnterRoom()
  {
    //Video is always there.
  }
}

YOUR LIVING ROOM

public class LivingRoom
{
  public bool ShowVideo {get; set;}
  public LivingRoom()
  {
  }
 
  public void EnterRoom()
  {
    if(ShowVideo)
    {
      //Show Video.
    }
    {
      //Show Painting.
    }
  }
}

Your living room

YOUR LIVING ROOM

IScreen

+ show()

Living Room

YOUR LIVING ROOM

public class LivingRoom
{
  private IScreen screen;
  public LivingRoom(IScreen screen)
  {
    this.screen = screen;
  }
 
  public void EnterRoom()
  {
    this.screen.Show();
  }
}
public class LivingRoom
{
  private ILcd lcd;
  public LivingRoom(ILcd lcd)
  {
    this.lcd = lcd;
  }
 
  public void EnterRoom()
  {
    lcd.Show();
  }
}

dependency injection

Question:

Is Dependency Injection different to Dependency Inversion?

Answer:

Dependency Injection is used when applying the Dependency Inversion Principle.

ISCREEN

+ show()

LivingRoom

Painting

+ show()

the contract

the contract

public interface IScreen
{
  void Show();
}
public class Painting : IScreen
{
  public void Show()
  {
    //Display Painting
  }
}

the contract

public interface IScreen
{
  void Show();
}
public class Video : IScreen
{
  public void Show()
  {
    //Display Video
  }
}

The system

public class House
{
  public LivingRoom livingRoom;

  public void LivingRoom()
  {
    var video = new Video();
    livingRoom = new LivingRoom(video);
    livingRoom.EnterRoom();
  }
}
public class House
{
  public LivingRoom livingRoom;

  public void LivingRoom()
  {
    var painting = new Painting();
    livingRoom = new LivingRoom(painting);
    livingRoom.EnterRoom();
  }
}

Benefits

  • Loosely coupled code

  • Increased Testability

  • Independent Developability

  • Allows for Pluggable architecture

  • Independent Deployability

disadvantages

  • You may never see the benefits

  • Can Increase Complexity

Don't call us we'll call you

Inversion of Control

Composition Root

Mark Seemann

Where should we compose object graphs?

As close as possible to the application's entry point.

A Composition Root is a (preferably) unique location in an application where modules are composed together.

public class House
{
  public LivingRoom livingRoom;

  public void LivingRoom()
  {
    var painting = new Painting();
    livingRoom = new LivingRoom(painting);
    livingRoom.EnterRoom();
  }
}

public interface IScreen
{
  void Show();
}

public class Painting: IScreen
{
  public void Show()
  {
    //Display Painting
  }
}

Where in our example?

Where in our example?

public class Program
{
  public static void Main(string[] args)
  {
    var painting = new Painting();
    var livingRoom = new LivingRoom(painting);
    var house = new House(livingRoom);
    house.LivingRoom();
  }
}

public class House
{
  public LivingRoom livingRoom;
  
  public House(LivingRoom livingRoom)
  {
    this.livingRoom = livingRoom;
  }
  public void LivingRoom()
  {
    livingRoom.EnterRoom();
  }
}

dependency graph

Where in our example?

public class Program
{
  public static void Main(string[] args)
  {
    var painting = new Painting();
    var livingRoom = new LivingRoom(painting);
    var house = new House(livingRoom);
    house.LivingRoom();
  }
}

public class House
{
  public LivingRoom livingRoom;
  
  public House(LivingRoom livingRoom)
  {
    this.livingRoom = livingRoom;
  }
  public void LivingRoom()
  {
    livingRoom.EnterRoom();
  }
}

Ioc/DI CONTAINER

Application Container

https://simpleinjector.org/blog/2016/07/working-around-the-asp-net-core-di-abstraction/

container ADAPTER

Get Sitecore Ready

COMPOSITION ROOT

public static class Bootstrapper
{
  public static Container Container;

  static Bootstrapper()
  {
    Container = new Container();

    // Application registrations go here 
    
    Container.Verify(VerificationOption.VerifyAndDiagnose);
  }
}

CONTROLLERS

The Pipeline

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor 
            type="DependencyInversion.CompositionRoot.InitialiseControllerFactory, DependencyInversion"
            patch:instead="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']"/>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>
public class InitialiseControllerFactory
{
  public virtual void Process(PipelineArgs args)
  {
    var controllerBuilder = System.Web.Mvc.ControllerBuilder.Current;
    var controllerFactory = new DependencyInversionControllerFactory(controllerBuilder.GetControllerFactory());

    controllerBuilder.SetControllerFactory(controllerFactory);
  }
}

Controller Factory

public sealed class ControllerContainerAdapter: SitecoreControllerFactory
{
  private static readonly Container Container = Bootstrapper.Bootstrap();

  public ControllerContainerAdapter(IControllerFactory innerFactory) : base(innerFactory){}

  public override IController CreateController(RequestContext requestContext, string controllerName)
  {
    try
    {
      if (controllerName.EqualsText(SitecoreControllerName))
        return CreateSitecoreController(requestContext, controllerName);

      var controllerType = GetControllerType(requestContext, controllerName);

      try
      {
        return Container.GetInstance(controllerType) as IController;
      }
      catch (ActivationException e)
      {
        Log.Info($"SimpleInjector could not resolve type, {controllerType.Name}", e);
      }

      return ResolveController(controllerType);
    }
    catch (Exception e)
    {
      if (MvcSettings.DetailedErrorOnMissingController)
        throw CreateControllerCreationException(controllerName, e);
      throw;
    }
  }
}

Command

CONTAINER ADAPTER

CONTAINER ADAPTER

public class CommandTaskContainerAdapter
{
  public void Execute(Item[] items, CommandItem commandItem, ScheduleItem schedule)
  {
    var commandTypeValue = commandItem["Command Type"];
    var commandType = Type.GetType(commandTypeValue);
    var command = (ICommand) Bootstrapper.Container.GetInstance(commandType);
    command.Execute(items, commandItem, schedule);
  }
}

Example Command

public class ExampleCommand
{
  private readonly ILogAdapter _logAdapter;

  public ExampleCommand(ILogAdapter logAdapter)
  {
    _logAdapter = logAdapter;
  }

  public void Execute(Item[] items, CommandItem commandItem, ScheduleItem schedule)
  {
    _logAdapter.LogInfo($"Hello from ExampleCommand, called from {commandItem.Name}");
  }
}

Pipeline

Factory Container Adapter

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <factories>
      <factory 
        id="FactoryContainerAdapter" 
        type="DependencyInversion.ContainerAdapters.FactoryContainerAdapter, DependencyInversion"/>
    </factories>
  </sitecore>
</configuration>
public class FactoryContainerAdapter: IFactory
{
  public object GetObject(string identifier)
  {
    var type = Type.GetType(identifier);
    return Bootstrapper.Container.GetInstance(type);
  }
}

Pipeline

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <preprocessRequest>
        <processor 
          ref="DependencyInversion.Pipelines.ExamplePipeline, DependencyInversion" 
          factory="FactoryContainerAdapter"/>
      </preprocessRequest>
    </pipelines>
  </sitecore>
</configuration>
public class ExamplePipeline : HttpRequestProcessor
{
  private readonly ILogAdapter _logAdapter;

  public ExamplePipeline(ILogAdapter logAdapter)
  {
    _logAdapter = logAdapter;
  }

  public override void Process(HttpRequestArgs args)
  {
    _logAdapter.LogInfo($"Hello from ExamplePipeline");
  }
}

Where To From here?

  • Presentations on other four Principle

  • Come have a chat with me.

    • I love this shit.

  • Sitecore Community Slack

    • @gravypower

  • Have a read of my blog.

    • https://blog.gravypower.net

    • Much ranting, you have been warned

  • Mark Seemann's blog.

    • http://blog.ploeh.dk

  • https://simpleinjector.org/

  • The Clean Coder book

Sample Solution

SOLID Sitecore - D is for Dependency inversion

By Aaron Job

SOLID Sitecore - D is for Dependency inversion

A summary of SOLID with a deep dive into Dependency Inversion followed up with a side of how to of Sitecore.

  • 698