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