{"id":139,"date":"2020-07-27T12:01:21","date_gmt":"2020-07-27T12:01:21","guid":{"rendered":"https:\/\/intern.quizu.de\/?p=139"},"modified":"2020-07-27T12:08:03","modified_gmt":"2020-07-27T12:08:03","slug":"microservice-architektur-protokoll","status":"publish","type":"post","link":"https:\/\/intern.quizu.de\/?p=139","title":{"rendered":"Microservice Architektur &#038; Protokoll"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Grundlagen<\/h2>\n\n\n\n<p>Unsere Microservices sind vollst\u00e4ndig separate, vom Riddle Core abgetrennte Symfony Umgebungen, die mindestens \u00fcber einen zentralen Message Controller zur Kommunikation und \u00fcber das <a href=\"https:\/\/github.com\/riddle-com\/WebsocketProtocolBundle\">RiddleWebsocketProtocolBundle <\/a>verf\u00fcgen. Dar\u00fcber hinaus ist die Gestaltung der Microservices nicht festgelegt und kann sich von Fall zu Fall unterscheiden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Protokoll<\/h2>\n\n\n\n<p>Die Kommunikation zwischen den  Komponenten WebSocket, Riddle Core und Microservices erfolgt \u00fcber das <a href=\"https:\/\/github.com\/riddle-com\/WebsocketProtocolBundle\">RiddleWebsocketProtocolBundle.<\/a> Dabei handelt es sich um deliminierte und newline-terminierte (\\n) Nachrichten, deren Komponenten jeweils mit &#8218;=&#8216; getrennte Key-Value-Paare sind. Wir verwenden zwei verschiedene Nachrichtentypen, die sich durch ihren Anwendungszweck und ihr Trennzeichen unterscheiden:<\/p>\n\n\n\n<p><strong>Mathilde Message<\/strong> (Trennzeichen Tilde (~)): Nachrichten, die \u00fcber den Riddle Core an einen Microservice weitergeleitet werden sollen und Antworten auf diese Nachrichten.<\/p>\n\n\n\n<p><strong>Hashimoto Message<\/strong> (Trennzeichen Hash (#)): Nachrichten von und zu Microservices<\/p>\n\n\n\n<p>Ganz einfache, g\u00fcltige Nachrichten k\u00f6nnen demnach wie folgt aussehen:<br><strong>Hashimoto<\/strong>: key=value#key2=value2\\n<br><strong>Mathilde<\/strong>: key=value~key2=value2\\n<br><br>Die Besonderheit der Mathilde Message liegt darin, dass sie eine Hashimoto Message zur Weiterleitung an einen Microservice enthalten kann. Diese Nachricht hat immer den  Key &#8218;fwd&#8216;, der Ziel-Microservice wird durch eine Zahl mit dem Key &#8217;scope&#8216; bestimmt.<br><br><strong>Beispiel<\/strong>: key=value~scope=1~fwd=key=value#key2=value2~key2=value2\\n<br>Diese Mathilde Message enth\u00e4lt folgende Komponenten:<br><strong>key<\/strong>: value<br><strong>scope<\/strong>: 1<br><strong>fwd<\/strong>: key=value#key2=value2<br><strong>key2<\/strong>: value2<br><br>Wobei die in &#8218;fwd&#8216; enthaltene Nachricht eine Hashimoto Message darstellt, die wiederum folgende Komponenten enth\u00e4lt:<br><strong>key<\/strong>: value<br><strong>key2<\/strong>: value2<br><br>Damit ein im Value-Teil enthaltenes Trennzeichen nicht zum fehlerhaften Parsen der Nachricht f\u00fchrt, werden die Value-Teile immer URL-encoded.<\/p>\n\n\n\n<p>Der Code des Protocol-Bundles \u00fcbernimmt f\u00fcr uns diese ganzen Arbeiten. Um delimitieren, terminieren, URL-encoden\/decoden und parsing braucht man sich beim Nutzen des Protokolls nicht zu k\u00fcmmern. Das ganze ist wie folgt umgesetzt:<\/p>\n\n\n\n<p>Jede Nachricht erbt von der Basis-Klasse &#8222;Message&#8220;. Diese nimmt eine Nachricht, einen Delimiter und einen Terminierer entgegen, wobei der Terminierer in unserem Fall immer &#8222;\\n&#8220; ist, und der Delimiter &#8222;~&#8220; bzw. &#8222;#&#8220;. Beim Erstellen eines Objekts wird automatisch der Terminierer angeh\u00e4ngt (falls nicht vorhanden) und die Nachricht wird mit dem Delimiter geteilt. Die einzelnen Teile werden in die Property &#8222;parts&#8220; geschrieben, wobei der Terminierer weggelassen wird. Dann wird die Funktion &#8222;parse&#8220; aufgerufen, die von der Child-Klasse implementiert werden muss. Das ist in unserem Fall MathildeMessage und HashimotoMessage. Dort wird zus\u00e4tzlich jeweils eine Data-Klasse eingef\u00fchrt, in der die Key-Value Paare der Nachricht auf Properties gemappt werden. Die Basis-Klasse &#8222;DataMapping&#8220; stellt dazu die Funktion &#8222;map&#8220; zur Verf\u00fcgung, sowie die Funktion &#8222;buildDelimitedMessage&#8220; um aus den Daten wieder eine Nachricht zu Erstellen sowie &#8222;toArray&#8220; um die Daten in Array-Struktur zu \u00fcberf\u00fchren. Die Funktion &#8222;map&#8220; arbeitet mit Setter-Methoden. Aus dem Key des Key-Value-Paars wird der Setter-Methodenname in Camel case abgeleitet ( &#8218;testProp&#8216; =&gt; &#8217;setTestProp&#8216;). Ist der Setter nicht vorhanden, schl\u00e4gt der komplette Vorgang fehl. Ist ein Key in einer Nachricht vorhanden, dann muss die Data-Klasse also auch die passende Property und Setter-Methode enthalten. Mittels Type-Hinting in der Setter-Methode kann auch ein spezieller Datentyp vorausgesetzt werden.<br>Die Basis-Klasse &#8222;DataMapping&#8220; enth\u00e4lt bereits einige Properties, die in jeder Nachricht vorhanden sein k\u00f6nnen:<br><br><strong>id<\/strong>: Reservierte Property, mit der ein ID-System realisiert wird, um ansynchrone Antworten auf Nachrichten zu erm\u00f6glichen<br><strong>messageType<\/strong>: 1 f\u00fcr Request, 2 f\u00fcr Response<br><strong>commandId<\/strong>: Die Befehls-ID. Der Bereich 1-99 ist f\u00fcr externe Befehle reserviert, d.h. Befehle die vom Nutzer \u00fcber das Frontend gesendet werden k\u00f6nnen. Der Bereich &gt;100 ist f\u00fcr interne Befehle reserviert<br><strong>success<\/strong>: Ob ein Befehl erfolgreich ausgef\u00fchrt wurde oder nicht<br><strong>reason<\/strong>: Der Grund warum ein Befehl nicht erfolgreich ausgef\u00fchrt wurde<br><br>Die parse-Funktion erstellt also ein Objekt der Data-Klasse die in der Child-Klasse festgelegt ist und ruft die map-Funktion auf. Schl\u00e4gt diese fehl, wird die Nachricht ung\u00fcltig, was in der Property &#8222;valid&#8220; reflektiert wird. War das Mapping erfolgreich, wird die Methode &#8222;validate&#8220; aufgerufen, die von der Child-Klasse implementiert werden kann. In dieser Methode k\u00f6nnen die Daten auf inhaltliche Korrektheit gepr\u00fcft werden. Dabei kann u.a. die Methode &#8222;hasProperties&#8220; der Data-Klasse verwendet werden, um zu pr\u00fcfen ob gewisse Properties mit einem Wert belegt wurden. Gibt die validate-Methode false zur\u00fcck, wird die Nachricht ebenfalls ung\u00fcltig.<br><br>Eine Besonderheit gibt es bei der Mathilde Message. Da diese wie bereits beschrieben eine Hashimoto Message enthalten kann, wird dies in den Methoden speziell ber\u00fccksichtigt:<br>Wird eine Mathilde Message aus einem Message-String erzeugt, wird nach abgeschlossenem Mapping automatisch ein Hashimoto Message-Objekt daraus erzeugt und in &#8222;fwd&#8220; gespeichert, womit der String \u00fcberschrieben wird. Wird eine Mathilde Message aus Daten-Komponenten erzeugt, kann &#8222;fwd&#8220; wahlweise einen String, ein Daten-Array, ein Objekt der Datenklasse der Hashimoto-Message oder ein Objekt der Hashimoto-Message enthalten. Egal in welchem Format, nach Abschluss des Parsings wird aus den enthaltenen Daten ein Hashimoto Message-Objekt erstellt und in &#8222;fwd&#8220; geschrieben. Diese Typ-Unabh\u00e4ngigkeit ist in der Methode &#8222;build&#8220; implementiert, die in HashimotoMessage und MathildeMessage enthalten ist. Diese Methode kann auch vom Nutzer verwendet werden, um Nachrichten aus einem der vier genannten Datentypen zu erzeugen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Neue Protokoll-Klassen erstellen<\/h2>\n\n\n\n<p>Beim Erstellen eines neuen Microservices, sollte auch eine neue Protokoll-Klasse erstellt werden. Hierzu sind folgende Schritte notwendig:<br><br>1. Message-Klasse erstellen (Hashimoto + &lt;MicroserviceSlug&gt; + Message, bspw. &#8222;HashimotoMailerMessage&#8220;)<\/p>\n\n\n\n<p>2. Data-Klasse erstellen (&lt;MicroserviceSlug&gt; + Data, bspw. &#8222;MailerData&#8220;)<\/p>\n\n\n\n<p>3.  In der Data-Klasse alle Properties (protected) und Setter-Getter-Methoden (public) anlegen<\/p>\n\n\n\n<p>4. Data-Klasse in der Message-Klasse angeben, z.B.<br><em>public&nbsp;static&nbsp;$DATA_CLASS&nbsp;=&nbsp;&#8222;Riddle\\WebSocketProtocol\\Data\\MailerData&#8220;<\/em><\/p>\n\n\n\n<p>5. In der Klasse &#8222;Scope&#8220; eine neue Scope-ID vergeben und samt Message-Klassenname eintragen<br><br>6. Falls gew\u00fcnscht in validate-Methode auf inhaltliche Korrektheit pr\u00fcfen<br><br>7. Falls gew\u00fcnscht Factory-Methoden zum Erstellen von Message-Objekten aus Daten erstellen, z.B.<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public static function buildSmtpRequestMessage(int $smtpId) :HashimotoMessage\n{\n    $data = new Data\\MailerData();\n    $data->setMessageType(1);\n    $data->setCommandId(110);\n    $data->setSmtpId($smtpId);\n\n    return HashimotoMailerMessage::build($data, true);\n}<\/code><\/pre>\n\n\n\n<p>Anschlie\u00dfend das Protokoll im Core und allen Microservices updaten. Im Core au\u00dferdem die passende Message-Handler Klasse anlegen (s.u.)<\/p>\n\n\n\n<p><br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Schnittstelle Riddle Core &lt;-&gt; Microservices<\/h2>\n\n\n\n<p>Die Kommunikation zwischen dem Riddle Core und den Microservices findet \u00fcber HTTP POST statt. Der Body wird als json gesendet. Folgende Daten sind enthalten:<\/p>\n\n\n\n<p><strong>success<\/strong>: Gibt den Erfolg der Nachrichten\u00fcbermittlung an, bezieht sich nicht auf die erfolgreiche Ausf\u00fchrung des Nachrichteninhalts<br><strong>code<\/strong>: HTTP response code<br><strong>reason<\/strong>: Grund warum die Nachrichten\u00fcbermittlung fehlgeschlagen ist<br><strong>data<\/strong>: Daten der Nachricht, die mittels toArray-Methode der DataMapping-Klasse in Array-Struktur umgewandelt wurden<br><br>Zum Senden von Nachrichten an Microservices wird Gateway-Service im Microservice-Bundle verwendet. Die send-Methode des Service nimmt eine Scope (Ziel-Microservice) und eine HashimotoMessage entgegen.<br>Der Microservice empf\u00e4ngt die Nachricht mit dem notwendigen Message-Controller (Endpunkt \/message).<br><br>Die Microservices k\u00f6nnen ebenfalls selbst initiierte Nachrichten an den Core senden. Dazu gibt es einen Core-Gateway-Service, der \u00fcber die Methoden send und forward verf\u00fcgt. Mit send werden Nachrichten direkt an den Core gesendet, mit forward werden Nachrichten \u00fcber den Core an einen anderen Microservice weitergeleitet. Diese Nachrichten werden vom Core im Message-Controller des Microservice-Bundle empfangen (Endpunkt \/internal\/mathilde und \/internal\/hashimoto). <\/p>\n\n\n\n<p>In den HTTP-Requests k\u00f6nnen drei besondere Header vorkommen:<br><strong>X-Origin-Env<\/strong>: Wird vom Riddle Core bei Requests an einen Microservice gesetzt, damit der Microservice wei\u00df von welcher Environment die Request kommt. Das ist wichtig, da wir nur einen Microservice f\u00fcr DEV und LIVE haben, es aber f\u00fcr DEV etliche verschiedene Environments \/ VHosts gibt.<br><strong>X-Origin-Scope<\/strong>: Wird von Microservices bei Requests an den Core gesetzt, damit der Core wei\u00df von welchem Microservice die Nachricht kommt. Somit kann der Core die zu verwendende Message-Klasse bestimmen und aus der Nachricht ein Hashimoto Message-Objekt erzeugen.<br><strong>X-Forwarded-From<\/strong>: Erh\u00e4lt der Core von einem Microservice eine weiterzuleitende Nachricht, wird bei der Request an den Target-Microservice der Header mit der Scope des sendenden Microservice belegt. Der empfangende Microservice kann also \u00fcberpr\u00fcfen, von welchem Microservice die Nachricht stammt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Message Handler<\/h2>\n\n\n\n<p>Um eine einfache Erweiterbarkeit und Wartbarkeit im Riddle Core herzustellen, ist es nicht vorgesehen dass \u00c4nderungen am Message-Controller oder Gateway-Service vorgenommen werden m\u00fcssen. Stattdessen wird f\u00fcr jede Message-Klasse eine Handler-Klasse im Core erstellt (Message-Klasse + &#8222;Handler&#8220;, z.B. HashimotoMailerMessageHandler). Diese muss folgende Methoden des BaseHandlers implementieren:<br><br><strong>handleEmbedForwardMessage(MathildeCoreMessage&nbsp;$message,&nbsp;bool&nbsp;$fromFallback<\/strong>):<br>Wird aufgerufen wenn im WebSocket bzw. im HTTP-Fallback eine externe Nachricht empfangen wird. False zur\u00fcckgeben, falls Nachricht nicht weitergeleitet werden soll, oder die weiterzuleitende Nachricht. Hier k\u00f6nnen also Bedingungen gepr\u00fcft werden und die Nachricht kann vor der Weiterleitung erweitert bzw. manipuliert werden.<br><br><strong>handleInternalForwardMessage(MathildeCoreMessage&nbsp;$message)<\/strong>:<br>Wie oben, nur handelt es sich hierbei um eine interne Nachricht, die von einem Microservice empfangen wurde<br><br><strong>handleInternalMessage(HashimotoMessage&nbsp;$message)<\/strong>:<br>Wird aufgerufen wenn eine Nachricht von einem Microservice empfangen wird, die direkt an den Riddle Core gerichtet ist. Die Nachricht muss dann in dieser Funktion verarbeitet werden. Es wird eine Antwort in Form einer HashimotoMessage als R\u00fcckgabe erwartet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">WebSocket<\/h2>\n\n\n\n<p>Der WebSocket ist mit Ratchet PHP gebaut, eine event-driven Websocket Library auf Basis von React PHP. Der Hauptcode zur Ausf\u00fchrung des WebSocket liegt in bin\/websocket im Riddle Core-Repo. Das Script erwartet als Parameter den ausf\u00fchrenden vhost. Durch ein Lock-File wird sichergestellt, dass der WebSocket nur einmal l\u00e4uft. Mit einem Interrupt-File kann die Ausf\u00fchrung des WebSocket jederzeit unterbrochen werden. Die Files befindet sich im var\/flags Verzeichnis und sind \u00fcber Riddle-Monika steuerbar.<br><br>Je nach vhost verwendet der WebSocket einen unterschiedlichen Port. Die Ports sind von au\u00dfen nicht erreichbar, durch einen Reverse-Proxy wird aber der Endpunkt \/ws\/connect\/1 auf den Port des Core-WebSocket umgeleitet.<br><br>Der WebSocket verwendet das MessageInterface &#8222;CoreWebSocket&#8220; im WebsocketBundle des Riddle Core. Dort werden eingehende Nachrichten geh\u00e4ndelt und ggf. \u00fcber den GatewayService des MicroserviceBundle an den Microservice weitergeleitet.<br><br>F\u00fcr Clients die keine Unterst\u00fctzung f\u00fcr WebSockets haben, existiert im WebsocketBundle ein FallbackController. Dieser nimmt \u00fcber HTTP exakt die selben Daten entgegen wie der WebSocket.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Grundlagen Unsere Microservices sind vollst\u00e4ndig separate, vom Riddle Core abgetrennte Symfony Umgebungen, die mindestens \u00fcber einen zentralen Message Controller zur Kommunikation und \u00fcber das RiddleWebsocketProtocolBundle&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/intern.quizu.de\/?p=139\">Weiterlesen<span class=\"screen-reader-text\">Microservice Architektur &#038; Protokoll<\/span><\/a><\/div>\n","protected":false},"author":6,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-139","post","type-post","status-publish","format-standard","hentry","category-development","entry"],"_links":{"self":[{"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/posts\/139","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=139"}],"version-history":[{"count":6,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/posts\/139\/revisions"}],"predecessor-version":[{"id":145,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=\/wp\/v2\/posts\/139\/revisions\/145"}],"wp:attachment":[{"href":"https:\/\/intern.quizu.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=139"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=139"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intern.quizu.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=139"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}