Tutorial “Enterprise Service Bus mit Mule ESB”: Transport, Connector, Endpoint: Ein paar Grundlagen…

Keine Kommentare

Von der allgemeinen „warum überhaupt ESB Einführung“ abgesehen, hatten die bisherigen Teile des Tutorials Beispiel-Charakter, die Grundlagen sind etwas auf der Strecke geblieben. Es gibt also etwas nachzuholen…

Wie praktisch jede andere größere Software hat Mule seine eigene Begriffswelt: Da gibt es Transports, Connectors, Endpoints und so weiter. Zumindest lassen die meisten Begriffe erahnen, was sich dahinter verbirgt. Wer ernsthaft mit Mule arbeitet, sollte jedoch etwas genauer wissen, wie Mule intern funktioniert.
Aber keine Angst: Es bleibt nicht bei reiner Theorie, ich werde wieder Beispiele mit nützlichen Konfigurationsparametern zeigen.

Endpoint

Womit soll’s losgehen? Am besten mit einem Inbound Endpoint, damit startet ein Mule Flow gleich in doppelter Hinsicht:

  1. Um einen neuen Flow zu definieren, zieht man im Mule-Studio aus der Palette am rechten Rand einen Endpoint in den Editor und lässt ihn dort fallen. Vom Studio wird damit automatisch sowohl der Endpoint als auch ein umgebender Flow angelegt. (Wer’s mag, kann natürlich auch erst einen leeren Flow im Editor anlegen…)
  2. Zur Laufzeit startet ein Flow immer an einem Endpoint: Dort wird – ausgelöst von einem externen Ereignis – der Endpoint aktiv und generiert eine MuleMessage, die anschließend den Flow durchläuft.

Das ist aber nur die eine Seite der Medaille: Der Inbound Endpoint. Zieht man aus der Palette einen Endpoint und lässt ihn innerhalb eines Flows fallen (der schon einen Inbound-Endpoint enthält), erzeugt man einen Outbound Endpoint. Während jeder Flow genau einen Inbound Endpoint enthält, kann er beliebig viele (auch keine) Outbound Endpoints enthalten.

„In“ und „Out“ beziehen sich dabei nur auf die Richtung der Signalisierung (Events), der Datenfluss kann durchaus in Gegenrichtung verlaufen: So kann ein Outbound http Endpoint dazu dienen, Daten von einem Webserver abzuholen. Der Flow muss dafür natürlich irgendwie gestartet werden (z.B. durch einen Quartz-Endpoint). Man kann aber auch viele Outbound Endpoints  als Start eines Flows verwenden, wenn man sie in einen Poll Scope schachtelt. Das war jedoch jetzt etwas vorweg gegriffen, dazu später mehr…

Neben „In“ und „Out“ existiert besitzen Endpoints noch eine weitere wichtige Eigenschaft, das Exchange Pattern:

  • Request Response: Entspricht einem (entfernten) Funktionsaufruf oder dem Austausch von zwei Nachrichten (Request und Response). Es ist typisch für einige Endpoints (z.B. http). Wird es beim Inbound Endpoint eingesetzt, beeinflusst es den gesamten Flow: Er endet nicht „irgendwo“, sondern läuft zum Endpoint zurück, da von dort aus die Antwort an den Aufrufer gesendet wird.
    Dieses Pattern wird von einigen Endpoints (z.B. File Endpoint) nicht unterstützt.
  • One Way: Könnte man auch als „Fire and Forget“ bezeichnen, ein Nachrichtenaustausch findet nur in einer Richtung statt. Dieses Pattern wird auch von einigen Endpoints unterstützt, bei denen man es überhaupt nicht erwartet: Ein http Inbound Endpoint kann One Way konfiguriert werden. Der Client bekommt dann sofort ein „OK“ geliefert, während parallel der Flow abläuft.
    Ist ein Outbound Endpoint mit Pattern One Way konfiguriert, läuft er ebenso parallel zum restlichen Flow ab: Ein (langsamer) FTP-Transfer einer großen Datei blockiert so nicht den restlichen Flow.
    One Way ist somit potentiell schneller als Request Response, der Absender bekommt aber auch keine direkte Rückmeldung über mögliche Fehler.

