Towards a new DI/Composition model for NuPattern manager

Coordinator
Jun 14, 2013 at 3:40 AM
Over the past couple weeks, on other projects that also leverage VS-MEF in much the same we we do in NuPattern, we started experiencing serious performance issues. After much spelunking and profiling, I came to the conclusion that there was a reason why VS-MEF extensibility never made it to PublicAssemblies, why it's not versioned with the same strategy as the other stable parts of VS (using binding redirects and changed assembly file names with their version post-fixed): it is simply not ready for use.

The VS-created composition containers have been basically rewritten down to the core: it's almost as if it's MEF just in the use of the core attributes and abstract base classes. The internal implementation is a completely different beast altogether. And not the kind of awe-inspiring beasts, but the "run away fast!" kind. I should have listened to my internal warnings that if it was so complicated, it was probably not worth it.

Anyway, in the last few days I completed transitioning Clide (http://github.com/clariuslabs/clide, our modern VS extensibility API) out of that ugly and undocumented VS-MEF dependency using Autofac, a long-standing and much loved DI framework I had the pleasure of using in the past. The latest version (3.0) stays true to its simply origins, while offering extensibility and flexibility that makes MEF look laughable in that regard.

I could achieve the same level of "declarative-ness" using attributes for registration and composition, for lifetime annotations (shared vs non-shared components), multi-level scoping of registrations and lifetime, and much more. A real pleasure to work with.

My guess is that our current implementation is the culprit of much of my random VS slowdowns on build, type, debug and many more. A multitude of components and plugins importing (say) SVsServiceProvider multiple times are effectively causing our own composition containers to also run queries over all registered types (library components, runtime, services, ALL of it) all the time!

So, issue #1 that will be solved, is Performance.

Issue #2 is versioning of the Library project.

MEF is a string-based type-name-based composition container, meaning that there can only be ONE such contract for a type if you expect to be able to resolve a single instance of it (i.e. resolve a command from its type + assembly simple name like we do now). In addition, we rely on VS to do the assembly loading, which (again) will load the library project a single time, which we currently distribute together with the runtime.

This was fine as a stopgap measure, but if we really want a huge library of reusable components, the library release simply cannot be tied to new NuPattern runtime releases. The bar for contributing to the library currently is way too high: someone has to understand and is expected to work with a solution containing several projects, a significant time to build, and a release process which is painful for everyone (get NuPattern committers to release a new Runtime, which also typically will mean releasing a new Authoring drop, and only after both are public, update your toolkit dependency so that you can now use that nice reusable command you needed). . This totally discourages contribution to a pool of reusable components and instead fosters creation of private libraries of components (even if they are generic) for the toolkits I use and control, just so as not to depend on external releases of NuPattern.

I've talked to Jezz in the past about the need to split the library into a separate project hosted on GitHub, with very low barrier to contribution:
  • Just send pull request
  • We one-click merge via web
  • Components documentation is generated automatically and posted as wiki pages on GitHub
  • MyGet.org picks up the merge commit and automatically builds a new nuget package
  • At this point contributor can already subscribe to the "CI" nuget package source and start using the command right-away
  • After some use (or immediately?) we can one-click publish to nuget.org from myget.org
So all in all, this could be a very fast turnaround for contributions, making it more likely that people contribute their components to the library.

In order for this to work, though, each toolkit must own its own version of the library, and we must resolve references to reusable components by using the toolkit-distributed binary, rather than a runtime-distributed one (maybe as a fallback?). And for this to work, it's absolutely essential to get rid of the VS-MEF stuff. I've tested this multi-version library approach with Autofac and have a spike that works flawlessly. Each toolkit would gets its own "scope" within Autofac, meaning they can have their own additional component registrations that add on top of global ones. And there is very good and deterministic disposal of those components too when things are unloaded, so it's safe too.

So, issue #2 of Library Versioning would be solved with this.

A third problem arises now: in the Authoring experience, we're (ab)using MEF to discover components to show (commands, events, etc.). This needs to go, obviously. We should have never used it in the first place, since now we have that weird model where dependencies must be marked [Import(AllowDefault = true)] which is completely unintuitive and error-prone.

My proposal for this is that we just expose the actual types the toolkit project can access via references: either project references or library references (such as the reusable library nuget package). And we use our reflection-based provider exclusively, and drop MEF part of it that we're doing now. This model is more explicit. Users can rely on simply installing a nuget package with components (say someone builds a "TFS Automation Library") and have them ready for use right-away on the authoring environment. That would increase the likelihood of not having to write code for every single thing you want to automate in VS, eventually.

So, issue 3 is Improving Authoring Type Discovery

Sorry for the very long post, but I had to put it up for consideration.

Note: this could break existing toolkits, since [Export]/[Import] attributes will need to become something else (i.e. [Component]/[Dependency] or the like), but other than that, it should be a mechanical replacement process.

I'm already under way on the library project documentation auto-generation as a wiki. The initial result of that is the http://github.com/clariuslabs/nudoc project which allows me to read the XML documentation with reflection augmentation. Next comes markdown generation (likely to be called NuMark ;))
Coordinator
Jun 16, 2013 at 4:41 AM
Woah!, there is quite a bit here.

I actually wasn't aware of the MEF performance issues, so thanks for elaborating.
I think I am happy moving from VS-MEF to Autofac, if that is your proposition?

Agree, that it is perhaps time to separate the Library project in terms of versioning and deployment from the Runtime. Make sense. And Nuget seems to be the right way to deploy it.

I guess I still dont understand the impact of the proposal in issue 3, but I trust it is for the best.

So where do we start with all this?
I am keen to find out if the outstanding refactoring we need to do to isolate all VS dependencies can play a part in all this as well?
Coordinator
Jun 25, 2013 at 11:38 PM
I've spent the last few days in intense refactoring towards Autofac. I'm doing this in my fork.

Should have good news soon. So far it's looking great and Autofac is proving every day to be a way superior framework for the non-trivial scenarios we have. There's even built-in support for the kind of dynamic composition scopes we do to resolve local imports for commands and other components (i.e. get the current IProductElement or interface layer via a simple property or constructor), instead of the current workarounds we do with MEF. This will most probably mean that it will perform much better too...
Coordinator
Jul 4, 2013 at 5:10 AM
I've lost my faith on this. It's a seriously large refactoring that touches way too many places (runtime, authoring, extensibility, components lifetime, etc.).

It's not worth it at this point, I think. This should be taken as a learning for a potentially IDE-portable future version, but that's it.

I will stop working on this for now. Spent a lot of energy but couldn't see the light at the end of the tunnel.