Click here to Skip to main content
Click here to Skip to main content
Technical Blog

DI and Pervasive services

, 12 Aug 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
Dependency Injection aids loose coupling. But there are some services that are pervasive, services that you would have to pass to a hundred different constructors if you want to use DI "properly". Is there an alternative?

I have been learning recently about Dependency Injection for loose coupling (for example, see these articles).

DI is really very simple. When a class X is designed for DI, it means that:

  1. X itself avoids specifying what other classes it depends on. For example, it avoids calling "new Y()" or using a singleton class, and it accesses the services it needs through interfaces (or, occasionally, abstract classes) rather than references to concrete types.
  2. X's dependencies are exposed explicitly in the constructor, or by properties of X, so that whoever creates/manages X can choose the dependencies.

Because X doesn't choose its own dependencies, a third party can do so on its behalf. This makes it possible to swap out a service that X needs for some other service without changing X itself, which may allow X to be used in more situations than it was designed for. For example, if X needs a database service, it would take a reference to some database-related interface (e.g. IQueryable<T> or whatever, I don't know, I don't use databases much myself) in its constructor. Now X may have initially been intended to use a MySQL database, but because X doesn't open the database connection itself, you may be able to switch to PostgreSQL or Oracle without changing X at all. For unit testing, you might not want to use a real database, so you could use a "mock object" that contains simulated data, based on LINQ-to-XML or something.

Often, there are often many levels of dependency. For example, a program for editing documents may have a main window, which contains a document editor, and toolbars/menus that depend on the document editor. The editor in turn depends on a document object and various user interface services. The document may depend on services for undo/redo, various data structures, and disk access, while the user interface services may depend on other data structures, drawing services, spatial analysis algorithms... who knows.

Often, in an app designed with DI, all the different components are wired together in a single place if possible, so that you can look in one place to see how components are connected and dependent on each other.

As the application grows more complex, the code that initializes all these objects via dependency injection also grows more complex. Eventually, you may get to a point where an IoC/DI framework like Ninject or Windsor or (my tentative favorite) Autofac can simplify all that initialization work.

But there are some services that are pervasive, services that you would have to pass to a hundred different constructors if you want to use DI "properly". Some examples are localization (to provide French and Spanish translations), logging (almost any component might want to write a diagnostic message), profiling (to gather performance statistics), and possibly "config options" (so end-users or admins can configure multiple components through a command-line, XML file or other source). In a compiler, a service for error/warning messages might be used all over the place. In Loyc, there might ultimately be hundreds of components that need to create AST Nodes or use other "pervasive" services.

It looks to me like passing such common services to constructors is more trouble than it's worth. If a component of Loyc may produce a warning message, is user-configurable, creates new AST nodes, and needs localization support, that's 4 constructor arguments just for "pervasive" services, never mind the more important, "meaty" services that it might need, like a parser, graph algorithm or whatever. If you use constructor injection, dozens of constructors are starting to smell.

How can we avoid the burden of passing around lots of pervasive service references? I'm not sure what the best way is. I have an idea in mind, but it is not appropriate for all cases. My idea involves a global singleton that can be swapped out temporarily while performing a specific task. I tentatively call it the Ambient Service Pattern.

For instance, in a compiler, the error/warning service might print to the console by default, or to an output window, but certain types of analysis might be "transactional" or "tentative": if an error occurs, the operation is aborted, and no error is printed, although if a warning occurs, it is buffered and printed if the operation succeeds. A concrete example of this scenario is the C++ rule known as SFINAE. Template substitution may produce an error, but if that error occurs during overload resolution, it is not really an error and no message should be printed. Given a global error service, we can model this rule by switching to a special error service, performing the operation, and then switching back afterwards.

To implement this pattern, use a thread-local variable alongside the interface to manage the service:

interface IService { ... }

class Service
{
    static ThreadLocalVariable<IService> _cur =
       new ThreadLocalVariable<IService>();