Ein Endpoint kann damit in vier verschiedenen Kombinationen dieser Eigenschaften daher kommen: In / Out kombiniert mit Request Response / One Way. Oft kommen noch weitere Varianten dazu (z.B. https statt einfachem http). Mule fasst diese Varianten in einem sogenannten Transport zusammen. Schaut man im Mule Studio ein Projekt im Package Explorer an und klappt „Mule Runtime [Mule Server 3.4.0 EE]“ auf, so sieht man für jeden Transport ein eigenes jar, für http das mule-transport-http-3.4.0.jar“.

Wenn man einen eigenen Transport entwickelt (dazu gibt es sicher in Zukunft einen eigenen Blog-Artikel), so wird daraus per Maven letztendlich auch ein solches jar. Mule liefert übrigens ein eigenes Maven Archetype mit, so dass einem beim Entwickeln eines Transports das Gerüst automatisch erstellt wird.

Hinter dem Begriff Transport verbirgt sich also nicht viel mehr als ein Container, der alle für Mule notwendigen Dateien (Klassen und Konfiguration)  bündelt.

Einfaches Beispiel

Ich habe Beispiele versprochen, also fangen wir mit einem einfachen an und bauen einen „Hello, World!“ Flow, der bei einem Aufruf aus dem Browser heraus eine freundliche Begrüßung liefert. Keine Angst: Das Beispiel wird später ausgebaut. Los geht’s: Im Mule Studio per Wizard ein neues Projekt erzeugen (wie es geht wiederhole ich hier nicht) und einen http Endpoint in den Editor ziehen. Nach einem Doppelklick wird er konfiguriert:

Konfiguration eines einfachen http Inbound Endpoints.

Möchte man es schön machen, kann man im Reiter „Advanced“ im Feld „MIME Type“ noch „text/plain“ eingeben, da unser Flow kein Html-Dokument zurückgibt, sondern nur einen einfachen Text. Wird das Mule Projekt ausgeführt, erhalten wir einen http Server, der auf Port 8080 horcht. Alle Anfragen, deren Pfad mit „hello“ beginnt, leitet Mule in unseren Flow. Andere Anfragen werden mit einer Fehlermeldung beantwortet, zum Beispiel bei Pfad „abc“ so:
Cannot bind to address "http://127.0.0.1:8080/abc" No component registered on that endpoint
Um eine nette Begrüßung zu bekommen, ziehen wir noch einen Expression Transformer in den Flow und konfigurieren ihn:

Ein Expression Transformer, der "Hello, World!" in die Mule Message schreibt.

 Der Flow sollte anschließend folgendermaßen aussehen:

Einfacher Flow mit einem http Inbound Endpoint.

 Alternativ lässt sich der Flow natürlich auch direkt in XML konfigurieren:

<flow name="HelloFlow" doc:name="HelloFlow">
    <http:inbound-endpoint exchange-pattern="request-response" 
                           host="localhost"
                           port="8080"
                           path="hello" 
                           doc:name="hello"
                           mimeType="text/plain"/>
    <expression-transformer expression="#['Hello, World!']" 
                            doc:name="Hello, World!" />
</flow>

Tippen wir jetzt im Browser http://localhost:8080/hello ein, sollte dort ein freundliches „Hello, World!“ erscheinen.

Was haben wir bis jetzt alles konfiguriert? Nicht viel: Einen Flow, einen Endpoint (aus dem http Transport). Machen wir die Sache etwas interessanter und konfigurieren einen zweiten Flow:

Mule Debug Flow

Oder in XML-Darstellung:

<flow name="DebugFlow" doc:name="DebugFlow">
    <http:inbound-endpoint exchange-pattern="request-response" 
                           host="localhost"
                           port="8080"
                           path="debug"
                           doc:name="debug"
                           mimeType="text/plain"/>
    <http:body-to-parameter-map-transformer doc:name="Body to Parameter Map" />
    <component class="de.codecentric.basics.DebugComponent"
               doc:name="DebugComponent" />
</flow>

Der Flow beginnt – ebenso wie der letzte – mit einem http Inbound Endpoint. Unterschiedlich sind nur der Name und der Pfad, hier „debug“ statt „hello“. Direkt hinter den Endpoint wird ein „body-to-parameter-map-transformer“ eingefügt. Er sorgt dafür, dass aus dem http-Request URL-Parameter oder Form-Parameter ausgelesen und in einer Java-Map abgelegt werden. Es folgt eine Java-Komponente, die aus der Map einen lesbaren String erzeugt:

public String debug(Map<String, String> parameters) {
    StringBuilder sb = new StringBuilder();
    sb.append("Parameters:\n");
    for (Map.Entry<String, String> entry : parameters.entrySet()) {
        sb.append(entry.getKey()).append('=').append(entry.getValue()).append('\n');
    }
    return sb.toString();
}

Ein Aufruf von http://localhost:8080/debug?key=value&hello=wo%3Drld zeigt, dass Mule auch mit URL-Encoding umgehen kann (aus %3D wird =), im Browser sollte erscheinen:

Parameters:
hello=wo=rld
key=value

Wir haben mit diesem Flow also einen zweiten Webserver erzeugt, der auch auf Port 8080 horcht. Ein Klick auf http://localhost:8080/hello bestätigt, dass der erste immer noch existiert. Moment. Wer schon mal mit Sockets gearbeitet oder einen Tomcat konfiguriert hat sollte merken, dass hier was nicht stimmen kann: Man kann keine zwei Web-Server auf einem Rechner auf dem gleichen Port horchen lassen (zumindest nicht auf dem gleichen Netzwerkinterface). Was geht hier also vor?

Http-Transport

Die zwei Endpoints können nicht jeder auf Port 8080 einen eigenen Socket öffnen und auf ihre Verbindungen warten, sie müssen sich irgendwie den Socket teilen, der Verbindungen entgegennimmt. Eine Komponente muss dann Header anschauen und anschließend auf Basis der URL die Requests entsprechend verteilen. Aber wie funktioniert das konkret? Glücklicherweise ist Mule Open Source, so können wir uns den Code anschauen und im Debugger verfolgen. Noch besser: Im Mule Studio sind nicht nur die Jars mit den Class-Dateien enthalten, sie sind auch direkt mit den zugehörigen Sourcen verbunden, so dass man ohne längere Konfigurationsorgien direkt Quelltexte vor Augen hat.

Einige Dinge sieht man aber auch schon ohne Debugger und Quellen direkt im Mule Studio: Am unteren Rand des Flow-Editor befinden sich drei Reiter:

  • Message Flow: Grafische Darstellung des Flows, damit fängt man meistens an.
  • Configuration XML: XML-Quelltext des Flows, für Experten oder faule „aus dem Blog in den Editor Kopierer“ 🙂
  • Global Elements: Einstellungen für alle Elemente, die sich außerhalb von Flows befinden.

Im zuletzt genannten Reiter kann nach einen Klick auf „Create“ ein HTTP\HTTPS-Connector angelegt werden. (Auch für andere Transports existieren Connector-Elemente.) In einem Connector lassen sich Einstellungen vornehmen, die für mehrere Endpoints eines Transports gelten. Folgendermaßen könnte ein Beispiel für die Einstellungen eines http-Connectors aussehen:

<http:connector name="HTTP"
                cookieSpec="netscape"
                validateConnections="true"
                sendBufferSize="100"
                receiveBufferSize="100"
                receiveBacklog="10"
                clientSoTimeout="10000"
                serverSoTimeout="10000"
                socketSoLinger="1000"
                enableCookies="false"
                sendTcpNoDelay="true"
                doc:name="HTTP\HTTPS">
</http:connector>

Ich werde hier nicht alle Einstellungen durchgehen (sie sind auch nicht vollständig, so fehlen die Angaben für einen http-Proxy), sie werden sowohl über Dokumentation der XSD als auch in den Konfigurationsdialogen über kleine i-Icons beschrieben.

