Dependency Injection: Microkernel selbst gebaut

Ich interessiere mich bereits seit längerer Zeit für CCD. Eine der CCD-Praktiken stellt Dependency Injection dar, wobei es im Wesentlichen um nichts anderes, als das späte Binden von Abhängigkeiten geht, sodass ein Austauschen von Komponenten ohne ein erneutes Übersetzen der Anwendung möglich wird. Hier stelle ich meine persönliche Microkernel-Implementierung zur Verfügung, mit der Komponenten per Konfiguration in eine Anwendung integriert werden können.

Eine ausführliche Zusammenfassung zum Thema Contract-First-Design, sowie eine (aus der Fachpresse) bekannte Microkernel-Implementierung sind unter: http://weblogs.asp.net/ralfw/ zu finden.

Um den Microkernel einsetzen zu können, wird ein entsprechender Konfigurationsabschnitt in der app- bzw. web.config-Datei benötigt. Im microkernelSection-Abschnitt befindet sich eine Auflistung von sogenannten Containern, wobei je Interface jeweils ein Container definiert wird, dem dann ein (oder mehrere) Implementierungen hinzugefügt werden können.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microkernelSection" type="Configuration.MicrokernelConfigurationSection, Configuration"/>
  </configSections>
  <microkernelSection>
    <containers>
      <clear /> 
      <container interfaceType="Interfaces.ILogger,Interfaces" defaultComponent="httpLogger"></container> 
    </containers>
  </microkernelSection>
</configuration>

Für das Beispiel des ILogger-Interface habe ich verschiedene Implementierungen im microkernel-Konfigurationsabschnitt deklariert. Das defaultComponent-Attribut des container-Elements kennzeichnet die zu verwendende Standard-Implementierung.

... 
<container interfaceType="Interfaces.ILogger,Interfaces" defaultComponent="httpLogger">
  <components>
    <clear />
    <component name="httpLogger" implementationType="Logging.HttpLogger, Logger.Http" singleton="true" />
    <component name="sqlLogger" implementationType="Logging.SqlServerLogger, Logger.SqlServer" singleton="true" />
    <component name="consoleLogger" implementationType="ConsoleLogger, DebugUtils" singleton="true" />
  </components>
</container> ...

Im Anwendungsverzeichnis befinden sich alle Abhängigkeiten - wobei demo.exe keine Verweise auf Assemblies enthält, die Implementierungen bereitstellen.

Konkrete Implementierungen zur Laufzeit einbinden

Wie die Anwendung konfiguriert werden muss, habe ich im vorherigen Abschnitt bereits gezeigt. Bevor ich auf die Implementierung der DynamicBinder-Klasse eingehe, zeige ich jedoch zuvor deren Verwendung. Das folgende Beispiel zeigt, wie ein ILogger via Microkernel in der Anwendung erzeugt werden kann...

using Interfaces;

private sealed class Program
{
  void Main(string[] args)
  {
    ILogger logger = DynamicBinder.CreateInstance&lt;ILogger&gt;();
    logger.Log("Hello World.");
  }
}

Natürlich kann auch eine von der Standard-Implementierung abweichende Komponente zur Laufzeit angezogen werden. Dazu kann der CreateInstance-Methode einfach ein Schlüssel übergeben werden, unter dem die Implementierung in der Konfiguration hinterlegt ist.

... 
ILogger sqlLogger = DynamicBinder.CreateInstance<ILogger>("sqlLogger"); 
logger.Log("Hello World."); ...

Die DynamicBinder-Klasse

Das Herzstück der DynamicBinder-Klasse bildet die CreateInstance()-Methode; darüber hinaus hat die Klasse nicht viel zu bieten, denn mehr ist für einen funktionierenden Mini-Kernel auch nicht notwenig :-)

Anhand des Typs, der in T übergeben wird, wird der Container ermittelt, der die Komponente beinhaltet, von der eine Instanz erzeugt werden soll. Wird im name-Parameter nichts angegeben, wird die in defaultComponent festgelegte Implementierung angezogen.

... 
public static T CreateInstance<T>(string name)
{ 
  if (configSection == null) 
    throw new InvalidOperationException(); 
  
  Configuration.ContainerConfigurationElement container; 
  if ((container = configSection.Containers.GetContainer( typeof(T))) != null) 
  { 
    if (string.IsNullOrEmpty(name)) 
      name = container.DefaultComponent; 
      
    Configuration.ComponentConfigurationElement component; 
    if ((component = container.Components[name]) != null) 
    { 
      Type impl = Type.GetType(component.Implementation); 
      if ((component.IsSingleton) && (singletons.ContainsKey(impl))) 
        return (T) singletons[impl]; 
        
      T instance; 
      Dictionary<string, string> settings; 
      if ((settings = component.GetSettings()) != null) 
        instance = (T) Activator.CreateInstance(impl, settings); 
        
      else 
      { 
        instance = (T) Activator.CreateInstance(impl); 
      } 
      
      if (instance != null) 
      { 
        if ((component.IsSingleton) && (singletons.ContainsKey(impl) == false)) 
          singletons.Add(impl, instance); 
        
        return instance; 
      } 
    } 
  } 
  
  return default(T); 
} ...

Unterstützung von Singletons

Der Microkernel unterstützt die Verwendung von Singletons. Nach dem Erzeugen einer Objekt-Instanz wird diese zwischengespeichert, wenn die Komponente in der Konfiguration als Singleton gekennteichnet wurde. Wird die Komponente ein weiteres Mal über den Microkernel angefordert, wird keine neue Instanz erzeugt, sondern das gespeicherte Objekt zurückgeliefert.

... 
<container interfaceType="Interfaces.ILogger,Interfaces" defaultComponent="httpLogger"> 
  <components>
    <clear />
    <component name="httpLogger" implementationType="Logging.HttpLogger, Logger.Http" singleton="true" />
  </components>
</container> ...

Speichern der Instanz...

...
if (instance != null)
{
  if ((component.IsSingleton) && (singletons.ContainsKey(impl) == false))
    singletons.Add(impl, instance);

}
...

Abrufen der Instanz...

...
Type impl = Type.GetType(component.Implementation);
if ((component.IsSingleton) && (singletons.ContainsKey(impl)))
  return (T) singletons[impl];
...

Links

Clean Code Developer
http://www.cleancodedeveloper.de/

Download: matzef.microkernel.zip
http://matze-friedrich.de/dev2/downloads/matzef.Microkernel.zip
Visual Studio 2008 Projektmappe

Ralf Westphal: http://weblogs.asp.net/ralfw/
http://weblogs.asp.net/ralfw/archive/2006/08/02/Dynamic-component-binding-made-easier-_2D00_-An-easy-to-use-Microkernel-to-help-reap-Contract_2D00_First_2D00_Design-benefits-in-.NET-programs.aspx
Dynamic component binding made easier - An easy to use Microkernel to help reap Contract-First-Design benefits in .NET programs