Software Design mit Sitecore

Helmut Balzert beschreibt den Begriff Software Architektur als „eine strukturierte oder hierarchische Anordnung der Systemkomponenten sowie Beschreibung ihrer Beziehungen“.

Unabhängig davon welche Definition des Begriffes man nehmen möchte, im Normfall handelt es von einer Summe teilbarer Einheiten, welche in irgend einer Weise miteinander kommunizieren und in der Aussenansicht des Benutzers “irgendwie eben” funktioniert.

Welche Art von Architektur, nun in welchem Kontext und Bedürfnis die richtige ist, kann selten genau beantwortet werden. Entscheidungen sind oftmals getrieben von Erfahrungen, System-Kenntnisse, Rahmenbedingungen und Qualitätsanforderungen.

Aus diesem Grund betrachte, lieber Leser, den Inhalt dieses Artikels nicht als “die Lösung” an. Zuviele Seiten hat der “Würfel der Entscheidung” in diesem Falle um eine allgemeingültige Regel definieren zu können.

Nichts desto trotz haben wir aufgrund unseren Projekterfahrungen als Platinum Sitecore Partner und somit Implementierer von grossen Sitecore Lösungen, den Wunsch maximale Qualität anbietet zu können. Qualität bedeutet für uns nicht nur fehlerfreie Ausführung der einzelnen Codezeilen, sondern auch Austauschbarkeit, Wartbarkeit, Testbarkeit, und möglichst alle anderen Kriterien von ISO 9126. Dies alles zusammen mit der Rahmenbedingung “Sitecore” als Basis-Platform.

Sitecore bietet einem Entwicklerteam maximale Freiheit in der Gestaltung seiner Software Architektur und limitiert ihn lediglich in den Machbarkeiten des Basis-Stacks “ASP.NET” von Microsoft. Somit hat man viele Varianten, seine Lösung zu implementieren. Nun möchte ich jedoch in diesem Beitrag eine konkrete Lösung für eine Sitecore-Architektur präsentieren:

Schichtenmodell mit Sitecore by Namics

 

Das geübte Auge, bekommt nun ein klassisches Schichtenmodell präsentiert. Dem ist eigentlich nichts hinzuzufügen. Wir haben eine Präsentationsschicht (View), Steuerungsschicht (Controller), Datenzugriffsschicht (Integration / Persistence), sowie eine Geschäftslogikschicht (Model, Service, Functional). Letztere Schichtzuweisung bildet der Kern dieses Blog-Beitrages und wird folgend genauer unter die Lupe genommen. Die anderen Schichten möchte ich in separate Blog-Beiträge erwähnen. Ihr dürft Euch also auf einen Teil 2 (View-Engine) und Teil 3 (Persistance) freuen in denen ich Euch zeigen möchte, wie nahtlose Integrationen mit einer Frontend-Technologie (ohne Übernahme/Konveretierung in native *.cshtml Dateien) gemacht werden können, sowie in der Persistierung Produkte wie Glass-Mapper Qualitätsvorteile geben und mittlerweilen unerlässlich sind.

 

Der Controller

Nun zurück zu unseren Schichten:

2Schichten

In Sitecore ist die Thematik des Controllers ein wenig unüblich – im Kontext eines CMS aber interessant gelöst. Was man als erstes neuen Sitecore Engineers erklären muss, ist dass ein Request auf die Webseite nicht einen expliziten Controller aufruft, sondern mehrere. Jede Komponente im CMS besitzt einen eigenen Controller-Lebenszyklus. D.h. wenn ich 5 Komponenten auf meiner Seite präsentieren möchte, ruft Sitecore mind. deren 5 Controller auf, welche eine Verarbeitung ausüben. Dies klingt schonmal nach einer soliden Basis. Nun kann es aber vorkommen, dass viele Komponenten auf der Seite ähnlichen oder gleichen Code ausführen müssen. Also wiederkehrende, gleiche Codemuster haben, welche abgearbeitet werden.

Früher führte uns diese Tatsache oft in das Verwendungsmusters des Utility-Patterns für kleinere wiederkehrende Aufgaben. Auch im Kern des Sitecore-Kernel-Codes oder anderen Klassen findet man dieses Pattern sehr oft wieder. Wenn die Verarbeitungslogiken dann aber grösser wurden, nannte man sie einfacher XyManager. Das Entwurfsmuster war selten besser im Inneren. Oftmals Klassen, welche nicht mehr Testbar waren, mit einem hohen internen Wissen. Ansprechbar direkt über statische Methoden. Wer schonmal versucht hatte, Sitecore ernsthaft zu “mocken” (Entschuldigung für die Anglezismen), weiss wo die Probleme liegen. Zudem war es einem nicht möglich, diese interne Verarbeitung zu beeinflussen. Ausser es gab die Möglichkeit über eine geschickte Pipeline in die Verarbeitung einzugreifen. (Hierfür immer noch ein grosses Lob von mir an Sitecore. Dieses Pattern ist Gold Wert und ich wüsste nicht, was ich machen würde, gäbe es dies nicht!)

Wir haben uns lange Gedanken gemacht, wie wir das wartbar lösen können. Das Resultat ist ein verwandter Ansatz des Strategy Patterns mit dem Grundgedanken von SOA.

3Controller

 

Unser Ziel war es also, dass der Controller keine eigene Logik besitzt, sondern nur für die Steuerung zuständig ist und ein definiertes Model erhält, welches er in ein ViewModel abfüllt (MVVM). Die eigentliche Geschäftslogik soll von einem dedizierten Service verarbeitet werden, welches mit der Steuerung von Input-Parametern das richtig konfigurierte Model zurück liefert.
Wie sieht das nun in der Realität aus? Ich möchte mit einem Codebeispiel folgen:

In diesem leicht abgespeckten Beispiel sehen wir einen simplen MVC Controller mit einer Action namens “TitleNormal”. Innerhalb dieser Methode laden wir über einen injecteten RenderingContext ein typisiertes Item (siehe Glass.Mapper). Im Prinzip ist es aber nichts anderes wie das bekannte RenderingContext.Current.Rendering.Item . Also das Data-Item einer angegebenen Datasource in Sitecore. Dieses Item wird nun als Eingabeparameter einem “TitleService” übergeben.
Dieser Service wurde ebenfalls im Controller über Constructor Injection hinzugefügt. Als Rückgabewert, erhalten wir ein fertiges Model, welches vor dem Aufruf der View in ein eigenes ViewModel überführt wird.

Diese Überführung macht in diesem Kleinbeispiel wohl wenig Sinn, da das Model und das ViewModel über die gleiche Anzahl an Properties verfügt und die Komponente “TitleNormal” sozusagen das ganze Spektrum des Models benötigt.
Dennoch sehe ich diese definierte Überführung als Sinnvoll an, da der Controller nicht Wissen kann, wann der TitleService erweitert wird.
Ziel sollte es sein, dass der Controller genau weiss, welche Informationen “seine” View benötigt und nur diese Informationen der View zur Verfügung stellt.
Andersrum könnte es im Sitecore Kontext möglich sein, dass das ViewModel mehr Informationen wie das Model besitzt. Und zwar Viewspezifische Informationen, welche nichts mit der “BusinessLogik” der Komponente zu tun hat. 

Der Service

 

 

4Service  

Wie sieht nun der Service im Inneren aus? Hier weiter mit unserem Beispiel:

Wie wir hier erkennen ist ein wichtiges Grundprinzip die exzessive Arbeit mit Interfaces. Diese sind unablässlich für DependencyInjection und somit Kontrolle und spätere Erweiterung der Software.

Offensichtlich haben wir also eine Service-Facade vor uns, welche im Kern auf die richtige Verarbeitungsmethode der Logikschicht verweist. Für den Konsumenten eines Services ist die mögliche innere Architektur der Logikschicht also nicht ersichtlich. Was wir erkennen ist, dass wir auf unserem Service mehrere Möglichkeiten haben, ein Title zu konstruieren. Für uns Interessant ist jedoch nur die aufgerufte Methode GetNormalTitle(…).

Die Logik

 

5Functional

Wir folgen dem Beispiel und schauen in die Logik-Schicht hinein:

