RavenDB: In the Code, Part 1—MEF
If you’ve not heard of RavenDB, it’s essentially a .NET-from-the-ground-up document database taking its design cues from CouchDB (and MongoDB to a lesser degree). Rather than go into the details about its design and motivations, I’ll let Ayende speak for himself.
Instead, I would like to document some of the great things I’ve found in the codebase of RavenDB, as I read to be a better developer. This series of articles discusses RavenDBs use of the following .NET 4 features.
- Managed Extensibility Framework (MEF)
- New Concurrency Primitives in .NET 4.0
- The new dynamic keyword in C# 4
While discussing RavenDB’s use of these features, I hope to provide a gentle introduction to these technologies. In this, the first post of the series, we discuss MEF. For a very brief introduction to MEF and its core concepts, see the Overview in the wiki.
Managed Extensibility Framework
MEF was originally in the Patterns & Practices team and has since moved into the BCL as the System.ComponentModel.Composition namespace. Glenn Block has nominated it as a plug-in framework, an application partitioning framework, and has given many reasons why you may not want to attempt to use it as your inversion-of-control container (especially if you listen to Uncle Bob’s advice). RavenDB uses MEF to handle extensibility for it’s RequestResponder classes.
RavenDB’s communication architecture is essentially an HTTP server that has a number of registered handlers of requests, not unlike the front-controller model of ASP.NET MVC. Akin to MVC’s Routes, each RequestResponder provides a UrlPattern and SupportedVerbs to identify those requests it will handle. A given RequestResponder will vary it’s work depending on the HTTP verbs, headers, and body of the request. It is in this sense that RavenDB can be considered RESTful (even if it isn’t, see street REST).
- public class HttpServer : IDisposable
- {
- [ImportMany]
- public IEnumerable<RequestResponder> RequestResponders { get; set; }
This HttpServer class dispatches requests to one of the items in the RequestResponders. This is populated by MEF because of the ImportManyAttribute. MEF looks in its catalogs and finds the RequestResponder class is exported, as is all of it’s subclasses; see below.
- [InheritedExport]
- public abstract class RequestResponder
The InheritedExportAttribute ensures that MEF considers all subclasses of the attributed class are themselves as exports. So, if your class inherits from RequestResponder and MEF can see your class, it will automatically be considered for each incoming request.
How does MEF “see your class”? Out-of-the-box MEF provides for the definition of what is discoverable in a number of useful ways. RavenDB makes use of these by providing it’s own MEF CompositionContainer.
- public HttpServer(RavenConfiguration configuration, DocumentDatabase database)
- {
- Configuration = configuration;
- configuration.Container.SatisfyImportsOnce(this);
Above, in the constructor of the HttpServer class, we see the characteristic call to SatisfyImportsOnce on the CompositionContainer. This instructs the container to satisfy all the imports for the HttpServer, namely the RequestResponders. The configuration.Container property is below:
- public CompositionContainer Container
- {
- get { return container ?? (container = new CompositionContainer(Catalog)); }
And the Catalog property is initialized in the configuration class’ constructor like this:
- Catalog = new AggregateCatalog(
- new AssemblyCatalog(typeof (DocumentDatabase).Assembly)
- );
So the container is created with a single AggregateCatalog that can contain multiple catalogs. That AggregateCatalog is initialized with an AssemblyCatalog which pulls in all the MEF parts (classes with Import and Export attributes) in the assembly containing the DocumentDatabase class (more on that later).
That takes care of the built-in RequestResponders, because those are in the same assembly as the DocumentDatabase class. If that smells like it violates orthogonality, you are not alone. But, I digress; what about extensibility? How does Raven get MEF to see RequestResponder plugins?
The configuration class also has a PluginsDirectory property; in the setter, is the following code.
- if(Directory.Exists(pluginsDirectory))
- {
- Catalog.Catalogs.Add(new DirectoryCatalog(pluginsDirectory));
- }
So, in Raven’s configuration you can specify a directory where MEF will look for parts. That’s the raison d'être of MEF’s DirectoryCatalog, since a plugins folder is such a common deployment/extensibility pattern. You can learn more about the various MEF catalogs in the CodePlex wiki.
Now, the real extensibility story for RavenDB is its triggers.
RavenDB Triggers
The previously mentioned DocumentDatabase class is responsible for the high-level orchestration of the actual database work. It maintains four groups of triggers.
- [ImportMany]
- public IEnumerable<AbstractPutTrigger> PutTriggers { get; set; }
- [ImportMany]
- public IEnumerable<AbstractDeleteTrigger> DeleteTriggers { get; set; }
- [ImportMany]
- public IEnumerable<AbstractIndexUpdateTrigger> IndexUpdateTriggers { get; set; }
- [ImportMany]
- public IEnumerable<AbstractReadTrigger> ReadTriggers { get; set; }
Following the same pattern as RequestResponders, the DocumentDatabase calls configuration.Container.SatisfyImportsOnce(this). So, the imports are satisfied in the same way, i.e. from DocumentDatabase’s assembly and from a configured plug-ins directory.
In RavenDB triggers are the way to perform some custom action when documents are “put” (i.e. upsert) or read or deleted. RavenDB triggers also provide a way to block any of these actions from happening.
Raven also allows for custom actions to be performed when the database spins up using the IStartupTask interface.
Startup Tasks
When the DocumentDatabase class is constructed, it executes the following method after initializing itself.
- private void ExecuteStartupTasks()
- {
- foreach (var task in Configuration.Container.GetExportedValues<IStartupTask>())
- {
- task.Execute(this);
- }
- }
This method highlights the use of the CompositionContainer’s GetExportedValues<T> function, which returns all of the IStartupTasks in the catalogs created in the configuration object.
Conclusion
We’ve seen three important extensibility points in RavenDB supported by MEF: RequestResponders, triggers, and startup tasks. Next time, we’ll look at two more—view generators and dynamic compilation extensions—while learning more about RavenDB indices.