Für die Freunde des Flow-Designs hier zu Weihnachten eine kleine Überraschung: Flows verteilen mit der Flow Runtime einfach gemacht. Als Beispiel soll Bestellung und "Aufbau eines Weihnachtsbaums dienen.
Wer kennt das nicht: Vor lauter Weihnachtsfeier- und Jahresendprojektstress wird der Kauf eines Weihnachtsbaums bis zum letzten Tag verschoben. Und dann muss man sehen, was übrig bleibt. Das macht die Familie gar nicht glücklich.
Aber jetzt gibt es eine Weihnachtsbaumanwendung für den Windows Desktop :-) Mit der kann man sich einen Weihnachtsbaum in verschiedenen Größen bestellen. So siehts aus:
Mit dem Slider auf der rechten Seite stellt man die gewünschte Zahl der Äste ein – und schon wird der Weihnachtsbaum hergestellt und “aufgebaut”. Naja… nur auf dem Bildschirm. Aber immerhin. Bevor es gar keinen gibt… ;-)
Die Herstellung des Weihnachtsbaums übernimmt eine TreeFactory:
In ihr gibt es drei “Herstellungsstationen”, die ein Baum nacheinander durchläuft. Der Flow dafür sieht so aus:
Zuerst werden nur die Äste in Form von *-Reihen erzeugt, dann werden diese Reihen so eingerückt, dass das charakteristische Dreieck entsteht, und schließlich kommt darunter ein kleiner I-Stamm.
Lokale DIY-Herstellung
In der ersten Ausbaustufe wird der Weihnachtsbaum lokal hergestellt, sozusagen xmas tree fabbing. Die TreeFactory läuft in der Desktop-Anwendung im Rahmen eines allgemeineren “Weihnachtsfabrik”-Flusses. Weihnachtsbäume sind ja auch nur eine, was man zum Fest herstellen könnte:
Die Anwendung bestellt beim Start sogleich einen Baum mit 2 Ästen - und danach immer wieder, wenn der Schieberegler bewegt wird. Und das alles from the comfort or your living room :-)
Auslagerung der Produktion
Das Weihnachtsbaum fabbing ist hübsch modern. Aber leider kommt dabei nur ein Weihnachtsbaum aus “Plastik” heraus. Ein natürlicher Weihnachtsbaum wäre schöner, oder? Oder einer, bei dem man aus verschiedenen Materialien wählen kann? Jedenfalls scheint eine Auslagerung der Weihnachtsbaumproduktion Vorteile zu bieten.
Aus der lokalen Weihnachtsbaumproduktion soll deshalb eine entfernte werden. Allerdings ohne große “Off-Shoring-Schmerzen”. Wie kann das mit Flows gehen?
Aus der Desktop-Anwendung wird ein Desktop-Client und die Produktion wandert in einen Server.
Dazu wird der Flow xmasfactory in der Anwendung durch eine generische so genannte Stand-In Operation ersetzt:
Die sieht nach außen immer genauso aus wie die Funktionseinheit, die sie ersetzt – intern jedoch leitet sie Input über ein Kommunikationsmedium weiter an die entfernte Funktionseinheit und leitet deren Output zurück in den lokalen Flow. Im Beispiel geschieht das mit WCF als API und TCP als Medium.
Der Code ändert sich dadurch wie folgt:
Die WcfStandInOperation bekommt eine lokale Adresse, auf der sie entfernten Output empfängt, und eine entfernte Adresse für die “off-shore” xmasfactory, an die sie Input sendet.
In der Flow-Definition wird der bisherigen lokalen xmasfactory der Name der Stand-In Operation mit “#” voran gesetzt. Dadurch wird die Stand-In Operation adressiert, behält über die Instanzkennung [1] nach dem “#” aber die Information, welche Funktionseinheit sie ersetzt.
Die WcfStandInOperation kommt aus der Assembly npantarhei.distribution.wcf, die der Client nun referenzieren muss. Die ist nun Teil des NPantaRhei Repositorys.
Soweit der Client. Er wird im Grunde einfacher. Die Servicefunktionalität entfällt und wird ersetzt durch ein Stand-In.
Aber keine Sorge, der Server, in dem sie neu aufgehängt wird, ist leicht herzustellen. Der enthält die extrahierte Funktionseinheit nun allein auf oberster Ebene in einem Flow:
Daran ist keine Veränderung vorzunehmen; sie muss sogar so aussehen, wie zuvor, weil sie sonst nicht durch das Stand-In korrekt repräsentiert würde.
Allerdings wird die xmasfactory nun direkt über Flow-Ports angesteuert. Wie sollen die heißen, damit sie automatisch bestimmt werden können? Aus xmasfactory.Build_tree wird .Build_tree@xmasfactory.
Dieser kleine Trick ist nötig, weil in einer Portbeschreibung nur ein Punkt vor dem Portnamen stehen soll. Die Beschreibung .xmasfactory.Build_tree wäre daher ungültig. Also wird der Portname nach vorne gezogen und der Funktionseinheitsname mit “@” dahinter gesetzt. Das ist nicht besonders schön, funktioniert aber erstmal. So können aus der Instanzkennung und dem Portnamen vom Stand-In die serverseitigen Portbeschreibungen bestimmt werden und umgekehrt.
Damit Requests an den Server zu Input für den Flow werden und Output als Response zum Stand-In zurückfließen kann, wird die Flow Runtime in einem so genannten Operation Host gehalten:
Der Operation Host empfängt über einen transportmediumspezifischen Transceiver Daten als Input für Flows in der Runtime; und Output aus Wurzelports der Runtime leitet er über den Transceiver dorthin zurück, von wo der zugehörige Input gekommen ist. Den Bezug stellt er über eine out-of-band Correlation-Id her, die er dem Input bei Empfang aufprägt und die über den ganzen Flow erhalten bleibt.
Was sich vielleicht kompliziert anhört, ist in der Codepraxis aber simpel:
In der Flow-Definition taucht die xmasfactory wie bekannt aus der lokalen Anwendung 1:1 auf. Ansprechbar wird sie gemacht durch die globalen Port des Wurzel-Flows wie oben beschrieben.
Ansonsten kommt nur der WcfOperationHost dazu, der die Flow Runtime aufnimmt und eine Adresse erhält, auf der er angerufen werden kann.
Fertig!
Mehr ist nicht zu tun, um eine Funktionseinheit entfernt zu betreiben. Der Flow im Client merkt davon im Grunde nichts. Und die entfernte Funktionseinheit auch nicht. Sie müssen also nicht immer wieder neue Servicedefinitionen vornehmen wie bei WCF. Mit denselben Bausteinen – Stand-In Operation und Operation Host – können Sie jede Funktionseinheit verteilen. Nur die Daten, die über das Transportmedium als Input dorthin und als Output von dort zurück fließen, müssen [Serializable] sein.
Fazit
Die Verteilung von Flows sollte so einfach wie möglich sein. Ich glaube, das ist nun ganz grundsätzlich der Fall. Die WCF-Bausteine sind dabei nur erste einfache Adapter für ein Transportmedium als Beispiele dafür, wie es geht. Nähere Informationen dazu und auch zur Behandlung von Exceptions in verteilten Flows, in der dotnetpro 2/2013.
Ich hoffe, es ist rüber gekommen, dass Sie Flows jetzt lokal entwickeln und testen können – und anschließend einen beliebigen Teil einfach verpflanzen. Dass die Kommunikation dann asynchron wird, ist nicht so schlimm, weil Flows darauf ohnehinn viel besser vorbereitet sind.
Indem Sie vor der Verteilung zum Beispiel ein Operation im zu entfernenden Flow auf asynchrone Verarbeitung setzen oder den Verarbeitungsmodus der Flow Runtime auf asynchrone Breitenorientierung setzen, können Sie deklarativ ganz einfach simulieren, wie sich die Verarbeitung nach der Verteilung verhalten wird.
Oder sie betreiben die Flow Runtime für den Client und die Flow Runtime für den Server im selben Adressraum mit einem kleinen Bus verbunden statt über WCF. Dazu finden Sie eine Hilfestellung in der Assembly npantarhei.distribution, auf die npantarhei.distribution.wcf aufbaut.
Fußnoten
[1] Instanzkennungen sind schon lange ein Feature der Flow Runtime. Sie kommen immer dann zum Einsatz, wenn eine Funktionseinheit in einem Flow an mehreren Stellen mit dem selben Namen benutzt werden soll.
Das ist bei Stand-In Operationen auch der Fall. Die selbe Stand-In Operation soll für verschiedene entfernte Funktionseinheiten stehen können.