Librarys
Librarys dienen dazu, um Code (Funktionalität und Datentypen) in verschiedenen DialogFlows, DialogSteps oder wiederum Librarys nutzen zu können und somit dem DRY-Prinzip gerecht zu werden. Da Librarys insbesondere in DialogSteps angebunden werden, stellen sie Assembler, Gateways und ViewModels bereit.
In diesem Diagramm ist die Sichtweise auf Basis folgender technischer Artefakte dargestellt:
- DynamicModel (z.B. ViewModel)
- Reference - Referenz auf Services, Librarys, Flows, DialogSteps
- CodeBehind - C#-Code, der durch die Bibliothek bereitgestellt wird. Das sind Gateways, Assembler, ViewModel etc.
- WSDL - die Beschreibung eines Webservice.
Diese Bibliotheken wiederum in DialogFlow, DialogStep und Library genutzt.
Inkompatible Änderungen und deren Auswirkungen
Wichtig hierbei sind folgende Fragestellungen: Was passiert bei inkompatiblen Änderungen? Welche Änderungen sind inkompatibel? Inkompatible Änderungen sind sehr zu vermeiden, insbesondere, wenn Bibliotheken in Kette geschaltet sind. Wird hierbei die letzte Bibliothek inkompatibel, so sind auch alle nutzenden Bibliotheken nicht nutzbar.
DRY revisited
Um Code zwischen Dialogabläufen wiederzuverwenden und somit dem DRY-Prinzip zu genügen, wird pro Service und pro GP-Komponente eine sogenannte ServiceClient-Library mit dem Namen des Services erstellt. Diese Bibliothek darf also von den Flows, den DialogSteps und den Libraries der GP-Komponente referenziert werden.
Namenskonvention: <Servicename>Client
In dieser Bibliothek werden der ServiceClient, die Datenverträge und optional weitere Model-Klassen definiert. Das folgende Beispiel zeigt eine einfache Ausprägung:
<?xml version="1.0" encoding="utf-8"?> <Library ... Namespace="Schleupen.AS.mal.bal.bap.Gateways" Name="BuchQueryServiceClient" Version="3.1.0.0"> <CodeBehind /> <References> <WebserviceReference Id="Schleupen.AS.MT.BIB.Buecher.BuchQueryService_3.1" Name="BuchQueryService" Embedded="true" AccessibilityLevel="Public" /> </References> <Classes> <StaticClass Name="WSProxies"> <WebserviceCallMethod Name="Query" Service="BuchQueryService" Operation="Query" /> <WebserviceCallMethod Name="QueryTitel" AccessibilityLevel="Internal" Service="BuchQueryService" Operation="QueryTitel" /> </StaticClass> </Classes> </Library>
Verträge zwischen Dialogablauf und Dialogschritten - ContractLibrary
Die Verknüpfung von Dialogschritten muss lose gekoppelt erfolgen. Daher ergibt sich hier die Frage, ob einfache Librarys hier ausreichend sind.
Der direkte Zugriff auf die internen Objekte ist nicht möglich (d.h. wird nicht unterstützt), auch wenn wie in folgendem Beispiel Step und Flow dieselbe Bibliothek verwendet wird. Wird beispielsweise ein BuchContract-Objekt von Step zu Flow gegeben, so wird eine tiefe Kopie dieses Objekts erstellt und weitergegeben. Damit werden unerwünschte Seiteneffekte von Flow zu DialogStep vermieden.
Für den Datenaustausch stellt ein Provider (als ein Flow oder DialogStep) eine Schnittstelle bereit, welche die Datenstrukturen für den Datenaustausch beschreibt. Man unterscheidet hierbei zwischen Input- und Output-Daten (analog zu Request/Response bei Services). Die Nutzer dieses Datenaustauschs werden Consumer genannt. Flows können sowohl Consumer als auch Provider sein.
Ein Step darf und kann keine Flows aufrufen. Daher ist der Datenaustausch in dieser Richtung nicht möglich.
Die Kopplung ist aber dennoch hoch, da hier dieselbe Code-Klasse verwendet werden muss.
Das ist zwar möglich, aber besser ist es, ContractLibraries zu verwenden:
Die Datenstrukturen für den Datenaustausch (Input, Output) zwischen Dialogschritten werden in ContractLibrarys definiert. Zur Laufzeit werden Instanzen von diesen Datenstrukturen ausgetauscht. Hierbei werden tiefe Kopien erzeugt.
Betrachten wir hierzu noch einmal das Beispiel des Ausleihens von Büchern, um die Implementierung zu skizzieren:
Zwischen jedem Dialogschritt wird eine ContractLibrary die den Namen des Schrittes beinhaltet, definiert. Beispiel: BuecherAuswaehlenContract
Namenskonvention: <Dialogschrittname>Contract
Diese ContractLibrary hat in diesem Beispiel folgende Gestalt:
<?xml version="1.0" encoding="utf-8"?> <ContractLibrary ... Namespace="Schleupen.AS.mal.bal.bap.BuecherAusleihen" Name="BuecherAuswaehlenContract" Version="3.1.0.0"> <Input ModelType="InputContract" /> <Output ModelType="OutputContract" /> <Models> <ContractModel Name="InputContract"> </ContractModel> <ContractModel Name="OutputContract"> <ContractAttribute Name="AusgewaehlteBuecher" Type="List`1[BuchContract]" Occurrence="Optional" /> </ContractModel> <ContractModel Name="BuchContract"> <ContractAttribute Name="Id" Type="System.Guid" Occurrence="Required" /> </ContractModel> ... </Models> ... </ContractLibrary>
Da in diesem Schritt Bücher ausgewählt und dem nächsten Schritt mitgegeben werden müssen, werden nur typisierte IDs als Output definiert (BuchContract
).
Um eine möglichst geringe Kopplung zwischen den Dialogschritten zu erreichen, werden nur typisierte IDs zurückgegeben. Damit muss der nächste Schritt zwar erneut die notwendigen Daten laden, was im allgemeinen nicht performance-kritisch ist und zudem mit aktuellen Daten gearbeitet wird. Die lose Kopplung ist hier wichtig!
Der folgende Code zeigt exemplarisch die Anbindung einer ContractLibrary in einem Dialogschritt:
<?xml version="1.0" encoding="utf-8"?> <DialogStep ... Namespace="Schleupen.AS.mal.bal.bap.BuecherAusleihen" Name="BuecherAuswaehlenStep" Version="3.1.0.0" Title="Bücher auswählen"> <Interface ImplementsContract="Schleupen.AS.mal.bal.bap.BuecherAusleihen.BuecherAuswaehlenContract_3.1" /> <Properties> </Form> </DialogStep>
GP-Komponenten-übergreifende Nutzung von Dialogabläufen
Die GP-Komponenten-übergreifende Nutzung von DialogFlows bringt weitere Herausforderungen mit sich. Zum einen werden diese im Allgemeinen durch unterschiedliche Teams betreut und zum anderen werden diese zu unterschiedlichen Zeitpunkten aktualisiert. Das bedeutet, dass eine hohe Stabilität notwendig ist und Inkompatibilitäten entsprechend kommuniziert werden müssen.
Komponenten-übergreifend darf nicht auf einfache Libraries zugegriffen werden! Es dürfen hier nur ContractLibraries verwendet werden.
Bei der Nutzung von WebServices besteht dasselbe Problem - dort werden Datenverträge und eine Anticorruption-Layer zur notwendigen Entkopplung implementiert. Durch eine Beschreibung werden jeweils die notwendigen Typen bereitgestellt und durch Serialisierung und Deserialisierung transportiert.
Dasselbe Verfahren wird bei der Verwendung externer Flows verwendet: Es werden für Ein- und Ausgabe sogenannte FlowContracts verwendet, die reine Schnittstellentypen für den DialogStep darstellen. Mithilfe von Assemblern werden diese Typen zu Typen des Flows übersetzt.
Die Übergabe des Input-FlowContracts und des Output-FlowContracts erfolgt, indem die Objekte serialisiert und wieder deserialisiert werden. Analog zu Service sorgt dies für die notwendigen Entkopplungen und Consumer und Provider können unterschiedliche (kompatible) Versionen derselben Bibliothek nutzen.
Im konkreten Beispiel wird eine Library namens BuecherAusleihenFlowContract für den Flow bereitgestellt bzw. genutzt. Um eine maximale Entkopplung aus Nutzungssicht zu erzielen, wird pro Flow eine sogenannte Contract-Library erstellt.
Das folgende UML-Diagramm zeigt noch einmal die Zusammenhänge in UML: