Im letzen Post “Logging mit der SharePoint Guidance Library” wurde, um einen Logger für SharePoint zu erstellen schon kurz der Service Locator erwähnt. Nun wollen wir uns genauer mit dem Locator beschäftigen und in einem Beispiel erarbeiten wozu das Service Locator Pattern verwendet werden kann und welche Probleme es löst.
Modulare, test- getrieben entwickelter Code ist der Grundstein für professionelle Softwareentwicklung. Um lose gekoppelte, testbare und vor allem erweiter- und wartbare Software aus einzelnen Komponenten zu erstellen müssen Abhängigkeiten zwischen Komponenten und auch zu externen Systemen entfernt werden. Jegliche fix verdrahtete Abhängigkeit erschwert sofort das Testen und führt zu einer starren Architektur. Eine solche Abhängigkeit ist sehr schnell erzeugt – das beste Beispiel hierfür ist das new() Schlüsselwort. Jeder verwendet es um Instanzen zu erzeugen. Das Erzeugen konkreter Instanzen jedoch ist bindet Komponenten aneinander. Folgender Code erläutert diesen Umstand.
1: public void CreateInvoice(Product product, Address address)
2: {
3: var calc = new ShippingCostCalculator();
4: product.ShippingCost = calc.CalculateCost(address);
5: }
In der Methode wird ein konkreter Konstruktor aufgerufen und somit sind die beiden Klassen fix aneinander gebunden. Noch schlimmer – die Klasse welche CreateInvoice() enthält, ist an die konkrete Implementierung der Klasse ShippingCostCalculator gekoppelt. Sollte sich etwas an der Berechnungslogik ändern, muss bestehender Code umgeschrieben und neu kompiliert werden. Ein Austauschen der Komponenten ist ebenfalls nicht möglich.
Das Service Locator Pattern ist eine Möglichkeit solche Kopplung zu vermeiden. Ganz einfach ausgedrückt werden dabei Dienste (Services), welche benötigt werden auf Implementierung gemapped. Ein Service wäre in diesem Fall der ShippingCostCalulator. Eine saubere Implementierung mittels des Patterns würde folgendermassen aussehen.
1: public void CreateInvoice(Product product, Address address)
2: {
3: var calc = ServiceLocator.Current.GetInstance(typeof (IShippingCostService));
4: product.ShippingCost = calc.CalculateCost(address);
5: }
Was hat sich geändert? Die konkrete Instanz von ShippingCostCalculator wird nicht mehr verwendet, somit ist die direkte Abhängigkeit von einer Implementierung entfernt. Anstelle des new() Aufrufes tritt der Service Locator. Der Locator nimmt den Typ eines Services entgegen. Services werden als Interfaces dargestellt. Ein Interface definiert welche Methoden ein Service öffentlich anbietet. Der Service Locator hat ein internes Mapping (wird unten beschrieben) welches Services (Interfaces) auf deren Implementierung mapped.
Was wurde erreicht? Der gesamte Code arbeitet gegen Services (Interfaces). Es gibt EINEN zentralen Ort an dem festgelegt wird welche Interfaces von welchen konkreten Implementierungen realisiert werden. Somit ist es möglich OHNE Änderungen im Produktivcode, die Implementierungslogik von IShippingCostService auszutauschen.
Arbeiten mit dem Service Locator aus der SharePoint Guidance Library
Der Service Locator aus der Library erlaubt es Mappings auf SPSite und auf SPFarm Ebene festzulegen. Das heisst jede SiteCollection kann eigene Services implementieren. Um einen Locator zu erzeugen existieren mehrer Möglichkeiten.
1: public void GetLocator()
2: {
3: var locatorSite = SharePointServiceLocator.GetCurrent();
4: var locatorNoContext = SharePointServiceLocator.GetCurrent(new SPSite("http://intranet"));
5: var locatorFarm = SharePointServiceLocator.GetCurrentFarm();
6: }
Der erste Aufruf lädt den Locator mit den Mappings aus der aktuellen SPSite, dies funktioniert nur wenn ein SPContext zur Verfügung steht. Ist kein Kontext verfügbar (zb. in einer eigenständigen Anwendung welche auf SharePoint zugreift)., kann die Überladung mit Angabe einer SPSite verwendet werden. Der dritte Aufruf referenziert den Locator für die komplette Farm.
Wird der Locator mit GetCurrent() referenziert, werden Mappings von der Farm und von der SiteCollectionebene geladen, wobei jene auf SiteCollection Ebene die entsprechenden Farm-weiten Mappings ersetzen.
Mappings definieren
1: public void CreateFarmMappings()
2: {
3: var serviceLocator = SharePointServiceLocator.GetCurrent();
4: var typeMappings =
5: serviceLocator.GetInstance<IServiceLocatorConfig>();
6:
7: typeMappings.RegisterTypeMapping<IShippingCostService, ShippingCostCalculator>();
8: }
Um Mappings auf Farm Ebene zu definieren, muss zuerst der aktuelle Locator referenziert werden. Danach wird die Konfiguration des Locators geholt. Diese ist standardmässig ebenfalls als Service bereits im Locator registriert.
Mittels RegisterTypeMapping wird nun ein Mapping definiert. Im Beispiel wird das IShippingCostService von ShippingCostCalculator implementiert.
Um Mappings auf SiteCollection Ebene zu definieren muss lediglich die Site mit angegeben werden.
1:
2: public void CreateSiteMappings()
3: {
4: var serviceLocator = SharePointServiceLocator.GetCurrent();
5: var typeMappings =
6: serviceLocator.GetInstance<IServiceLocatorConfig>();
7: //Site angeben
8: typeMappings.Site = SPContext.Current.Site
9:
10: typeMappings.RegisterTypeMapping<IShippingCostService, ShippingCostCalculator>();
11: }
Damit ist die Konfiguration abgeschlossen. Ein guter Ort diese Konfiguration auszuführen ist ein FeatureEventReceiver, welcher beim Installieren die Konfiguration erzeugt.
Wenn man sich eine klassische Anwendung welche auf SharePoint aufbaut vorstellt, wird aus dem Beispiel klar, wie der Locator eine Entkopplung erreicht und somit auch das automatisierte Testen ermöglicht.
Angenommen der ShippingCostCalculator greift in seiner Implementierung auf eine SharePoint Liste zu zum Errechnen der Kosten. Automatisiertes Testen fällt somit aus – niemand wird auf dem Build Server einen SharePoint Server installieren. Ausserdem müsste zum Testen jedesmal die SharePoint Liste in einen genau definierten Zustand versetzt werden und nach dem Test muss wieder aufgeräumt werden.
Geht man wie oben vor, kann man zum Testen ganz einfach eine Mock Implementierung von IShippingCostService im Locator registrieren, welche gefakte Werte liefert und somit ist die Abhängigkeit auf das externe System – SharePoint – verschwunden.
Viel Spass beim Testen!
Downloads:
SharePoint 2010 Guidance Library
Andreas Aschauer