    // Current service
    public static Cur { get { return _cur.Value; } }

    // Use to install a new service temporarily, or to install the
    // initial service when the program begins
    public static PushedTLV<IService> Push(IService newValue)
        { return new PushedTLV<IService>(_cur, newValue); }
}

Depending on the app, there could be different threads doing independent work, which is why the thread-local variable is needed.

Note: If you need to support threads that fork, there is a problem because .NET thread-local variables cannot inherit their value from the parent thread. To work around this problem in Loyc, I made a whole infrastructure for thread creation, with a ThreadEx to wrap the standard Thread class, and a ThreadLocalVariable<T> class to be used instead of [ThreadStatic], which registers itself in a global weak reference collection so that when a thread is created with ThreadEx, the values of all ThreadLocalVariables can be propagated from the parent thread to the child thread (custom propagation behavior is also supported). Obviously, this workaround is a huge pain in the ass since all code must agree to use ThreadEx and the new .NET stuff like the "Parallel Extensions" won't propagate the thread local variables. I guess to properly support .NET 4, I should try to find a new solution based on ThreadLocal<T>.

PushedTLV is a helper class that changes the value of a thread-local variable until it is disposed. Use it like this:

using (var old = Service.Push(newService)) {
    // Perform an operation that will use the new service
}
// old service is automatically restored

An unfortunate consequence of this pattern is that the interface appears to be coupled to the thread-local variable. Sure, you could still write a class X that has a constructor-injected IService, but this might confuse those that indirectly use X: if someone changes Service.Cur, they might assume all code that needs an IService will use Service.Cur, but X could be using a different IService.

While the Ambient Service Pattern doesn't work like traditional dependency injection, it still follows the spirit of DI because components remain independent from a specific implementation of the pervasive service.

In the version of the pattern seen here, the service provided acts as a singleton. Of course, if the service is something that can be instantiated on-demand (e.g. a data structure), the thread-local variable would hold a factory instead of a singleton. The "Push()" method would switch to a different factory instead of a different instance, and there would be a "New()" method instead of a "Cur" property.

I wonder if the .NET Framework itself should adopt this kind of pattern. Consider what you do to open a file: you call File.Open() or make a FileStream, right? Now what if management decides "we need our app to be able to open files from an FTP site". Rather than change the code that calls File.Open() (and what if a third-party library is calling it?), wouldn't it be more elegant if you could swap in a new file management service that understands FTP sites (and perhaps maintains a local disk cache)? And hey, what if you want to change your console app to use a graphical console with icons and proportional fonts? What if you decide Debug.WriteLine needs to store a log somewhere?  

Here's my implementation of PushedTLV, but as I mentioned, it's based on my own special ThreadLocalVariable class.

/// <span class="code-SummaryComment"><summary>Designed to be used in a "using" statement to alter a 
</span>/// thread-local variable temporarily.<span class="code-SummaryComment"></summary>
</span>public class PushedTLV<T> : IDisposable
{
    T _oldValue;
    ThreadLocalVariable<T> _variable;

    public PushedTLV(ThreadLocalVariable<T> variable, T newValue)
    {
        _variable = variable;
        _oldValue = variable.Value;
        variable.Value = newValue;
    }
    public void Dispose()
    {
        _variable.Value = _oldValue;
    }
  
    public T OldValue { get { return _oldValue; } }
    public T Value { get { return _variable.Value; } }
}

Do you have another idea for managing pervasive services with minimum fuss? Do tell.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Qwertie
Software Developer Trapeze Software, Inc.
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, and LLLPG, among other things. Now I'm old.
 
In my spare time I'm developing a system called Loyc (Language of your choice), which will include an enhanced C# compiler. Many programs have an add-in architecture; why not your programming language? I'm also looking for a life partner. Oh hi future wife! Wazzap.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 12 Aug 2010
Article Copyright 2010 by Qwertie
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid