to a variety of configurations for research and experimentation, but LensKit provides the implementations and configuration infrastructure necessary to test many different variants out-of-the-box. We are, however, working on solving the getting-started problem through improved documentation, more example code, and simplified wrapper APIs.
3.11 Usage and Impact
Since we originally published LensKit in 2011 [Eks+11], it has seen use in several research projects. In our own research, we have used LensKit for the algorithmic analyses in the rating interface experiments we have run [Klu+12; Ngu+13], as well as the research de- scribed in the remainder of this thesis. Google Scholar records a total of 27 citations of the core LensKit paper [Eks+11]14. LensKit also provides the recommendations for several live systems:
• MovieLens, operated by GroupLens Research, provides movie recommendation and tagging services. URL:http://www.movielens.org
• BookLens, also operated by Grouplens, provides book recommendations integrated with library card catalogs. URL:http://booklens.umn.edu
• Confer, from MIT CSAIL, is an online conference program site that uses LensKit to recommend papers for conference attendees to see and other attendees that they may wish to meet. URL:http://confer.csail.mit.edu/
From time to time, someone will post on the LensKit mailing list with a question about using LensKit in some new environment, and there are also likely other uses that we have not heard about.
3.11. Usage and Impact
Our MOOC on recommender systems (Introduction to Recommender Systems on Cours- era) used LensKit as the basis for its programming assignments; we had 800–1000 users complete the programming assignments.
LensKit is also regularly brought up in discussions about reproducible recommender systems research. We encouraged the recommender research community to adopt a cul- ture of publishing code built and tested against accepted, publicly-available recommender platforms to support new recommender algorithm and evaluation research. While this has not yet been established as a general norm, there is increasing interest in reproducible re- search and best practices for comparable recommender research, which is an encouraging sign. It will take the community time to establish best practices for evaluating recommender research, and the conversation seems to be going in profitable directions.
Chapter 4
Supporting Modular Algorithms
M — LensKit included — comprise many components that work together to provide the system’s functionality. Individual components seldom operate alone; many of them depend on other components to fulfill their responsibilities. In the last twenty years, the dependency injection (DI) pattern has seen wide adoption as a means of fitting together the components of such systems, and we have adopted it for designing and configuring LensKit’s recommender implementations as described in section 3.7. Unfortu- nately, LensKit’s needs are not well-served by existing dependency injection toolkits. The first versions of LensKit used Google Guice for instantiating algorithms; when it proved in- adequate, we tried using PicoContainer, which was also a poor fit. We finally wrote Grapht, a dependency injection toolkit for Java, to manage LensKit’s dependency injection with a new set of configuration and graph processing capabilities.1
Our work on dependency injection has been driven by two specific shortcomings with other toolkits with respect to LensKit’s needs:
• Limitations on configuration that severely hinder the composeability of components. Most toolkits to not deal well with the same class or interface appearing in many places in the object graph with different implementations or configurations, and the tools they do provide for such scenarios are weak. This means components cannot be 1Michael Ludwig contributed significantly to the work in this chapter, particularly refining the design and writing the initial implementation. We are preparing this work to submit for publication.
4.1. Dependency Injection
freely reused and composed, but need to be wrapped in extra classes that know about how they fit into the final object graph.
• Inability to construct and process the object graph as a first-class object prior to instan- tiation made it difficult — if not impossible — to provide robust support for detect- ing & reusing common components in experiments, separating prebuilt and run-time components, and providing diagnostic and debugging support for algorithm configu- rations.
Grapht addresses both of these concerns: the first through context-sensitive policy, al- lowing objects to be configured based on where they are used, and the second by decoupling dependency resolution from object instantiation and exposing the resolved object depen- dency graph as an object that can be analyzed and manipulated. Grapht’s internal archi- tecture is built on this object graph abstraction and many of its features are implemented in terms of graph transformations; this has the side effect of making it amenable to formal treatment. We use a formal model of dependency injection — a abstraction of Grapht’s design — to describe the key algorithms and to show that certain commonly-used depen- dency injection features are technically superfluous, replacable with strictly more expresive alternatives.
4.1 Dependency Injection
Dependency injection [Fow04; YTM08] is a design pattern arising from applying Inversion of Control to the problem of instantiating objects that have dependencies on other objects. If a component A requires another component B in order to fulfill its obligations, there are sev- eral ways that it can obtain a suitable reference. A can instantiate B directly (listing 4.1(a)); this is straightforward, but makes it difficult to substitute alternative implementations of
4.1. Dependency Injection
public UserUserCF() {
similarity = new CosineSimilarity(); }
(a) Direct instantiation
public UserUserCF(SimilarityFunction sim) { similarity = sim;
}
(b) Dependency injection
Listing 4.1: Constructors depending on another component.
B. A can also obtain B from some other service, like a factory or service locator, allowing alternative implementations to be used but making A dependent on the resolution strategy. Finally, in dependency injection, A can require B to be provided via a constructor argument (listing 4.1(b)). That is, the dependency (B) is injected into A. Whatever component creates A is therefore free to substitute an alternate implementation or reconfigure B in any way it wishes.
When used throughout the design of a system, dependency injection (DI) provides a number of advantages. Most follow from reduced coupling between components. Some of these advantages include:
• Components are free of all knowledge of the implementations of their dependencies — they do not even know what classes implement them or how to instantiate them, only that they will be provided with a component implementing the interface they require.
• Components can be reconfigured by changing the implementations of their depen- dencies without any modification to the components themselves.
4.1. Dependency Injection A B C D E (a) Class diagram
A B C D E (b) Object diagram Interface Depends/Uses Implementation Implements/Extends Object Provides (c) Shape legend. Figure 4.1: Class dependencies, object graph, and legend.
• Components can be more easily tested by substituting mock implementations of their dependencies. While mocking is not new, the component design encouraged by de- pendency injection makes it particularly easy to substitute mock objects.
• Each component’s dependencies are explicit, appearing as formal arguments of the constructor setup and initialization methods, so the component’s interaction with the rest of the system is largely self-documenting. This can make the system easier to understand and more amenable to static analysis.
The reduced coupling and increased flexibility of dependency injection comes with a cost: in order to instantiate a component, its dependencies must be instantiated first and provided via a myriad of constructor parameters. This requires the code initializing a com- ponent to know its dependencies, construct them in the proper order, and pass them in to the constructor. Doing this manually, while possible, is cumbersome.
To make it easier to configure and instantiate software built around dependency injec- tion, a number of toolkits called dependency injectors or DI containers have been devel- oped. These toolkits take care of resolving the dependencies of the various components in a system, instantiating them in the proper order, and wiring them together. This automated support for dependency injection is sometimes called autowiring.