Wie hier ersichtlich ist, passiert erst in diesem Schritt die eigentliche Logik der Komponente. Das Datasource Item wird über die Sitecore-API der Sitecore.Kernel gelesen, deren Felder in das definierte Model abgefüllt werden. Berechnungen, If-Anweisungen, usw. – Logik eben.
Wie gehe ich nun aber mit der Situation um, wenn ich innerhalb der Logik-Schicht Codestellen habe, welcher ich gerne über mehrere Services wiederverwenden möchte?

In diesem Fall gibt es eine Grundlegende Fragen: Geht es um die Verarbeitung eines bestehenden Objektes oder um die Generierung eines “neuen” Models: Bei der Generierung eines neuen Models, darf und soll die Logikschicht wiederum einen anderen Service aufrufen. Services benutzen Services. Microservices? Im Prinzip, ja! So sollen benutzte Services gerne im inneren, wieder die Dienstleistung anderer Services in Anspruch nehmen. Dies führt zu einer maximalen Wiederverwendung des Codes bei nur einer Codebasis für die entsprechende Dienstleistung.

Der Processor

Wenn es um eine Verarbeitungseinheit handelt, haben wir uns für die Definition kleinster wiederverwendbarer Elementverarbeitung, dem sogenannten “Processor” geeinigt. Sobald ich ein definiertes Model auf die gleiche Weise verarbeiten möchte, auch. über Servicegrenzen hinaus, erstellen wir sogenannte “Processors”. Bei den Processors gibt es zwei verschiedene Modifikationsarten: Verarbeitung von Informationen innerhalb des gleichen Models, oder Austausch eines Models von ModelA nach ModelB. Folgend die Variante eines Model-Austausches:

Die DI Registrierung

Schlussendlich müssen alle Services noch in der DI-Config richtig registriert werden. Als Beispiel für unseren TitleService zusammen mit Unity könnte die Typenregistrierung wie folgt aussehen (UnityConfig.cs):

 

Fazit

So, was haben wir nun davon? Volle Flexibilität, Austauschbarkeit, Wartbarkeit und Testbarkeit!

6ServiceSchema

 

Es ist beispielsweise nicht mehr nur möglich eine einzelne Schicht auszutauschen, sondern über DI, jede Servicefacade, Logikelemente oder gar einzelne Prozessoren zu patchen und somit zu ersetzen. Auf jeder Granularitätsstufe, welche ich möchte. Über die Interface-Definition und Vermeidung von statischen Bauweisen, habe ich volle Testbarkeit. Die Fachlichkeiten werden innerhalb der Solution strikte getrennt und somit auch sehr wartbar.

Auf dem ersten Blick wirkt es nach einem massiven Aufwand für die Erstellung dieses Konstruktes. Nach der Übung eines oder zwei Services hat sich dies aber bereits so eingespielt, dass der Aufwand des Konstruktbaus kaum mehr messbar ist. Auch natürliche Fehler, welche aufgrund einer chaotischen Controller-Schlacht früher passierten, sind wie eliminiert, da eine aufgeräumte Code-Struktur automatisch zur Reduktion von Fehlern führt. So meine Meinung.

 

Wiederverwendung über mehrere Lösungen

Wir entwickeln nun seit längerer Zeit gemäss diesem Konzept. Trennbare für sich stehende Fachlichkeiten werden in eigene Services ausgelagert. Dies sehr konsequent. Um nun wiederkehrende Anforderungen projektübergreifend verwenden zu können, kann wiederum der grosse Vorteil der transportierbarkeit dieses Patterns als letzter Nutzen dieser Variante hervorgehoben werden. So haben wir beispielsweise Dienste wie ein TranslationService, SiteStructureService, oder TypeBuilderService erfolgreich in NuGet Pakete verpackt und können für uns intern diese wiederkehrenden Funktionalitäten in unseren Lösungen über ein einfaches Install-Package Namics.Sitecore.TypeBuilder installieren. Wiederverwendbarkeit par excellence.

Ein Gedanke zu “Software Design mit Sitecore

  1. Pingback: Modularisierung von Frontend mit NitroNet for Sitecore – Sitecore

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>