Vier Kommunikationsframeworks für serviceorientierte Architekturen im direkten Vergleich

Kommunikations-Contest
Kommentare

Mobile Endgeräte jeder Größe und Ausbaustufe sind allgegenwärtig. Sie bedingen Veränderungen in der Architektur hin zu echten verteilten Anwendungen mit Client-Server-Architektur. Die Folge: Effiziente, zuverlässige, einfach zu programmierende und möglichst sprach- und plattformübergreifende Kommunikationsprotokolle gewinnen eine immer größere Bedeutung Wir testen vier Kandidaten intensiv an einem praktischen Beispiel und erarbeiten nebenbei einen Fragenkatalog für das Design eines praxistauglichen Services.

Wer mit der Entwicklung einer solchen Serviceschnittstelle betraut ist, dem fällt die Auswahl aus den vielen kommerziellen und Open-Source-Möglichkeiten nicht leicht. Dass die Anbieter der diversen Kommunikationsframeworks durch die Bank das Blaue vom Himmel versprechen, macht die Sache nicht unbedingt einfacher. Zwar ist es im Rahmen eines Artikels kaum möglich, hierauf eine umfassende Antwort zu entwickeln. Dennoch werden wir an einem konkreten Beispiel einige wichtige Aspekte bei der Auswahl eines solchen Kommunikationsframeworks herausarbeiten und anhand von vier ausgewählten Frameworks gegenüberstellen. Die hierbei gewonnenen Erkenntnisse und Statistiken lassen sich natürlich nicht direkt auf andere Vertreter der Gattung beziehungsweise auf andere Einsatzfälle übertragen. Trotzdem soll geklärt werden, welche Fragen man stellen sollte und welche Kriterien und Maßstäbe mit Blick auf die konkreten Anforderungen an das Projekt angebracht sind. Außerdem wird erläutert, wie sich die ausgewählten Technologien tendenziell bezüglich Traffic und Performance verhalten. All diese Informationen helfen uns, eigene Projekte auf ein solides Fundament zu stellen und existierende Projekte kritisch zu hinterfragen.

Anwendungsfall

Als Anwendungsfall soll ein Service implementiert werden, der es einem Client erlaubt, über mehrere Aufrufe einen Syntaxbaum für einen beliebig komplexen mathematischen Ausdruck aufzubauen. Man kann sich das etwa so vorstellen wie in den Abbildungen 1 bis 3 dargestellt: Der Client baut den Baum, beginnend bei der Wurzel, sukzessive Node für Node unter Verwendung von literalen Werten, Symbolen und Operatoren, auf. Nachdem der Baum fertig ist, kann der Client über einen weiteren Methodenaufruf namens Calculate() verschiedene Werte für die Variablen angeben, worauf er vom Server das berechnete Ergebnis erhält. Das kann er nun im Prinzip beliebig oft mit den verschiedensten Argumenten tun, und der Server wird klaglos den Baum mit den angegebenen Werten berechnen.

Abb. 1: Architektur der Anwendung

Abb. 2: Eine quadratische Funktion

Abb. 3: Calculation Tree der Funktion aus Abbildung 2

Obgleich die Anwendung grundsätzlich Aufrufe von beliebig vielen Clients entgegennehmen kann, funktioniert der Beispielcode in der aktuellen Umsetzung nur mit einem einzigen Client wirklich korrekt. Um den Code nicht übermäßig kompliziert werden zu lassen, arbeiten in unserem Beispiel nämlich alle Clients auf einem einzigen globalen Syntaxbaum. Sie würden sich bei parallelen Zugriffen also früher oder später gegenseitig in die Quere kommen.

Aus diesen Anforderungen lässt sich ableiten, dass es für die Schnittstelle zwischen Client und Server wünschenswert wäre, die einzelnen Aufrufe soweit zu kapseln, dass für die Serveraufrufe möglichst wenig zusätzlicher Code zu schreiben ist. Im Idealfall würden diese hinter einem Interface gekapselt sein, sodass sich für den Anwendungsprogrammierer ein solcher Serveraufruf zunächst nicht grundlegend von einem normalen Funktionsaufruf unterscheidet. Jeder zusätzliche Aufwand senkt nicht nur die Effizienz bei der Programmierung der Anwendung, sondern bietet auch mehr Möglichkeiten für Fehler. Last but not least würde damit auch der Code der Anwendung unnötig mit Aspekten aufgebläht, die auf diesem Abstraktionslevel überhaupt nichts verloren haben – Separation of Concerns lässt grüßen.

In unserem Fall hängt jeder weitere Schritt vom Ergebnis des vorhergehenden ab, sodass wir auf die asynchrone Verarbeitung verzögert eintrudelnder Serverantworten verzichten und alle Operationen direkt im Haupt-Thread der Anwendung ausführen. In einer realen Anwendung sollte man grundsätzlich alle blockierenden Aktionen, bei denen die Wahrscheinlichkeit signifikanter Latenz besteht, in einen Hintergrund-Thread verlagern, um die Reaktionsfähigkeit der Anwendung sicherzustellen. Das generelle Vorgehen bei der Programmierung ändert sich dadurch jedoch nicht wirklich, sodass dieser Aspekt im Beispielcode zugunsten von Klarheit und Nachvollziehbarkeit ebenfalls keine Berücksichtigung fand.

Aus den oben genannten Anforderungen an die Serviceschnittstelle geht außerdem hervor, dass unser Service-API auf Ebene der Businesslogik operiert. Das ist insofern interessant, als einige der im Angebot befindlichen Frameworks auch auf der Ebene der zugrunde liegenden Daten arbeiten und beispielsweise auch Optionen zur Verfügung stellen, mit denen Datensätze aus Datenbanktabellen abgefragt und manipuliert werden können. Aus Architektur- und Security-Sicht ist der Ansatz, über die Schnittstelle direkt auf physische Tabellendaten zuzugreifen, insbesondere für öffentlich erreichbare Schnittstellen eher fragwürdig: Assoziationen zu SQL-Injektionen drängen sich hier geradezu auf. Dennoch gibt es natürlich Einsatzfälle, wo gerade dieses Verfahren oder der REST-konforme Zugriff auf semantische Ressourcen das Mittel der Wahl ist.

Aus der Masse der in Frage kommenden Kandidaten wurden vier ausgewählt, die erstens die von uns aufgestellten Kriterien erfüllen und zweitens für die Programmierung mit Delphi geeignet sind. Die Auswahl der Technologien deckt dabei den industrieüblichen Bereich ohne Anspruch auf Vollständigkeit ab. Am Start sind mit SOAP und XML-RPC zwei auf XML basierende, standardisierte Technologien. Trotz ihres durchaus respektablen Alters sind beide nach wie vor im Einsatz. Eine der derzeit am meisten angesagten Technologien für Serviceschnittstellen ist der Representational State Transfer – oder kurz: REST –, über den leider recht viele Mythen und Halbwahrheiten im Netz herumgeistern. Für unsere Zwecke müssen wir an dieser Stelle weder genaue Genese noch wissenschaftlich exakte Bedeutung des Begriffes herleiten. Es reicht zunächst völlig aus, wenn wir im Folgenden unter REST einen Architekturstil verstehen, der ein auf wenigen Regeln basierendes Kommunikationsverfahren zwischen einem Service und einem Client beschreibt. Als vierter Kandidat steht das inzwischen in vielerlei Produkten und Diensten für die Kommunikation eingesetzte Apache Thrift an der Startlinie.

Um für die Messungen vergleichbare Ausgangsbedingungen zu schaffen, unterscheiden sich die vier Varianten nur bezüglich der konkreten Implementierung des Service-Interfaces, die sich nach den Gegebenheiten des zugrunde liegenden Frameworks richten muss. Alle anderen Aspekte, insbesondere Aufbau von Server- und Clientapplikationen, wurden soweit möglich völlig identisch implementiert. Der eigentliche Anwendungskern, also die Implementierung des Syntaxbaums, ist in einer von allen Servervarianten gemeinsam verwendeten Unit untergebracht.

[ header = SOAP ]

SOAP

Dank Standardisierung bringt Delphi wie viele andere Entwicklungsumgebungen seit vielen Versionen die eingebaute Möglichkeit mit, SOAP-Services mit Bordmitteln zu erstellen und zu konsumieren. Grundsätzlich ist dies auf zwei Wegen möglich: Für eine Serveranwendung erstellt man typischerweise ein Interface und die benötigten Ein- und Ausgabeparameter manuell im Code und registriert diese bei den entsprechenden Handlern der Delphi-Runtime. Die für eine SOAP-Schnittstelle verwendbaren Datentypen unterliegen dabei einigen Einschränkungen. So sind – neben einfachen Basistypen wie String, Integer und Double sowie Aufzählungstypen – nur von TRemotable abgeleitete Klassen zulässig. Bereits Sets sind in einer SOAP-Schnittstelle nicht mehr zulässig, diese und alle anderen Auflistungen müssen stattdessen mithilfe von Arrays dargestellt werden.

SOAP

Das Simple Object Access Protocol ist ein von Microsoft definiertes und beim W3C registriertes Protokoll zum Datenaustausch zwischen Anwendungen. SOAP schreibt den generellen Aufbau der stets im XML-Format ausgetauschten Daten vor, legt jedoch weder Semantik des Nachrichteninhaltes noch Übertragungswege fest. Eine SOAP-Nachricht besteht immer aus einer Envelope, in die ein Header und ein Body eingebettet sind. Der Header dient zur Aufnahme von Metadaten sowie für protokollspezifische Erweiterungen wie zum Beispiel Authentifizierung. Der Body enthält den eigentlichen Inhalt der Nachricht, also die anwendungsspezifischen Daten. Der Server antwortet dem Client mit einer analog aufgebauten Nachricht, die bei Exceptions statt eines Bodys einen Fault-Block mit Fehlerinformationen enthalten kann. Dienste stellen typischerweise einen Weg bereit, Metainformationen über WSDL-Dateien abzurufen, die wiederum von vielen modernen Entwicklungsumgebungen eingelesen werden können.

Bei der Programmierung eines SOAP-Clients unterscheidet sich das Vorgehen dahingehend, dass man hierbei das vom Service bereitgestellte WSDL-Dokument mit der Definition des Servicekontrakts in die Entwicklungsumgebung importiert. Durch diesen Importvorgang wird von der IDE eine Delphi-Unit erstellt, die neben den entsprechenden Datenklassen auch das Interface und eine Funktion zur Erstellung eines Proxy-Objekts enthält, das das Interface für den Anwendungsprogrammierer zur Verfügung stellt. Dieser Vorgang unterscheidet sich vom Vorgehen in anderen Entwicklungsumgebungen, wie z. B. Visual Studio, nicht grundlegend. Viele, wenn auch nicht alle modernen Entwicklungsumgebungen bieten die Möglichkeit, WSDL-Dateien zu importieren und daraus entsprechenden Code für den Zugriff auf den Service zu generieren.

Damit ist es mit SOAP grundsätzlich möglich, sowohl plattform- als auch programmiersprachenübergreifende Anwendungen zu erstellen. Ob eine konkrete Zielplattform SOAP tatsächlich unterstützt, sollte jedoch geprüft werden, bevor eine entsprechende Designentscheidung gefällt wird. Typischerweise, aber nicht zwangsläufig, kommunizieren SOAP-basierte Anwendungen über HTTP. SOAP spezifiziert lediglich den Aufbau der ausgetauschten Nachrichten, nicht jedoch den von den Kommunikationspartnern zu verwendenden Transportweg. Beispielsweise definiert das Verfahren der Übertragung von SOAP-Nachrichten über E-Mail. Analog wäre der Austausch von Nachrichten über Pipes oder eine Message Queue ebenso denkbar.

Die Erstellung einer SOAP-Serveranwendung gestaltete sich dank der Unterstützung durch die Delphi-Entwicklungsumgebung verhältnismäßig einfach. Auftretende Probleme lassen sich anhand der mitgelieferten Beispiele und mithilfe der Unterstützung durch die Delphi-Newsgroups und -Foren schnell lösen. Leider enthält die VCL auch in Delphi XE nach wie vor kleinere Fehler im integrierten WSDL-Generator, sodass das erzeugte Dokument in bestimmten Fällen unvollständig sein kann. Um dies zu testen, empfiehlt sich der Import der von der VCL generierten Servicebeschreibung in Delphi oder einer anderen geeigneten Entwicklungsumgebung.

[ header = REST ]

REST

Als dritter Kandidat wurde die REST-Implementierung aus dem Synopse mORMot-Framework des Franzosen Arnaud Bouchez gewählt. Hauptgrund für die Auswahl war die Tatsache, dass dieses Delphi-Framework eine spezielle, auf REST aufbauende Methode anbietet, einen solchen Service mit einem Delphi-Interface auszustatten. Obgleich das Verfahren eindeutig proprietär ist, relativiert sich das angesichts der Tatsache, dass es für Interaktionen über RESTful APIs lediglich eine vergleichsweise grobe Spezifikation gibt. Diese lässt letztlich nicht nur das konkrete Datenformat offen (die Verwendung von XML oder JSON wird nur empfohlen). Unglücklicherweise sieht sie außerdem keine standardisierte Möglichkeit zum Abrufen von Meta-Informationen über den Service vor.

REST

Der Ursprung von REST lässt sich ziemlich eindeutig auf die Dissertation von Roy Fielding zurückführen. Er beschreibt in dieser Arbeit REST als Architekturstil für verteilte Hypermediasysteme. Neben der Forderung der Statuslosigkeit bezüglich der Kommunikation mit dem Server formuliert Fielding weitere Anforderungen an einen REST-konformen Service. RESTful Services sind seitdem aufgrund ihrer offensichtlichen Vorteile zu einem der bekanntesten Verfahren geworden, wie Informationen in verteilten Anwendungen abgerufen und bearbeitet werden. Wichtig für das Verständnis der Arbeit ist die Tatsache, dass sich Fielding darin nicht grundlos immer wieder explizit auf Hypermediasysteme und den Zugriff auf Ressourcen bezieht. Er benennt darin zudem auch Einschränkungen und Implikationen einer REST-konformen Architektur. In diesem Zusammenhang ist auch Roy Fieldings Blog-Artikel „REST APIs must be hypertext driven“ eine inklusive aller Kommentare unbedingt lesenswerte Diskussion häufiger Missverständnisse im REST-Hype.

Genau aus diesem Grund implementiert das mORMot-Framework für jeden definierten Service mithilfe von RTTI einen speziellen Aufruf, mit dessen Hilfe der Client diese Metainformationen in einem ebenfalls proprietären Format beim Verbindungsaufbau vom Server abruft. Die vom Framework bereitgestellte Basisklasse für die Clientprogramme bildet sodann eine Prüfsumme über diese Metainformationen und vergleicht den berechneten Wert mit den Informationen, die es aus den im Clientcode einkompilierten Klassen gewinnt. Stimmt die Prüfsumme nicht überein, wird die Verbindung abgebrochen. Das bedeutet im Umkehrschluss, dass diese spezielle Implementierung Interface-basierter Dienste keine Softversionierung (Kasten: „Softversionierbarkeit“) erlaubt, obwohl REST dies per se problemlos unterstützen kann. Sind Änderungen oder Erweiterungen am Interface nötig, gilt das geänderte Interface genau wie bei COM als neue Entität. Ist eine Interoperabilität mit älteren Servern oder Clients erforderlich, muss der Server sowohl die alte als auch die neue Version unter einem eindeutigen Namen anbieten. Ebenso problematisch ist die Anbindung eines solchen Service in andere Programmiersprachen. Weil das Framework aber neben Delphi auch Free Pascal unterstützt, ist Plattformunabhängigkeit in gewissen Grenzen möglich.

Softversionierbarkeit…

… bezeichnet die Fähigkeit, einen einmal veröffentlichten Servicekontrakt unter Beibehaltung des Kontraktes nachträglich modifizieren zu können, ohne damit die Kompatibilität mit älteren Implementierungen des Kontraktes zu brechen. Die aus COM bekannten Interfaces sind nicht softversionierbar, nach jeder Änderung am Interface oder den Methodensignaturen bekommt das Interface zwingend eine neue GUID. Webinterfaces sind dagegen häufig softversionierbar und können in gewissen Grenzen ohne größeren Aufwand auch nachträglich noch geändert oder erweitert werden.

Für kommerzielle Zwecke ist vor allem die Frage der Lizenzierung interessant. Das mORMot-Framework ist komplett Open Source und bietet aktuell die Auswahl aus drei Lizenzen, nämlich GPL, LGPL und MPL, sodass für den Vertrieb eigener Software kaum Wünsche offen bleiben dürften. Apropos: Das Framework kommt mit einer beeindruckenden Menge an Dokumentationen des Wegs. Allein das Benutzerhandbuch umfasst derzeit nahezu 800 Seiten. Beschäftigt man sich ein wenig detaillierter mit dem Projekt, stellt man schnell fest, dass mORMot weit mehr ist als nur ein Kommunikationsframework. Es handelt sich hierbei tatsächlich um eine komplette Laufzeit-Bibliothek, die sich zum Ziel gesetzt hat, insbesondere serviceorientierte Architekturen und Anwendungen mit Delphi bestmöglich zu unterstützen. Das schließt neben einer umfangreichen Unterstützung für REST insbesondere Features wie Zugriff auf diverse Datenbanken mit besonderer Unterstützung für SQLite 3 ein sowie Technologien wie JSON, PDF, GDI+, NamedPipes und vieles mehr. Der Support ist ebenfalls mit „sehr gut“ zu bewerten. Bei Sorgen, Nöten und Problemen erhält man im Forum schnell kompetente Hilfe. In den Issue Tracker eingetragene Bugs und Erweiterungsvorschläge werden zügig bearbeitet. Die Programmierung weist viele Parallelen zu SOAP auf. Auch hier wird der Interface-Kontrakt mithilfe entsprechender Datenstrukturen im Pascal-Quelltext formuliert. Das Gegenstück auf der Clientseite wird allerdings nicht generiert, sondern man bindet einfach dieselbe Unit auch im Client ein. Eine bekannte Einschränkung ist dabei die Tatsache, dass man für Datenstrukturen statt auf Klassen besser auf Records zurückgreifen sollte. Aufgrund dieser geringen Unterschiede war die Implementierung dieser Version vergleichsweise simpel, da im Wesentlichen der Code aus der SOAP-Implementierung mit wenigen Änderungen wiederverwendet werden konnte.

[ header = Apache Thrift ]

Apache Thrift

Als vierter und letzter Kandidat tritt mit Apache Thrift ein Framework an, das zwar weit weniger bekannt als die anderen drei ist, nichtsdestotrotz aber eine beeindruckende Liste an Referenzprojekten vorweisen kann. Ursprünglich bei und für Facebook entwickelt, wurde das Projekt vor einigen Jahren als Open Source freigegeben und in den Apache Inkubator aufgenommen. Neben Facebook vertrauen namhafte Projekte wie die Datenbanken Apache Cassandra, HBase und Hypertable sowie die Webplattform Evernote auf Thrift, um nur einige wenige zu nennen.

Apache Thrift

Das Apache-Thrift-Framework ist ein Softwareframwework für die Entwicklung skalierbarer plattform- und sprachübergreifender Anwendungen. Es verbindet eine im Quellcode vorliegende Runtime-Bibliothek mit einem Code-Generator. Letzterer erzeugt anhand einer vorliegenden Servicebeschreibung spezifischen und optimierten Code. Die Servicebeschreibung erfolgt dabei in einer leicht erlernbaren Interface Definition Language (IDL). Auch Thrift baut auf wenigen vordefinierten Basisdatentypen auf, die mittels struct sowie drei Containertypen nahezu beliebig komplexe Strukturen erlaubt. Das Framework unterstützt derzeit Codegeneratoren für 21 verschiedene Programmiersprachen und -sprachversionen. Thrift wurde ursprünglich von Facebook für interne Zwecke entwickelt, um die in verschiedenen Sprachen geschriebenen Dienste und Anwendungen möglichst effizient miteinander integrieren zu können.

Die Technologie hinter Thrift weicht etwas von denen der anderen drei Kandidaten ab. Während in allen anderen Fällen textuelle Daten übertragen und auf der Zielmaschine geparst werden, ist dies bei Thrift nicht zwingend der Fall. Obgleich wir im Beispielcode als Datenformat JSON verwenden, unterstützt Thrift außerdem auch ein sehr effizientes binäres Format. Natürlich müssen die Daten beim Empfänger in geeigneter Weise verarbeitet werden. Thrift ist aber extra dafür optimiert, den gesamten Übertragungsvorgang so schnell und so effizient wie nur irgend möglich durchzuführen, was den Vorgang des Parsens einschließt. Um dies zu erreichen, verwendet Thrift speziell für den definierten Servicekontrakt generierten und optimierten Code. Die Definition des Dienstes wird in einer so genannten IDL-Datei vorgenommen, die dann mithilfe eines speziellen Compilers in Code für die gewünschte Zielsprache übersetzt wird. Eines der wesentlichen Designziele von Thrift ist es, plattform- und programmiersprachenübergreifende Kommunikation möglichst effizient abzuwickeln. Mittlerweile unterstützt Thrift eine ganze Reihe verschiedenster Programmiersprachen: Java, PHP, C++, C# und Delphi, außerdem Sprachen wie Haskell, Erlang, D, Go, JavaScript inklusive Node.js und viele andere. Neben mehreren verschiedenen Übertragungsformaten, die in Thrift-Terminologie als Protokolle bezeichnet werden, glänzt das Framework neben Sockets auch mit diversen anderen Transporten. Der Ausbau der Infrastruktur und die Unterstützung der Protokolle und Transporte ist je nach Zielsprache mehr oder weniger umfangreich implementiert, sodass man vor einer diesbezüglichen Designentscheidung unbedingt prüfen sollte, ob die gewünschte Kombination aus Plattform, Programmiersprachen, Transport und Protokoll tatsächlich verfügbar ist oder ob man auf eine andere Kombination ausweichen sollte. Apache Thrift steht, wie nicht anders zu erwarten, unter einer Apache-2-Lizenz. Für die Definition eines Thrift-Service können in der IDL-Datei wie auch bei allen anderen Kandidaten neben den üblichen Basistypen auch Aufzählungstypen und strukturierte Typen Verwendung finden. Daneben unterstützt die IDL-Syntax auch einige einfache Containertypen. Einzelne Datenelemente können direkt in der IDL als erforderlich oder optional gekennzeichnet werden. Das wirkt sich einerseits auf die Codegenerierung aus, andererseits auf die Softversionierbarkeit. Ein einmal als erforderlich gekennzeichnetes und veröffentlichtes Element darf ab diesem Moment nicht mehr modifiziert oder aus dem Interface entfernt werden.
Alle optionalen Elemente hingegen können jederzeit entfallen und durch andere Elemente ersetzt werden. Die offizielle Dokumentation besteht aus einem Whitepaper und einem für jede Programmiersprache mitgelieferten, einheitlichem Tutorial sowie entsprechenden Testprojekten. Daneben steht ein Wiki mit weiterer Dokumentation zur Verfügung. Apache-üblich existieren ein Issue Tracker und projektbezogene Mailinglisten, wo sich bei akuten Fragen meist auch schnelle und kompetente Hilfe findet. Implementierungstechnisch besteht die größte Herausforderung nach der Definition des Interfaces in einer schnell erlernbaren Syntax in der IDL-Datei und der Kompilierung derselben. Das Vorgehen bei der Initialisierung von Client und Serveranbindung lässt sich aus den Beispielcodes schnell entnehmen, sodass man zügig zu den ersten Resultaten kommt. Alle in der IDL definierten Datenstrukturen werden in referenzgezählte Klassen übersetzt, was die Programmierung angenehm entspannt gestaltet, da man sich um Aufräumarbeiten keine Gedanken machen muss.

[ header = XML-RPC ]

XML-RPC

Ebenso wie SOAP ist auch XML-RPC mehr oder weniger standardisiert und basiert, wie der Name schon sagt, ebenfalls auf XML. Erstaunlicherweise fanden sich im Internet lediglich zwei ernstzunehmende und für Delphi geeignete Implementierungen. Eine kommerzielle Lösung ist von RemObjects im RemObjects-SDK für Delphi erhältlich. Das zweite Projekt ist eine Open-Source-Lösung unter LGPL-Lizenz, die auf SourceForge gehostet ist. Auch wenn Letzteres nicht wirklich ein aktives Projekt ist, musste es dennoch mangels Alternativen als Testkandidat herhalten. Obgleich diese konkrete Bibliothek nur für Delphi geeignet ist, so stehen doch dank Standardisierung auch für andere Programmiersprachen geeignete Implementierungen für XML-RPC bereit.

XML-RPC…

… ist quasi der Vorläufer von SOAP. Wie dieses baut auch XML-RPC grundsätzlich auf XML-Daten auf. Der zweite Teil des Namens steht einfach für „Remote Procedure Call“, bezeichnet also den Aufruf einer Prozedur oder Funktion „aus der Ferne“. Im Gegensatz zu SOAP schreibt die Spezifikation für XML-RPC zwingend die Verwendung von HTTP vor. Aus sechs fest definierten Basisdatentypen lassen sich mittels array oder struct komplexere Strukturen zusammenbauen. Verglichen mit dem weitaus mächtigeren SOAP sind XML-RPC-Nachrichten dank des bewusst limitierten Umfangs regelmäßig weniger umfangreich als ihre SOAP-Pendants. Über eine als XML-RPC Introspection bezeichnete Erweiterung werden Clients in die Lage versetzt, Metainformationen über einen angebotenen Dienst vom Server abzurufen.

Für professionelle Applikationen mit dieser Bibliothek sollte man außerdem die Einschränkung der Lizenzierung beachten, insbesondere, da das Paket auf zwei weiteren Open-Source-Komponenten aufbaut. Ein weiteres K.O.-Kriterium besteht möglicherweise darin, dass die vorliegende Implementierung aufgrund ihres Alters kein Unicode unterstützt, weder in Form von UTF-8 noch als UTF-16. Um die Bibliothek mit der Delphi XE lauffähig zu bekommen, ist zunächst ein Update der beiden Zusatzkomponenten DIMime und LibXmlParser, zu finden auf den entsprechenden Projektseiten, notwendig. Außerdem muss der den Codebeispielen beiliegende SVN-Patch auf die aus dem SVN-Repository des XML-RPC-Projekts ausgecheckte Version des Codes angewendet werden. Bei Problemen mit der Bibliothek kann man kaum mit Support rechnen: Das letzte offizielle Release ist bereits mehrere Jahre alt, die Entwickler pflegen das Projekt nach eigener Aussage nicht mehr aktiv weiter. Ein Projektforum ist zwar vorhanden, aber ähnlich wenig frequentiert. Glücklicherweise ist der Quellcode nicht übermäßig umfangreich, sodass man im Problemfall mithilfe eines handelsüblichen Debuggers schnell selbst Abhilfe schaffen kann.

Sowohl bei SOAP als auch bei XML-RPC werden Methodenaufrufe und Parameter über ihre Namen identifiziert. Beide Verfahren ermöglichen Softversionierung, indem neue Felder oder Methoden einfach hinzugefügt werden. Die Formatdefinition selbst gibt keine Auskunft darüber, ob ein bestimmtes Argument oder Datenfeld erforderlich oder optional ist. Derartige Informationen müssen also über eine separate Dokumentation an den Service-Consumer kommuniziert und in der Implementierung des Servers im Code geprüft werden. Wie wir noch sehen werden, ist die übertragene Datenmenge bei XML-RPC deutlich geringer als bei einem entsprechenden SOAP-Call, was vor allem an einem geringeren Overhead liegt. Das XML-RPC-Protokoll hat nicht ganz zufällig einige auffällige Gemeinsamkeiten mit SOAP. Sowohl SOAP als auch XML-RPC entstanden entweder direkt oder unter Beteiligung von Dave Winer, sodass man SOAP auch als Nachfolger von XML-RPC betrachten könnte.

Open Data

Das Open Data-Protokoll oder kurz OData, ist ein Microsoft-Standard, dessen primärer Zweck es ist, auf REST aufsetzende Web Services besser zu standardisieren. Die recht längliche Spezifikation beschreibt detailliert Aufbau und Datentypen von OData-konformen Services. Hierzu definiert Microsoft einen Satz an Erweiterungen und Ergänzungen zum AtomPub-Format. In der Vergangenheit firmierte dieses Protokoll unter verschiedenen Namen, z. B. Astoria oder ADO.NET Data Services Protocol. Microsoft stellte das Protokoll unter die so genannte „Open Specification Promise“. Diese ist, kurz gesagt, ein einseitiges, rechtlich bindendes und nicht widerrufbares Versprechen Microsofts, keinerlei rechtlichen Ansprüche und Forderungen aus der Verwendung bestimmter Technologien einzufordern. Da Open Data aber neben AtomPub wiederum auf REST und JSON basiert, wurde diese Variante nicht extra getestet.

Traffic und Performance

Fast erwartungsgemäß produzierten die XML-basierten Verfahren sowohl die meisten Pakete als auch die größeren Datenmengen. Dass die Ergebnisse jedoch so drastisch ausfallen wie in Abbildung 4, ist schon bemerkenswert.

Abb. 4: Generierter Traffic (kleinere Werte sind besser)

SOAP und XML-RPC erzeugen für dieselbe Aufgabe etwa doppelt so viel Datenaufkommen wie die verwendete mORMot-REST-Implementierung, während Apache Thrift mit lediglich etwa einem Viertel davon auskommt. Das Verhältnis zwischen größtem und kleinstem Wert beträgt immerhin fast eins zu sieben. Gerade für Einsatzfelder mit geringer Übertragungsrate oder limitiertem Datenaufkommen kann dieser Unterschied entscheidend für die Performance der gesamten Anwendung sein. Wie man an dieser Auswertung eindrucksvoll erkennt, ist die Wahl tendenziell geschwätziger Datenformate wie XML keine besonders geschickte Entscheidung, wenn es um möglichst effiziente Übertragung von Daten geht. Die konsequente Optimierung von Thrift auf diesen Aspekt hin macht sich in der Tat deutlich bemerkbar.

Ein ähnliches Bild zeigt sich bei den Performancemessungen in Abbildung 5. Hier erkennen wir vor allem zwei Dinge: Die Datenübertragung zwischen Client und Server auf ein- und derselben Maschine oder in einem lokalen Netzwerk ist kaum vergleichbar mit der erzielbaren Übertragungsrate im Internet, sondern unterscheidet sich davon um etwa ein bis zwei Zehnerpotenzen. Weiterhin relativiert sich das Verhältnis der einzelnen Technologien mit steigender Latenz des Übertragungsmediums. Trotzdem geht auch hier Apache Thrift in allen Fällen als klarer Sieger hervor, dicht gefolgt von der REST-Implementierung des mORMot-Frameworks.

Bemerkenswert ist, dass sich auch XML-RPC grundsätzlich recht wacker schlägt. Lediglich der dort mehrfach und eindeutig reproduzierbare, deutliche, allerdings völlig unerklärliche Einbruch der Übertragungsrate im Web trübt das Ergebnis. Bei allen Tests fällt die SOAP-Implementierung der VCL durch die Bank weg besonders negativ auf.

 

Abb. 5: Aufrufe pro Sekunde (größere Werte sind besser)

Falls Sie die Tests in Ihrer Umgebung selbst durchführen wollen, hier noch ein Hinweis zur Konfiguration der Clients: Im Programmverzeichnis wird eine Datei Client.INI erwartet, in der zwei konfigurierbare Einstellungen vorhanden sind. Die erste Einstellung gibt den Computernamen oder die IP-Adresse des Servers an, mit dem sich der Client verbinden soll. Die zweite Erstellung legt die Anzahl Aufrufe für den Performancetest fest. Werte zwischen 100 und 1000 sind ein guter Richtwert für diesen Parameter. Fehlt die Konfigurationsdatei, nimmt der Client automatisch localhost als Server und 100 Aufrufe als Default-Werte an.

[ header = Programmierbarkeit und weitere Aspekte ]

Programmierbarkeit

Die Erstellung der Testanwendung war mit allen verwendeten Frameworks in angemessener Zeit leicht umzusetzen. Der Hauptunterschied liegt im Wesentlichen in der Art und Weise, wie die Implementierung an das definierte API angeschlossen wird und wie viel manuelle Nacharbeit dafür erforderlich ist. Hat man die Syntax der IDL-Datei einmal erlernt, ist die Umsetzung von Änderungen am Interfacecode mit Thrift am schnellsten und zuverlässigsten erledigt, da die Notwendigkeit der manuellen Änderung des Quellcodes entfällt. Einen ähnlichen Komfort bietet die VCL-Implementierung von SOAP, da nach Erstellung des Servercodes das für die Clients erforderliche Gegenstück im Regelfall mit der Entwicklungsumgebung generiert werden kann. In allen anderen Fällen muss nicht nur die initiale Implementierung geschrieben werden, sondern es müssen auch alle Änderungen gegebenenfalls für jede einzelne Programmiersprache nachgepflegt werden, was zumindest theoretisch wieder Raum für ganz banale, deshalb aber umso ärgerlichere Übertragungs- oder Tippfehler lässt.

Als die mit Abstand umständlichste Implementierung von allen stellte sich die Verwendung von Delphi XML-RPC heraus. Da dieses Projekt keinen deklarativen Ansatz bietet, muss vieles auf verhältnismäßig niedrigem Abstraktionsniveau vom Anwendungsentwickler explizit ausprogrammiert werden. Beispielsweise müssen alle Datenfelder in die Datenstruktur eingefüllt und auf der Gegenseite wieder extrahiert werden. Die durchdachte Unterstützung seitens der Bibliothek vereinfacht diesen Vorgang zwar und lässt diesen Teil der Programmierung zur reinen Fleißaufgabe werden. Dennoch erwartet man von einem modernen Framework inzwischen einfach ein besseres Tooling. Dazu kommt auch das ebenso umständliche manuelle Verdrahten der Handler-Funktionen auf der Serverseite. Ist man für den produktiven Einsatz auf XML-RPC angewiesen, wäre daher unbedingt zu überlegen, inwieweit man stattdessen besser auf die von RemObjects angebotene kommerzielle Implementierung zurückgreifen sollte, die aller Wahrscheinlichkeit nach moderne Delphi-Versionen von Haus aus und vor allem ohne manuellen Eingriff unterstützen dürfte.

Weitere Aspekte

Für diese Tests wurden Sicherheitsaspekte vollkommen außer Acht gelassen. In einer echten Anwendung ist diese Frage aber durchaus relevant. Das umfasst zunächst die Frage, ob beispielsweise SSL unterstützt wird. Weiterhin ist zu prüfen, ob die Plattform der Wahl standardisierte Authentifizierungs- und Autorisierungsmechanismen unterstützt. Last but not least sind auch Fragen zur Sicherheit, insbesondere zur serverseitigen Implementierung, zu stellen. Hierunter fällt natürlich die Sicherheit gegen Angriffe, die den Server kompromittieren würden, aber auch die Robustheit gegenüber fehlerhaftem Input. Es wäre schließlich fatal, wenn sich der Server bereits nach einem einzigen fehlerhaften Datenpaket verabschiedet und ein potenzieller Angreifer somit vielleicht einen stark frequentierten oder unternehmenskritischen Dienst problemlos per Mausklick aus dem Verkehr ziehen kann.

Für eine praxistaugliche Umsetzung ist neben Sicherheitsaspekten auch die vom Server verarbeitbare Last ein wesentliches Kriterium. Hierzu ist es unbedingt erforderlich, dass die Serverimplementierung Multithread-fähig ist und nach Möglichkeit auch gleich von Haus aus unterstützt. Oftmals liegt der Flaschenhals jedoch nicht in der serverseitigen Protokollanbindung, sondern in der Programmierung der dahinter liegenden Businesslogik oder der Datenbankanbindung.

Auch wenn Protokolle wie REST sich Statuslosigkeit auf die Fahnen geschrieben haben, ist das in der Praxis meist nur die halbe Wahrheit. Faktisch ist es so, dass jede ernstzunehmende Applikation ab einer bestimmten Größe quasi zwangsweise irgendeine Form von Status verwalten muss. Konzepte wie REST mögen bei naiver Betrachtung vielleicht suggerieren, dass eine komplett statuslose Applikation das erstrebenswerte Ziel wäre, dem ist jedoch nicht so. Tatsächlich bezieht sich der Terminus „statuslos“ in REST nämlich ausschließlich auf den Zustand von Kommunikation und Server, wie Roy Fielding ja auch im Abschnitt 5.1.3 seiner Arbeit explizit formuliert hat. Daher bedeutet „statuslos“ eben gerade nicht, dass es in der gesamten Applikation, die ja auch den Client und das Datenbank-Backend einschließt, überhaupt keinen Status gibt. Bereits wenn sich der Benutzer für die Services anmelden muss, liegt nämlich de facto ein Status vor. Die Situation wird sich leider auch nicht ändern, indem man, wie vereinzelt in der Praxis anzutreffen, die Existenz eines Status mit einer gewissen Ignoranz einfach mal leugnet und alle daraus resultierenden Probleme mit der lapidaren Bemerkung „macht doch einfach alles statuslos“ an die gegenüberliegende Seite weiterdelegiert.

Ein rationaler und verantwortungsvoller Entwickler wird sich daher nicht der Illusion hingeben, dass man schon keinen Status zu verwalten haben werde, sondern wird sich vielmehr rechtzeitig Gedanken darüber machen, wie man mit früher oder später fast zwangsläufig anfallenden Statusdaten umgeht. Die beiden grundsätzlichen Ansätze sind folgende: Ist der Umfang der Statusdaten überschaubar, sollte man diese vollständig auf Seiten des Clients halten und den notwendigen Kontext in jeder Anfrage an den Server übermitteln. Das ist im Übrigen auch genau das Prinzip hinter REST. Je größer der Umfang dieser zu übermittelnden Kontextdaten aber wird, desto mehr Traffic wird dadurch generiert werden. Ab einem bestimmten Punkt ist es deshalb besser, den Speicherort für die einzelnen Status-Datenelemente in geeigneter Weise zwischen Client und Server aufzuteilen. Um den Server selbst dennoch statuslos zu halten, wird der serverseitige Teil des Status daher typischerweise in so genannten Sessions gehalten und in ein Speichermedium mit möglichst kurzen oder nach letztem Zugriff gestaffelten Zugriffszeiten persistiert.

Alle Designentscheidungen – und auch alle während der Implementierungsarbeiten getroffenen Entscheidungen – sind immer ein Balanceakt, bei dem es gilt, verschiedene Aspekte und Anforderungen, wie eben Traffic, Performance und Speicherplatz, gegeneinander abzuwägen. Das Ziel aller Bemühungen sollte aus Benutzersicht eine stabile, skalierbare, flüssig reagierende Applikation sein, die die zugedachte Aufgabe und alle Randbedingungen möglichst perfekt erfüllt und die vom Benutzer eingegebenen Daten sicher verwahrt. Und das sind letzten Endes die wichtigsten Kriterien, an denen sich alle Entscheidungen messen lassen müssen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -