350 likes | 579 Views
Dependency Injection with StructureMap. by Jon Kruger. SOLID Software Design Principles. Dependency Inversion Principle. Part 1: High level modules should not depend on low level modules. Both should depend on abstractions. Tight Coupling.
E N D
Dependency Injection with StructureMap by Jon Kruger
Dependency Inversion Principle Part 1: High level modules should not depend on low level modules. Both should depend on abstractions.
Tight Coupling • Two classes are “tightly coupled” if they are linked together and are dependent on each other • If a class is tightly coupled to another class, it cannot work independent of the other class • Makes changing one class difficult because it could launch a wave of changes through tightly coupled classes
Tips for not violating DIP - Layers UI Business Logic (Domain Model) Data Access Database
Tips for not violating DIP - Layers UI Interfaces Business Logic (Domain Model) Interfaces Data Access Database
Tips for not violating DIP - Layers • Each layer should not know anything about the details of how the other layers work. • Example: your domain model should not know how data access is done – it shouldn’t know if you’re using stored procedures, an ORM, etc.
Layers – What’s the big deal? • Tight coupling is bad – if your business layer contains code related to data access, changes to how data access is done will affect business logic • Harder to test because you have to deal with implementation details of something you’re not trying to test
Dependency Inversion Principle Part 2: Abstractions should not depend on details. Details should depend on abstractions.
DIP Enables Testability • Stubs, mocks, and fakes in unit tests are only possible when we have an interface to implementThe main reason for the Dependency Inversion Principle is to help us write unit tests.
Enabling DIP – create interfaces public class ProductRepository : IProductRepository { public Product Get(int id) { ... } } public interface IProductRepository { Product Get(int id); }
Enabling DIP – Constructor Injection public class GetProductService : IGetProductService { private IProductRepository _productRepository; public GetProductService( IProductRepositoryproductRepository) { _productRepository = productRepository; } public IList<Product> GetProductById(int id) { return _productRepository.Get(id); } }
DIP Violations • Use of “new” public class GetProductService { public IList<Product> GetProductById(int id) { varproductRepository = new ProductRepository(); return productRepository.Get(id); } }
DIP Violations • Use of “static” public class SecurityService { public static User GetCurrentUser() { // do something } }
DIP Violations • Use of singletons using “static” public class ProductCache { private static readonly _instance = new ProductCache(); public static ProductCache Instance { get { return _instance; } } }
Enabling DIP – Constructor Injection public class GetProductService : IGetProductService { private IProductRepository _productRepository; public GetProductService( IProductRepositoryproductRepository) { _productRepository = productRepository; } public IList<Product> GetProductById(int id) { return _productRepository.Get(id); } } Problem: How do we create these objects?
What is a DI (IoC) Container? • Creates objects that are ready for you to use • Knows how to create objects and their dependencies • Knows how to initialize objects when they are created (if necessary)
DI Containers • Popular .NET choices: • StructureMap, Ninject • Other .NET choices: • Unity, Castle Windsor, Autofac, Spring .NET
Setting up StructureMap ObjectFactory.Initialize(x => { x.For<IGetProductService>().Use<GetProductService>(); }); “When someone asks for an IGetProductService, create and return a new GetProductService.”
StructureMap - Conventions “Scan the assembly containing the IProductRepository and automatically map classes and interfaces where the interface name is the class name prefixed with ‘I’.” ObjectFactory.Initialize(x => { x.Scan(scan => { scan.WithDefaultConventions(); scan.AssemblyContainingType<IProductRepository>(); }); });
StructureMap - Initialization “When someone asks for an IProductRepository, create a new ProductRepository, run the code in the OnCreationForAll() block, and then return the object.” ObjectFactory.Initialize(x => { x.For<IProductRepository>() .Use<ProductRepository>() .OnCreationForAll(repository => repository.ConnectionString = ConfigurationManager.AppSettings["MainDB"]); });
StructureMap - Singletons “When someone asks for the IProductCache, return the same ProductCache each time.” ObjectFactory.Initialize(x => { x.ForSingletonOf<IProductCache>().Use<ProductCache>(); });
StructureMap – Custom Construction “When the user asks for an IGetCurrentUserService, run the custom code to create one.” ObjectFactory.Initialize(x => { x.For<IGetCurrentUserService>() .Use(c => new CurrentUser(Thread.CurrentPrincipal)); });
StructureMap – Custom Construction “When the user asks for an IProductCache, use this singleton that someone else created and I’m stuck having to use.” ObjectFactory.Initialize(x => { x.For<IProductCache>().Use(ProductCache.Instance); });
StructureMap – Lifecycles “When someone asks for an IDatabaseSession, there should only be only IDatabaseSession created per web request (when running a web app) and only one per thread (when not running a web app).” ObjectFactory.Initialize(x => { x.For<IDatabaseSession>() .LifecycleIs(new HybridLifecycle()) .Use<DatabaseSession>(); });
Advanced Ninja Tricks What if we want to load and save Customer objects differently? public interface IRepository<T> { T Get(int id); IList<T> GetAll(); void Save(T obj); void Delete(T obj); } public class Repository<T> : IRepository<T> { // the standard implementation }
Advanced Ninja Tricks public interface IRepository<T> { T Get(int id); IList<T> GetAll(); void Save(T obj); void Delete(T obj); } public class Repository<T> : IRepository<T> { // the standard implementation } public class CustomerRepository : IRepository<Customer> { // a slightly different implementation }
Advanced Ninja Tricks • IRepository<Order> will map to Repository<Order> • IRepository<Customer> will map to CustomerRepository(if CustomerRepositoryimplementsIRepository<Customer>) ObjectFactory.Initialize(x => { x.Scan(scan => { scan.WithDefaultConventions(); scan.AssemblyContainingType<IProductRepository>() scan.ConnectImplementationsToTypesClosing( typeof(IRepository<>)); }); x.For(typeof(IRepository<>)).Use(typeof(Repository<>)); });
A Pluggable Architecture UI IGetObjectService<T>, ISaveObjectService<T>, IDeleteObjectService<T> Business Logic (Domain Model) IRepository<T> Data Access Database
Types of objects in a DI world • Dependencies • Taken into a constructor • State (if any) is managed by the DI container (e.g. singletons, lifecycles) • These are things you want to stub out in a test • Don’t new up dependencies • e.g. IRepository<T>, IProcessOrderService (things you write)
Types of objects in a DI world • Entity objects • Each object has its own state • Does not have access to dependencies • Can (and should) be newed up • You never stub out entities in a test • Not managed by the DI container at all
Types of objects in a DI world • Other concrete types • e.g. string, int, SqlConnection, MemoryStream • Wrap these with the DI container if you need to • .NET Framework types, types from 3rd party libraries
More DI Container Rules • If you have to have static variables, isolate them behind the DI container (e.g. Thread.CurrentPrincipal) • Call ObjectFactory.GetInstance<T>() to create objects when you can’t take them in as constructor parameters (like when your app starts) • Take non-dependency parameters (e.g. primitive types) into methods instead of the constructor on dependencies • Don’t use the DI container when writing unit tests • Try to avoid calling ObjectFactory.GetInstance<T>() from code that you want to unit test