Was nicht im Connector referenziert wird ist der Port. Das liegt daran, das ein Connector durchaus für mehrere verschiedene Ports zuständig sein kann. Es muss also noch etwas zwischen Connector und Endpoint existieren, was sich mehrere Endpoints mit einem gemeinsamen Port teilen. Um dieses „etwas“ zu finden, muss man nun doch in die Quelltexte eintauchen, dort findet man (unter anderem) folgende Klassen:

  1. HttpConnector: Der Connector, ist – wie schon beschrieben – so auch im XML sichtbar. Die konfigurierbaren Attribute befinden sich größtenteils in der Ableitungshierarchie, unter anderem eine Map mit der URL als Schlüssel und Instanzen von HttpMessageReceiver als Wert.
  2. HttpMessageReceiver: Die Klasse macht beinahe nichts selbst, sie delegiert während der Initialisierung an weitere Klassen. Sie enthält eine Referenz auf den Endpoint, von ihr existiert daher für jeden Flow (und damit jeden Inbound Endpoint) eine eigene Instanz.
    In der Methode processRequest(…) werden die http-Requests verarbeitet.
  3. HttpRequestDispatcher: Hier befindet sich der Kern des http-Servers, inklusive des ServerSocket, der Verbindungen entgegennimmt. Mehrere Flows (und damit Inbound Endpoints) teilen sich einen Dispatcher, wenn sie auf dem gleichen Port arbeiten. Der Dispatcher verteilt die Requests auf Basis des Pfads an die Inbound Endpoints beziehungsweise die zugehörigen Flows.
  4. DefaultInboundEndpoint: Der Endpoint ist – wie der Connector – direkt im XML sichtbar. Er spielt zur Laufzeit keine Rolle mehr, hält aber die Konfigurationsdaten.

Von den vier Klassen sind nur zwei im XML sichtbar, von den beiden anderen werden Instanzen im Hintergrund angelegt. Die Struktur sieht in anderen Transports ähnlich aus.

Der http-Connector wird immer dann angelegt, wenn im Projekt mindestens ein http-Endpoint vorhanden ist. Was passiert nun, wenn es in einem Projekt mehr als einen http-Connector gibt? Probieren wir es einfach mal aus, verdoppeln den XML-Abschnitt und ändern nur den Namen. Zur Belohnung gibt es einen netten Stacktrace:

There are at least 2 connectors matching protocol "http", so the connector to use must be specified on the endpoint using the 'connector' property/attribute. Connectors in your configuration that support "http" are: HTTP, HTTP2

Die Meldung sagt auch schon (fast) was man ändern muss: In den Endpoints fehlt ein Attribut. Ergänzt man in beiden Endpoins connector-ref="HTTP",so  funktioniert wieder alles wie vorher: Beide Endpoints werden beim ersten http-Connector eingetragen, der zweite bleibt ungenutzt.

Wo wir gerade beim Experimentieren sind: Was passiert, wenn wir die beiden Endpoints (mit gleichem Port) auf die beiden http-Connectoren verteilen? Das dürfte nicht funktionieren, da sich die beiden dann keinen HttpRequestDispatcher mehr teilen können. Es funktioniert auch tatsächlich nicht, allerdings erscheint als Fehlermeldung nur eine unauffällige NullPointerException (ohne Stacktrace) im Log.

http-Polling

Ich hatte oben versprochen, auch noch http-Polling zu zeigen, also los, bauen wir einen Flow, der alle zehn Sekunden die Startseite von Google aufruft und als Html-Quelltext ins Log schreibt. (Ein besseres Beispiel ist mir nicht aufgefallen, eine Seite mit Börsenkursen wäre wohl nützlicher…) Im grafischen Editor ziehen wir dafür aus der Palette einen Scope mit Bezeichnung „Poll“ auf die Editor-Fläche und lassen ihn fallen. Nach einem Doppelklick kann man darin die Zeit zwischen zwei Abfragen (in Millisekunden) eintragen (der Begriff „Frequency“ für die Wartezeit ist nicht glücklich gewählt).

Um das Ergebnis im Log zu anzuzeigen, fügen wir eine Logger-Komponente ein, die per Mule-Expression-Language auf die Payload der Message zugreift.  Startet man das Projekt, sieht man nicht das erwartete Ergebnis: Statt des Html-Codes der Google-Startseite wird nur ein org.mule.transport.http.ReleasingInputStream angezeigt. Hier zeigt sich eine Optimierung von Mule: Der Inhalt der Seite wird nicht sofort in den Speicher eingelesen – was bei großen Seiten/Dateien ein Out of Memory zur Folge haben könnte – sondern es wird nur ein InputStream weitergereicht. Um ihn zu „materialisieren“ kann man die Echo-Component verwenden, damit erhält man den folgenden Flow:

Flow für http-Polling

Oder in XML-Darstellung:

<flow name="PollingFlow" doc:name="PollingFlow">
    <poll frequency="10000" doc:name="Poll">
        <http:outbound-endpoint exchange-pattern="request-response"
                                host="www.google.de"
                                port="80"
                                method="GET"
                                doc:name="Google" />
    </poll>
    <echo-component doc:name="Echo" />
    <logger message="#[message.payload]" level="INFO" doc:name="Logger" />
</flow>

Lässt man diesen laufen, erscheint alle zehn Sekunden der gewünschte Html-Code im Log.

Schaut man sich den Flow genauer an, fällt noch ein Detail auf: Der Flow gehorcht dem Exchange-Pattern „One Way“ (ansonsten würde vom Logger eine Linie zurück zum Poll-Scope bzw. http-Endpoint laufen). Der http-Endpoint hat jedoch zwei Pfeile oben rechts in der Ecke, das heißt sein Exchange-Pattern ist Request-Response. Warum? Er wird vom Poll-Scope aktiviert (Request) und sein Ergebnis (Response) liefert die initiale Nachricht für den Flow. Der Flow insgesamt kann nur One-Way laufen, wohin sollte bei dem Poll-Scope ansonsten auch ein Ergebnis geleitet werden?

Zusammenfassung/Ausblick

Was haben wir nun gelernt? Hinter dem eher abstrakten Begriff „Transport“ verbirgt sich in Mule konkret  ein jar-Archiv, das alle Dateien des Transports zusammenfasst. Viele Parameter lassen sich direkt am Inbound- oder Outbound-Endpoint konfigurieren, der zugehörige Connector wird in dem Fall von Mule automatisch zusammen mit dem Endpoint angelegt und mit Default-Parametern versehen. Will man die Parameter des Connectors beeinflussen, muss man explizit einen Connector konfigurieren, der dann automatisch von allen Endpoints des zugehörigen Transports genutzt wird. Mehrere Connectoren für einen Transport sind möglich, in dem Fall muss jedoch in jedem Endpoint per connector-ref einer ausgewählt werden.

Was kommt im nächsten Teil? Ein Baustelle habe ich schon erwähnt: Den Bau eines eigenen Transports. Dabei ist es schwierig, ein gutes Beispiel zu finden, da Mule für viele Fälle schon fertige Transports mitliefert.

Vorher kommt zum Aufwärmen auf jeden Fall noch ein anderes Thema: Threads und Threadpools in Mule. Damit sollte man sich etwas auskennen, um Ressourcen richtig zuzuweisen und um die Performance seiner Mule-Applikationen zu optimieren.

Weitere Teile dieser Artikelserie

  1. Was ist ein ESB und wofür kann man ihn nutzen?
  2. Tutorial “Enterprise Service Bus mit Mule ESB”: Hello World/Bus
  3. Tutorial “Enterprise Service Bus mit Mule ESB”: MuleMessage und Java-Komponenten
  4. Tutorial „Enterprise Service Bus mit Mule ESB“: Nachrichten mit Java transformieren
  5. Tutorial „Enterprise Service Bus mit Mule ESB“: Integration von CICS Programmen
  6. Tutorial “Enterprise Service Bus mit Mule ESB”: Transport, Connector, Endpoint: Ein paar Grundlagen…
  7. Tutorial “Enterprise Service Bus mit Mule ESB”: Performance und Threads
  8. Tutorial “Enterprise Service Bus mit Mule ESB”: Steuerung und Kontrolle per JMX
  9. Veröffentlichen von Informationen zu Mule Applikationen im Maven-Umfeld
  10. Tutorial “Enterprise Service Bus mit Mule ESB”: Exceptions und Email
Roger Butenuth

Dr. Roger Butenuth hat in Karlsruhe Informatik studiert und anschließend in Paderborn promoviert (Kommunikation in Parallelrechnern). Er hat langjährige Erfahrung in der Projekt- und Produktentwicklung.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

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