|
ExistingApplications
Guidance for introducing IoC into existing applications.
IntroductionThis page is an ongoing work intended to help designers looking to take advantage of Autofac as a means of controlling complexity while evolving existing applications. MotivationWhen a new application is built, it is quite often possible to understand the whole thing. If the application grows organically, at a certain point a line is crossed - and suddenly change becomes very difficult. Understanding one block of code within the context of the whole can get very time-consuming. Structuring applications in a dependency-injected fashion allows each individual component to be reasoned about in relative isolation. On top of this, behaviour-based testing can be applied to dependency-injected components in order to validate that all side effects of an operation are understood. Identifying Units of WorkIoC containers build graphs of objects in response to a request for a single service or root object. Identifying these root objects is the first step in introducing IoC. In a web application, the most frequent choice of root object is the page or controller. In an application itself exposing services, the service implementers generally fit this role. Desktop applications are more diverse, and the right choice may be based around a workflow or service layer role, or else some user interface unit such as a dialog. Once you've identified which objects are going to be created directly from the container, you need to build or configure an integration that will let your application request those components from the container. Useful TechniquesExpose SingletonIn its traditional form, usually an antipattern. Using singletons through global accessors like MySingletonClass.Instance creates the kind of binding that IoC is here to avoid. Wrapping up a singleton is straightforward: builder.Register(MySingleton.Instance); Injected components can now depend on the MySingleton type through constructor parameters or properties. The example above will dispose MySingleton.Instance if necessary when the container is disposed. If your singleton implementation already manages disposal, you might make the following adjustment: builder.Register(MySingleton.Instance).ExternallyOwned(); Lazy SingletonsThe above examples register instances because this ensures that singleton semantics are maintained (instance registrations do not support Factory or Container scope.) The downside however is that if the singleton is lazily instantiated then the act of registering it will cause its creation, which may be undesirable. In these cases, expressions to the rescue! (Surprise surprise!) builder.Register(c => MySingleton.Instance); Hide Factory MethodFactory methods can be cool but quite often they're exposed through static methods. To avoid this use a delegate registration: builder.Register(c => MyFactory.CreateProduct()).FactoryScoped(); Factory methods quite often require parameters. These can be passed through with the ActivationParameters to the factory method: builder.Register((c, p) => MyFactory.CreateProduct(p.Named<int>("size"))).FactoryScoped();This is problematic for autowired components but the problem can be addressed using expressions: builder.Register(c => new NeedsProduct(c.Resolve<Product>(new NamedParameter("size", 10))));Alternatively, if your injected components need to create the product directly (rather than have the product injected) check out generated factories. Adapt Static ClassSometimes an application will group related functionality into a class of static functions: static class IdGenerator
{
static int _next = 0;
public static int GetNext()
{
return ++_next;
}
}This kind of design usually has no place in an application created using dependency injection. Instead, create an interface that provides the same functionality: interface IIdGenerator
{
int GetNext();
}And an implementing adapter class that uses the static methods: class IdGeneratorAdapter : IdGenerator
{
public int GetNext()
{
return IdGenerator.GetNext();
}
}You can now register your adapter with the container, so that your dependency-injected components can be free of the static dependency upon IdGenerator. builder.Register<IdGeneratorAdapter>().As<IdGenerator>(); This simple example is somewhat trivial - however in applications that make heavy use of static methods it can provide a smooth migration path to a more loosely-coupled design. Note also that there doesn't need to be a 1-1 correspondence between adapter classes and the static methods they wrap. There is an opportunity to improve the application by making the adapters more finely- or coarsely-grained, and this should be considered. The Inverse ApproachIf you can freely change the existing parts of the application, you might consider the opposite approach: move all of the functionality from the static methods into new service objects, then make the static methods access the IoC container to get their implementation: static class IdGenerator
{
public static int GetNext()
{
return ApplicationContainer.Instance.Resolve<IIdGenerator>().GetNext();
}
}This design is more appealing because it keeps more of the code in the 'new' components. For sufficiently complex applications though this can become quite a bit of work. The global ApplicationContainer.Instance property is yet another static dependency to eliminate later on, too, and may be an annoyance when taking advantage of nested containers (it might make more sense to resolve the IdGenerator in the context of the nested container rather than the root one.) Maximising ReturnsExtracting InterfacesIsolating Components |
Sign in to add a comment