Author: Jamis Buck ([email protected])
Date: 25 Oct 2004
This document proposes several approaches to integrating Rails (http://www.rubyonrails.org) with the Needle Dependency Injection Framework (http://needle.rubyforge.org). These approaches vary from the superficial (with very little change to the existing Rails infrastructure) to the highly intrusive (involving major rewrites of large portions of Rails and its dependencies).
“Rails” is a collection of dependencies that together form a web application framework. These dependencies are:
The goal of this document is propose several new approaches to rearchitecting Rails’ components, especially with regards to taking advantage dependency injection (DI).
Dependency injection is a design pattern, closely related to the service locator pattern. It concerns itself with inverting an object’s control over its dependencies such that the object no longer cares what implements its dependencies. Instead, the object expects the client to tell it what object to use for each dependency. This process is called dependency injection, since the client is (conceptually) injecting each dependency into the object.
A dependency injection framework automates the process of injecting dependencies into objects. The framework is told not only which objects (called services in this document) to manage, but also what their dependencies are and how they should be instantiated. Then, clients of the framework request services by name, and the framework automatically instantiates and injects the dependencies of that service, returning to the client a ready-made, ready-to-use object.
Such a framework is also called a “light-weight container”, contrasting with the “heavy-weight” containers that the J2EE environment is notorious for.
The question needs to be asked, then:
“What would dependency injection bring to Rails that would be worth the effort?”
The answer depends on how deeply Needle is used to replace parts of Rails. At the simplest, using DI would reduce the tight coupling between the pieces of Rails (especially the ActionController and the ActionView). On a deeper level, DI could make it easier for third-parties to plug different pieces into the Rails framework, either as extensions, or as replacements for existing modules. This could even go so far as to become a kind of “plugin” system for Rails, although that particular goal is beyond the scope of this document.
The task of implementing Rails using DI is not trivial. In particular, Rails has made the following design decisions that make reimplemention difficult:
Although none of the above issues preclude the introduction of DI, they do complicate the implementation and require some careful thought.
Because Rails is already architected with a very modular design, the task of refactoring for DI is simplified somewhat (with the exception of where there is a tight coupling between those modules). Thus, one approach to refactoring is to introduce DI separately to each module, with the expectation that another module (i.e., “Rails” itself) would then tie those disparate modules together in a single DI container.
One significant problem (at least, as regards using DI) lies in the architecture of the ActiveRecord module. Because the AR module makes heavy use of class methods and mixins, it is not really feasible to rearchitect it to use DI. Such a rearchitecture in this case would result in an interface significantly different from what ActiveRecord currently exports. Thus, AR should probably be exempted from any such rearchitecture.
That said, it is certainly possible to create another ORM layer that does take advantage of DI. Then, clients of Rails could choose to use one or the other, as suited their preference.
With regards to ActionController and ActionView, some of the same challenges as AR exist, but not to the same degree. The primary challenge lies in reengineering in such a way to allow a DI container to be instantiated, and then have services added to that container.
ActiveRecord should not be refactored. It’s current design is such that it would not be possible to add dependency injection and still retain the same (or similar) interface. Instead, if an ORM layer that uses DI is desired, a new project should be created to implement one.
At the time of this writing, I do not have the necessary knowledge of the internals of ActionController and ActionView, and so am not yet competent to offer suggestions as to how they might be refactored.
Comments, suggestions, and discussion are welcome.