Nowadays it's hard to develop modern application without
using IoC container. Absence of container leads to coupled codebase, expensive
and hard to maintain unit tests (and they're not "unit" at all) and,
as result, slow development speed and low quality software.
When using IoC container it’s important to ease the process
of component and dependency registration and provide enough information to
reader (for example during code reviews). While I’m big fan of Convention over
Configuration, it doesn't always fit my needs. In applications I develop, I
split components into at least 2 categories: components with lifetime bound to
lifetime of application and the majority of components with lifetime per operation,
such as HTTP request or service bus message. Cache, message bus are examples of
former and command handlers, repositories, ApiControllers represent the latter.
Having scope per operation brings the following benefits:
- Atomicity and isolation. I usually treat this scope as unit of work and try to develop persistence so that result of operation is visible to outer world only after successful completion of operation.
- Performance and scalability. Each concurrently executing request will have it’s own copy of state, handlers, repositories and so on, avoiding excessive contention. Watch and read Pat Helland’s “Immutability Changes Everything”.
- Forget about exception safety. Writing exception safe code is hard! It is much easier to throw away scoped container with all it's instances than writing exception safe code.
So it is very important to understand lifetime of the
component when reading its code. It helps find mistakes like usage of non thread-safe structures inside long-living class:
[ComponentRegistration(Scope = Lifetime.Application)] internal sealed class MagicWand : IMagicWand { private HashSet<string> _state = new HashSet<string>();
// ... }
As you may understand from this example, I use attribute to
specify lifetime of the component. Unfortunately, there are some pitfalls when
it comes to building container during application startup. When using Assembly.GetTypes()
(or even GetExportedTypes()) on few specific (for example, by file mask) assemblies
to discover components with registration attribute, you may occasionally load lot
of assemblies which will significantly slow application startup. It may be fine
for service running 24/7 that starts once in a while, but desktop application that
consists of 3-5 assemblies of “business logic” and 30-50 of UI controls (like
Infragistics) will suffer a lot.
This happens, for example, when assembly you’re scanning
contains type which derives from type defined in another assembly. The problem
can be minimized by moving components not related to DI to separate assembly, but
I was looking for better solution. What if generate IoC specific component
registrations at compile time?! Roslyn looks like the solution for this and
many other problems I have, but I’ve got old proven hammer, which is Text
Template Transformation Toolkit or T4.
Check out how I did it for StructureMap, meanwhile I’m going
to do the same for my lovely Autofac.
> What if generate IoC specific component registrations at compile time
ОтветитьУдалитьOmg, that's exactly what I did in my 'Plasma' couple years ago.