Serviceorientierte Architektur am Beispiel JAMES
Masterarbeit zur Erlangung des akademischen Grades Master of Science
Verfasser: Matthias Frick, MSc.
Vorgelegt am FH-Masterstudiengang MultiMediaTechnology, Fachhochschule Salzburg
Begutachtet durch:
Dr. Sebastian Schaffert (Betreuer)
DI Dr. Simon Ginzinger, MSc (Zweitbetreuer)
Salzburg, 26.08.2013
FH Salzburg
Masterarbeit
Serviceorientierte Architektur
JAMES
Hiermit versichere ich, Matthias Frick, geboren am 26.10.1987 in Feldkirch, dass ich die Grundsätze wissenschaftlichen Arbeitens nach bestem Wissen und Gewissen eingehalten habe und die vorliegende Masterarbeit von mir selbstständig verfasst wurde. Zur Erstellung wurden von mir keine anderen als die angegebenen Quellen und Hilfsmittel verwendet.
Ich versichere, dass ich die Masterarbeit weder im In- noch im Ausland bisher in irgendeiner Form als Prüfungsarbeit vorgelegt habe und dass diese Arbeit mit der den BegutachterInnen vorgelegten Arbeit übereinstimmt.
Salzburg, am 26.08.2013
Eidesstattliche Erklärung
Die vorliegende Masterarbeit befasst sich mit der Thematik "Serviceorientierte Architektur am Beispiel JAMES". Im Speziellen wird auf die Forschungsfrage "Wie kann die Struktur einer monolithischen Ruby on Rails Applikation in eine serviceorientierte Architektur umgewandelt werden?" eingegangen. Zuerst werden die Grundlagen von Services sowie deren Kommunikation und technologische Möglichkeiten zur Umsetzung der Kommunikation erläutert. Hierbei wird ein Vergleich sowie die Interpretation der Ausführungen verschiedener AutorInnen vorgenommen. Anschließend folgt die Vorstellung diverser bestehender Frameworks und Tools, welche als Grundbausteine für eine serviceorientierte Architektur fungieren könnten. Nachfolgend werden die Grundzüge des im Rahmen dieser Arbeit entstandenen Prototypen für ein Ruby-Tool für serviceorientierte Architektur anhand eines einfachen Beispiels vorgestellt. Die Beantwortung der Forschungsfrage findet ihren Abschluss in der Ausarbeitung der Umstrukturierung der Applikation JAMES. Diese Neuordnung der Architektur erfolgt anhand des Prototypen. Unterschiedliche Vorgehensweisen werden erläutert und das Ergebnis präsentiert. Mit einer abschließenden Diskussion und einem Ausblick auf zukünftige Entwicklungen wird diese Masterarbeit beendet.
Serviceorientierte Architektur
JAMES
Ruby on Rails
Ruby
Rack
OSGI
Kommunikation
Web Services mit REST
Messaging
Partitionierung
This master static deals with the topic of service-oriented architecture, using the example of JAMES. The author examines the question of how the structure of a monolithic Ruby on Rails application can be converted into a service-oriented architecture. First, a theoretical basis will be established by presenting the principles of various services as well as technological possibilities for the realization of their communication. The chapter will then outline and interpret multiple approaches introduced by various authors. Moreover, existing frameworks and tools, particularly relevant for service-oriented architecture, will be described. Second, the author will explain the underlying principles of the prototype designed in line with the present research. Subsequent to that, the static elaborates on the restructuring of the application JAMES. As a matter of course, the reshaping of its architecture is carried out using the designed prototype. To conclude, the author will evaluate research outcomes and provide an outlook on future developments.
Service-oriented architecture
JAMES
Ruby on Rails
Ruby
Rack
OSGI
communication
Web Services with REST
Messaging
Partitioning
1. Einleitung
1.1 Motivation
Jede Software benötigt einen Aufbau, eine Strukturierung - in der Gesamtheit gesprochen - eine Architektur. Dabei gibt es die Möglichkeit eine Anwendung „monolithisch“ zu entwickeln. „Monolithisch“ stammt vom englischen Fachbegriff „monolithic“ und bedeutet soviel wie „untrennbar“. Eine monolithische Applikation wird somit als ein großes Ganzes entwickelt. Es gibt eine Code-Basis, auf welcher weitere Klassen und Module aufbauen. Ein Beispiel für eine monolithische Anwendung ist eine Standard Ruby on Rails-Applikation (Ruby on Rails vgl. 5.1). Eine solche Applikation bezieht den gesamten Programmcode aus eigenen Ressourcen, mit anderen Quellen findet kein Austausch statt. Sollten externe Quellen (z.B. Libraries, Gems) zum Einsatz kommen, sind diese vollends in die Anwendung eingegliedert und werden direkt angesprochen. Die Dienste, welche innerhalb einer monolithischen Anwendung angeboten werden, werden ebenfalls von der Anwendung selber erbracht. Es dienen keine Zweitanwendungen oder zusätzlichen Dienste als Untergruppen der Hauptapplikation.
Eine zweite Variante ist die Entwicklung einer Software im nicht-monolithischen Stil. Dabei wird eine Applikation in verschiedene Teilbereiche, so genannte Services (vgl. 2.1), unterteilt, welche anschließend getrennt voneinander entwickelt werden. Die dadurch entstehende Struktur wird als serviceorientierte Architektur (serviceorientierte Architektur (SOA) vgl. 2.2) bezeichnet. Bei dieser Vorgehensweise nutzt die Hauptapplikation – im Gegensatz zum monolithischen Ansatz – Zweitanwendungen und externe Dienste bzw. Services und ist selber in einzelne Module aufgeteilt.
SOA ist ein Architektur-Ansatz, der bestimmte Probleme lösen kann. Dazu zählen beispielsweise die Integration von unterschiedlichen Softwaresystemen in eine Umgebung oder der Austausch verschiedener Funktionalitäten durch einzelne Services. Durch den nicht-monolithischen Programmierstil ist eine leichtere Entwicklung, einfacheres Testing, gute Skalierung und Portierung möglich. Einzelne Services können wiederverwendet werden und die gesamte Applikation wird robuster. (Dix 2011, 39) Andererseits führt dieser Ansatz auch zu Problemen bzw. es ergeben sich dadurch auch Schwierigkeiten, wie zum Beispiel die Kommunikation (vgl. 3) eines solchen Systems.
Die genannten Vor- und Nachteile bestätigt Werner Vogel, CTO des Onlineshops Amazon, in einem Interview mit Jim Gray aus dem Jahre 2006. Er berichtet dabei von der geschichtlichen Entwicklung von Amazon. (O’Hanlon 2006, 14f) Die Plattform wurde vor zehn Jahren (Interview-Stand 2006, somit heute, Jahr 2013, schon 17 Jahre) als eine monolithische Implementierung entwickelt. Im Laufe der Zeit wurde sie immer serviceorientierter, um den ständig neuen Anforderungen standhalten zu können. Nur dadurch konnten Themen wie Performance, Skalierung, Wartung, Wiederverwendung, Spezifizierung von EntwicklerInnen und Isolation von einzelnen Modulen bewältigt werden. Ruft eine BenutzerIn gewisse Seiten von Amazon auf, werden im Hintergrund mitunter einhundert Services angesprochen, welche am Aufbau der Seite beteiligt sind. (O’Hanlon 2006, 16)
Die Architektur einer Software ist nicht nur heutzutage ein interessantes und wichtiges Thema, sondern sorgte seit jeher für Gesprächsstoff in der Informatik. In den neunziger Jahren lieferten sich beispielsweise Andrew Stuart Tanenbaum und Linus Torvalds eine heftige Debatte über das Thema „monolithisch vs. microkernel“. Diese wurde als „The Tanenbaum–Torvalds Debate“ bekannt. Die microkernel-Variante ist im Prinzip ein erster Ansatz von SOA (SOA vgl. 2.2). Tanenbaum greift in dieser Debatte die Systemarchitektur von Linux scharf an:
“LINUX is a monolithic style system. This is a giant step back into the 1970s. That is like taking an existing, working C program and rewriting it in BASIC. To me, writing a monolithic system in 1991 is a truly poor idea.”
(DiBona, Ockman, and Stone 1998, 103)
Dieses Zitat hat bei Torvalds, seinerseits Linux Kernel Entwickler, natürlich Gegenstimmung hervorgerufen und eine heftige Diskussion angefochten. Details zu diesem auch als „Flamewar“ bekannt gewordenen Dialog sind dem Paper „OPENSOURCES - Voices from the Open Source Revolution“ zu entnehmen. Tatsache ist aber, dass dieses Thema bereits seit mehreren Jahrzehnten hart umkämpft ist. Mittlerweile hat der Streit zu Kompromissen geführt. So ist einerseits die monolithische Architektur von Linux etwas serviceorientierter geworden, und andererseits sind microkernelbasierte Systeme, wie beispielsweise Windows NT, sehr monolithisch geworden.
Aus dieser Entwicklung „monolithisch vs. microkernel“ entnimmt der Autor, dass SOA ein relevantes Forschungsgebiet für eine Bearbeitung in einer Masterarbeit ist. Durch die aktuellen Entwicklungen in der Computerwelt (z.B. Stichwort „Cloud Computing“) ist dieses Themengebiet auch zum jetzigen Zeitpunkt relevant. Durch die Cloud werden die Vorteile einer SOA noch weit größer. Angesprochenen Themen wie der Skalierung einer Anwendung können gute Lösungen entgegengestellt werden. Des Weiteren werden Bereiche wie die Skalierung immer wichtiger, da der allgemeine Anteil an InternetbenutzerInnen, und somit potentiellen KundInnen von Softwarelösungen, immer mehr zunimmt. Somit schließt sich der Kreis und die Motivation bzw. Begründung, warum gerade zum jetzigen Zeitpunkt das Thema SOA behandelt werden sollte.
1.2 Fragestellung und Ziel
Im Rahmen dieser Masterarbeit soll die Struktur einer monolithischen Ruby on Rails-Applikation in eine SOA (vgl. 2.2) umgebaut werden. Bei dieser Anwendung handelt es sich um das Abschlussprojekt des Autors dieser Arbeit für den Studiengang „MultiMediaTechnology“ an der Fachhochschule Salzburg. Die Anwendung trägt den Namen „JAMES“ und wird im späteren Verlauf dieser Arbeit näher vorgestellt (vgl. 6.1). Es muss allerdings bereits jetzt vorweg genommen werden, dass es sich bei JAMES um eine Online-Applikation handelt. Themen wie Skalierung, Wiederverwendbarkeit oder Isolation von Modulen in Bezug auf das Testing sind im Rahmen dieses Projektes sehr wichtig und stellen somit den Bezugspunkt zu den angesprochenen Punkten im Kapitel „Motivation“ (vgl. 1.1) dar.
Der Autor hat sich bewusst für die Umstrukturierung von JAMES entschieden. Das hinter der JAMES-Implementierung liegende Framework Ruby on Rails (vgl. 5.1) kommt auch immer öfter im Enterprise-Bereich zum Einsatz und stellt deshalb eine gute Basis dar.
Durch die Umstrukturierung von JAMES soll auf die Forschungsfrage „Wie kann die Struktur einer monolithischen Ruby on Rails-Applikation in eine SOA umgewandelt werden?“ eingegangen werden.
Wichtig ist, dass die Grundbausteine der neuen Architektur in der Programmiersprache Ruby implementiert sind. Es soll im Rahmen dieser Masterarbeit ein Prototyp für ein SOA-Tool für Ruby-Anwendungen entstehen. Dabei ist die Implementierung dieses Prototyps an die Applikation JAMES gekoppelt. Er soll allerdings nicht maßgeschneidert auf JAMES angepasst werden, sondern auch für andere Ruby-Anwendungen verwendet werden können.
Um das Forschungsgebiet einzugrenzen wird nur ein Lösungsweg für die Programmiersprache Ruby vollzogen werden. Modelle, Tools und Möglichkeiten aus anderen Programmierumgebungen, wie beispielsweise der Java-Welt, werden allerdings zur Hilfe herangezogen und eventuell vorhandene Vorteile und Wissen sollen genutzt werden.
Diese Masterarbeit verfolgt somit zwei Ziele. Zum einen soll aufgezeigt werden, wie die Umstrukturierung durchgeführt werden kann. Zum anderen soll der dadurch entstehende Prototyp des SOA-Tools auch für andere Anwendungen verwendet werden können. Die, nach der Meinung des Autors, angenehme „ruby-like“ Programmierung soll auf diesem Weg auch in größeren Anwendungen, welche monolithisch implementiert mit der Zeit auf Grenzen stoßen könnten, Platz finden können.
1.3 Aufbau
Mithilfe dieser Arbeit soll ein Weg gefunden werden eine monolithische Anwendung in eine SOA-Struktur (vgl. 2.2) umzuwandeln. Um dies durchführen zu können werden den LeserInnen zuerst allgemeine Grundlagen in Bezug auf Services (vgl. 2) präsentiert. Anschließend wird auf die Kommunikation und Technologien zur programmiertechnischen Umsetzung der Kommunikation innerhalb einer SOA-Struktur eingegangen (vgl. 3 bzw. 4). Die Kommunikationsarten und deren mögliche Implementierungslösungen sind zum allgemeinen Verständnis einer SOA wichtig.
Mögliche Bausteine der neu entstehenden Architektur werden im Kapitel „Frameworks und Tools“ (vgl. 5) erläutert. Diese sollen aufzeigen mit welchen Werkzeugen eine SOA für Ruby-Anwendungen entstehen könnte. Dabei werden Projekte aus den Programmiersprachen Ruby und Java herangezogen. Dadurch können für die spätere Lösung unterschiedliche Möglichkeiten aufgezeigt werden. SOA spielt im Java-Bereich schon länger eine wichtige Rolle und die bereits bestehenden Konzepte sollen nicht außen vor gelassen werden. Die Kombination aller Möglichkeiten der Umsetzung für die passende Lösung steht im Vordergrund.
Diese Masterarbeit wird anhand der Beispielapplikation JAMES erstellt. LeserInnen erhalten im nächsten Kapitel Informationen zu diesem Projekt (vgl. 6). Der aktuelle Projektstand sowie die Ziele und Anforderungen werden erläutert und klar definiert.
Im darauffolgenden Kapitel „Prototyp“ (vgl. 7) erfolgt zuerst eine allgemeine Erklärung zum Aufbau und den Konzepten des entwickelten Prototyps für ein Ruby-SOA-Tool, anschließend wird er anhand eines einführenden Beispiel erläutert.
Im Rahmen einer Umstrukturierung muss eine Partitionierung, sprich eine Zerlegung einer Applikation in einzelne Teilkomponenten, durchgeführt werden. Der Autor stellt zuerst allgemeine Varianten der Partitionierung theoretisch vor und geht anschließend praxisnah am Beispiel JAMES auf die einzelnen Möglichkeiten ein. Im Unterkapitel „Umsetzung“ (vgl. 8.6) erfolgt die Umstrukturierung am Beispiel JAMES und die Lösung wird präsentiert. Hier findet die Beantwortung der Forschungsfrage dieser Masterarbeit statt.
Die Arbeit schließt mit einer allgemeinen Diskussion und einem Ausblick auf das behandelte Forschungsgebiet. Dabei wird die vorliegende Thematik zusammengefasst und auf die allgemeine Entwicklung des Bereichs „SOA in Ruby-Anwendungen“ eingegangen. Zusätzlich werden verschiedene Möglichkeiten der Weiterentwicklung des Prototyps aufgezeigt. Mit diesem Abschnitt wird die Masterarbeit abgeschlossen.
2. Services - Grundlagen
Im folgenden Kapitel werden theoretische Grundlagen über Services und serviceorientiertes Design bzw. serviceorientierte Architektur thematisiert. Diese Grundlagen bilden das Basiswissen für die Beantwortung der Forschungsfrage dieser Masterarbeit.
2.1 Service
Ein Service stellt die Grundlage eines serviceorientierten Systems dar. Für ein besseres Verständnis wählt der Autor zunächst eine weniger abstrakte Erklärung, um einen Service zu definieren. Wird beispielsweise eine durchschnittliche, weltoffene Stadt angenommen, so können in dieser Stadt bereits verschiedenste Services, welche den BürgerInnen zur Verfügung stehen, gefunden werden. (Erl 2005, 32) Es gibt öffentliche Verkehrsmittel, Kaffees, Bars, Kinos oder Einkaufszentren. Ein Kino bietet BesucherInnen zum Beispiel gegen entsprechendes Geld den neuesten Film an. Die BesucherInnen bezahlen und können den Film konsumieren. Dies könnte als Service angesehen werden. Das Kino stellt den Gästen eine Dienstleistung zur Verfügung.
Wird nun dieses Prinzip auf die Software-Entwicklung umgelegt, kann ein Service wie folgt definiert werden: Ein Service ist eine in sich abgeschlossene Einheit (Kino), welche eine Aufgabe zu erledigen hat (Film präsentieren) und dabei die dafür benötigte Logik in sich hält, sprich einkapselt (Technik, Equipment um Film abspielen zu können). Dabei steht die Abgeschlossenheit der Funktionalität im Vordergrund. (Josuttis 2007, 26) Ein Service sollte möglichst unabhängig und nur für sich selbst agieren können. Des Weiteren verfügt ein Service über ein definiertes Input-Datenformat (Geld der Kino-BesucherInnen) und über ein definiertes Output-Format (Kinovergnügen). Ein Service erledigt die Aufgabe bestenfalls vollständig entkoppelt und unabhängig. Benötigt er allerdings zusätzliche Informationen, welche von einem anderen Service zur Verfügung gestellt werden, so kennt er die Schnittstelle zum entsprechenden Service. Damit ein Service überhaupt ausgeführt werden kann, muss er selber auch eine öffentliche Schnittstelle zur Verfügung stellen.
Ein Service gibt von seiner Implementierung nach außen hin nichts preis. Durch die öffentlich definierte Schnittstelle und die definierten Datenformate (Input bzw. Output) kann eine Trennung zwischen Service-interner Implementierung und öffentlicher Application programming interface (API) gezogen werden. (Josuttis 2007, 30)
2.1.1 Zustände
Bei Services wird zwischen „stateful“ Services und „stateless“ Services unterschieden. „Stateful“ Services haben Zustände und diese Zustände sind mitunter auch zur Laufzeit variabel änderbar. Als „stateless“ hingegen wird ein Service bezeichnet, wenn er keinen Zustand hat. Dieser Service ist nicht von einem Zustand abhängig. Wie genau die Trennung zwischen „stateful“ und „stateless“ zu ziehen ist, ist teilweise etwas konfus und nicht leicht zu entscheiden. Der Grund dafür ist, dass auch bei einem „stateless“ Service ein Zustand involviert ist. (Josuttis 2007, 191) Die Kernfrage für die Unterscheidung liegt daher darin, festzustellen wo und für wie lange ein solcher Zustand in einem Service gehalten wird. Laut Josuttis’ Ausführungen ist die Feststellung, ob ein Service „stateful“ ist oder nicht, eine Frage der Perspektive. Services können von einem Business-Standpunkt aus als zustandslos angesehen werden, aus technischer Sicht allerdings als „stateful“ und umgekehrt. (Josuttis 2007, 191) Um den Zusammenhang von Zuständen in Bezug auf Services besser erklären zu können, wird in den folgenden Absätzen noch expliziter auf die beiden Varianten von Services eingegangen.
- stateful Service
- stateless Service
- „stateless“ vs. „stateful“
Ein „stateful“ Service ist ein Service, der zwischen verschiedenen Service-Aufrufen Zustände aufrecht erhält, d.h. ein Zustand „X“ wird beim ersten Service-Aufruf angelegt. Folgt ein zweiter Service-Aufruf, existiert der Zustand „X“ bereits. Er enthält die Daten, welche beim ersten Service-Aufruf in Bezug auf diesen Zustand „X“ entstanden sind. (Josuttis 2007, 193)
Als konkretes Beispiel kann ein herkömmlicher Webshop herangezogen werden. Eine BenutzerIn legt einen Artikel in einen Warenkorb und schaut sich nach weiteren Produkten um. Der erste Artikel verweilt in der Zwischenzeit im Warenkorb. Der Warenkorb steht symbolisch für einen Zustand. Sollte die BenutzerIn einen zweiten Artikel hinzufügen, würde sich der Zustand des Warenkorbs dementsprechend ändern. Zu guter Letzt schließt die BenutzerIn den Einkauf ab und alle Produkte aus dem Warenkorb werden ausgeliefert. Somit hat sich der Zustand des Warenkorbs während des kompletten Einkaufsprozesses verändert und am Ende ein, von den jeweiligen Zwischenständen des Zustands abhängiges, Ergebnis geliefert. (Josuttis 2007, 194)
Ein „stateless“ Service ist ein Service, der zwischen verschiedenen Service-Aufrufen keinerlei Zustände aufrecht erhält, d.h. ein Service wird aufgerufen und führt seine Aufgabe aus. Für die Ausführung dieser Arbeit erstellt der Service temporär diverse lokale Variablen und Objekte. Ist die Aufgabe erledigt und der Service-Aufruf damit abgeschlossen, werden alle temporär erstellten Hilfsmittel wieder zerstört. (Josuttis 2007, 192) Wichtig ist dabei, dass nur die vom Service intern verwendeten Hilfsmittel zerstört bzw. aufgeräumt werden. Der Service könnte in einem System integriert sein, welches über eine Datenbank sowie eine Frontend-Applikation verfügt. Nach erfolgreicher Beendung des Service-Aufrufs werden aber nicht die Datenbank oder Frontend-Applikationsdaten gelöscht, sondern nur intern relevante, temporär erstellte Variablen und Objekte des Services selber. Dieser Punkt ist zum Verständnis, wie ein „stateless“ Service funktioniert, essentiell. Josuttis erläutert dies in seinen Ausführungen wie folgt:
“The service is stateless when all the data of the service instance (process or thread) that performs the call is thrown away after the call. In other words, it must not matther whether the next service call is performed using the same or a different service thread or service process.” (Josuttis 2007, 192)
Dies alles zeichnet einen zustandslosen Service aus. Es gibt allerdings auch die Möglichkeit, dass ein zustandsloser Service einen temporären Zustand zur Ausführung benötigt. Dieser temporäre Zustand und all die internen Daten können am Ende wieder entfernt werden. Der Output des Services bleibt trotzdem derselbe. Hier kann ein Service, welcher Daten zwischen zwei verschiedenen Backends synchronisiert, als Beispielanwendung genannt werden. (Josuttis 2007, 193) Dieser Service bekommt als Input eine BenutzerIn mit einer neuen Telefonnummer und soll die persönlichen Daten dieser BenutzerIn synchronisieren. Der Service speichert sich die alte Telefonnummer der BenutzerIn in einem temporären Zustand ab. Zuerst wird das erste Backend auf den neuesten Stand gebracht, anschließend folgt das zweite. Tritt während der Durchführung ein Fehler auf, kann die alte Telefonnummer aus dem temporären Zustand wieder hergestellt werden. Ist die Aktualisierung der Daten erfolgreich, kann die alte Telefonnummer inklusive der Hilfsvariablen und Objekten zerstört werden.
Der „stateless“ Service verfügt über einige Vorteile gegenüber dem „stateful“ Service. Unter anderem ist das Load-Balancing und die Ausfallsicherheit für die Service-Ebene eines serviceorientierten Systems viel einfacher zu handhaben. Ein System muss nur so gebaut werden, dass von Service-A aus auf die bestmöglichste Service-B-Instanz zugegriffen werden kann. Service-B muss dafür keinen speziellen Zustand haben oder beachten, er kann sofort und unabhängig genutzt werden. Beispielsweise kann die Kommunikation über verschiedene Instanzen und sogar Threads erfolgen, welche zuerst von der einen EndkonsumentIn und dann von der zweiten EndkonsumentIn genutzt wurden. Zwischen den einzelnen Instanzen bzw. Threads (abhängig von der jeweiligen Implementierung) herrschen somit keine Abhängigkeiten. Ein weiterer Vorteil von zustandslosen Services ist, dass die Anzahl der einzelnen Instanzen oder Threads beliebig erhöht werden kann, sollte das System in einem bestimmten Bereich an seine Grenzen stoßen. Durch diese Erhöhung kann der Datendurchlauf gesteigert werden und eine einfachere Skalierung ist möglich. Fällt in einem „stateless“ Service eine Instanz aufgrund eines Fehlers aus, kann eine andere Instanz die Arbeit der ersten übernehmen und der Service-Call kann weitergeführt werden. (Josuttis 2007, 196)
„Stateful“ Services haben den großen Nachteil, dass sie meistens an Sessions gebunden sind. Diese sind abhängig von der jeweiligen KundIn und können daher nicht einfach ausgetauscht werden, d.h. KonsumentInnen sollten immer von der gleichen Service-Instanz bedient werden. Fällt diese eine Instanz aus, kann eine andere Instanz den Aufruf nicht einfach weiterverarbeiten, da der Aufruf an eine Session gebunden ist.
Josuttis spricht sich in seinen Ausführungen klar für den Einsatz von „stateless“ Services und den Verzicht auf „stateful“ Services aus. In der Praxis ist dies aber von der jeweiligen Applikation und der Aufgabe eines Service abhängig. Wird beispielsweise ein Online-Shop mit Warenkorb realisiert, spricht vieles für den Einsatz eines „stateful“ Services, wie beim obigen Beispiel bereits erklärt. Das Thema Performance spielt oft eine wichtige Rolle bei der Entwicklung einer Applikation. Beinhaltet eine solche Anwendung einen Service, welcher über eine lange Liste iteriert und Berechnungen durchführt, könnte auch ein „stateful“ Service zum Einsatz kommen. Der Grund dafür könnte sein, dass über diese Liste beispielsweise immer von oben nach unten iteriert werden muss. Fällt bei einer solchen neuen Iteration das System bei Listenplatz 10.000 aus, kann bei einem neuerlichen Aufruf der aktuelle Zustand des Services herangezogen werden und ab dem Listenplatz 10.001 die Durchführung wieder neu gestartet werden. Um hierbei die Richtigkeit der Funktionalität zu gewährleisten, könnte der Service seinen Zustand in einer Datenbank-Tabelle zwischenspeichern. (Josuttis 2007, 197)
Nach der Meinung des Autors überwiegen die Vorteile von zustandslosen Services gegenüber Services mit Zuständen. Ein endgültiges Urteil kann allerdings erst nach Analyse des jeweiligen Systems und den Aufgaben der verschiedenen Services gefällt werden. Dadurch kann für ein System der Einsatz von „stateless“ Services und für eine andere Architektur die Zuhilfenahme von „stateful“ Services besser sein. Eine Kombination beider Arten von Services ist auch möglich. Hierbei muss speziell auf die Kommunikation von Services untereinander geachtet werden.
2.2 Serviceorientierte Architektur
SOA ist ein Konzept für große, verteilte Systeme. Um SOA verstehen zu können, müssen zuerst die Eigenschaften von diesen Systemen erläutert werden:
Große, verteilte Systeme müssen (meistens) mit alten Hinterlassenschaften umgehen können. Dabei handelt es sich beispielsweise um Komponenten, welche über Input- und Output Datenformate verfügen, die nicht mehr den State-of-the-Art Formaten entsprechen oder mit alten Versionen von Programmiersprachen implementiert worden sind. Es können nicht immer alle einzelnen Bereiche großer Systeme technisch auf dem aktuellen Stand sein. Dies ist auch nicht notwendig, da sie teilweise nie mehr angepasst werden müssen und trotzdem ihre Arbeit problemlos und korrekt erledigen. SOA kann nicht einfach eingeführt werden, indem alle einzelnen Komponenten neu designet und neu implementiert werden. Aus diesem Grund sind in solchen großen Systemen immer alte Parts dabei. SOA in ein System zu etablieren ist somit nicht gleichzusetzen mit der Entwicklung eines neuen Projektes. Große, verteilte Systeme haben noch eine weitere wichtige Eigenschaft: Sie werden in ihrer langen Lebensdauer durch unterschiedliche EntwicklerInnen implementiert, gewartet, geändert, erweitert und aktualisiert. Dabei arbeiten mitunter verschiedene Teams und Unternehmen (Tochterunternehmen, Subunternehmen, etc.) über Jahre im Prinzip am gleichen System. Daraus können unterschiedliche Budgets, Interessen oder Ansichten resultieren. (Josuttis 2007, 4)
Mithilfe von SOA können solche großen, verteilten Systeme strukturiert werden. SOA implementiert dabei keine konkrete Struktur, sondern steht für das Konzept, ein großes System auf- und einzuteilen. SOA besteht aus einzelnen Services (vgl. 2.1), welche zusammen das komplette System abbilden. Die einzelnen Services sind dabei unabhängig voneinander und werden durch die Gesamtstruktur vereinheitlicht. In einem solchen System kann jeder Service (Ressource) bei Bedarf für jeden anderen Service über einen standardisierten Weg zugänglich gemacht werden. SOA kann außerdem mithilfe von jeder servicebasierten Technologie umgesetzt werden. In der Literatur wird hier oft das Beispiel Web Service über Representational State Transfer (REST) (vgl. 4.3) herangezogen. (Josuttis 2007, 12) Erl bestätigt in seinen Erläuterungen ebenfalls, dass jegliche Technologie für die Umsetzung von SOA zum Einsatz kommen kann. (Erl 2005, 30)
Josuttis schreibt in seinen Ausführungen, dass SOA keine konkrete Architektur ist. Er betrachtet SOA als eine Art Stil, Paradigma, Konzept, Perspektive, Philosophie oder Darstellung. SOA ist somit kein bestimmtes Framework, welches herangezogen werden kann. SOA ist eine Denkweise, ein Wertesystem, welches zu bestimmten konkreten Entscheidungen führt, wenn es um eine konkrete Software Architektur für ein bestimmtes Projekt oder eine bestimmte Anwendung geht.
Aus der Sichtweise von Josuttis lässt sich ableiten, dass SOA nicht gekauft werden kann. Es gibt kein bestimmtes Tool oder Rezept, welches sofort zum Einsatz kommen kann und für jede individuelle Applikation eine gute Lösung darstellt. Es muss für jede Anwendung eine eigenständige SOA Denkweise vollzogen werden, um gute Endergebnisse erzielen zu können. (Josuttis 2007, 12)
Windley zitiert in seinen Ausführungen Anne Thomas Manes, Vizepräsidentin der Burton Group, folgendermaßen:
“SOA is about behavior, not something you build or buy. You have to change behavior to make it effective.” (Windley 2006, 12)
Anne Thomas Manes verdeutlicht damit nochmals, dass SOA nicht gekauft werden kann. Es muss gelebt werden bzw. muss die Leistung dementsprechend angepasst werden, dass SOA effektiv zum Einsatz kommen kann.
SOA ist mittlerweile für viele ein vorbelastetes Wort geworden und impliziert für einige EntwicklerInnen sogleich den Einsatz von gewissen Technologien. (Dix 2011, 29) Laut Dix zählen zu diesen Technologien unter anderem folgende:
- Simple Object Access Protocol (SOAP)
- Web Services Description Language (WSDL)
- Extensible Markup Language Remote Procedure Call (XML-RPC)
SOAP ist ein leichtgewichtiges Protokoll für den Austausch von strukturierten Informationen in einer dezentralisierten, verteilten Umgebung. Es verwendet Extensible Markup Language (XML)-Technologien um ein erweiterbares Messaging-Framework zu definieren. Dieses Framework stellt Regeln für das Nachrichtendesign zur Verfügung. Es regelt, wie die Daten in einer Nachricht abzubilden sind und wie sie interpretiert werden sollen. SOAP gibt eine Konvention vor, wie entfernte Prozeduraufrufe über die Nachrichten zu erfolgen haben. Zwei große Design-Ziele für SOAP sind Einfachheit und Erweiterbarkeit. SOAP versucht diese Ziele durch Eliminieren von Features, die häufig ohnedies bereits in den verteilten Systemen selber vorkommen, zu erfüllen. (Gudgin u. a. 2007)
Das W3C Konsortium betitelt einen Service in der folgenden Beschreibung als „Endpunkt“.
WSDL ist ein XML-Format zur Beschreibung von Netzwerkdiensten als ein Set von Endpunkten. Diese Endpunkte operieren auf Nachrichten, die entweder dokumenten- oder verfahrensorientierte Informationen beinhalten. Die Operationen und Nachrichten werden abstrakt beschrieben und dann auf ein konkretes Netzwerkprotokoll und Nachrichtenformat gebunden, um einen Endpunkt zu definieren. Verwandte konkrete Endpunkte werden in abstrakte Endpunkte (Services) kombiniert. (Christensen u. a. 2001)
WSDL wird häufig in Kombination mit SOAP (vgl. 2.2) und dem XML Schema verwendet, um Services im Internet anzubieten. Ein Client, der einen Service aufruft, kann WSDL lesen, um zu bestimmen, welche Funktionen auf dem Server verfügbar sind. Alle verwendeten speziellen Datentypen sind in der WSDL-Datei in XML-Form eingebunden. Der Quellcode, der zum Zusammensetzen der gesendeten Objekte auf der Client-Seite notwendig ist, kann automatisiert aus der WSDL-Datei generiert werden. Der Client kann nun SOAP (vgl. 2.2) verwenden, um eine in WSDL gelistete Funktion letztendlich aufzurufen.
XML-RPC ist eine Definition zum Methoden-, oder auch Funktionsaufruf durch verteilte Systeme. Bei der Spezifikation wurde darauf Wert gelegt, dass eine Implementierung von XML-RPC ohne großen Aufwand in unterschiedlichen Programmiersprachen und auf unterschiedlichen Systemplattformen möglich ist. Aus diesem Grund wurden zur Realisierung zwei bereits vorhandene Standards miteinander verbunden. Um die Daten transportieren zu können, wurde auf Hypertext Transfer Protocol (HTTP) zurückgegriffen. Für die Darstellung der übertragenen Daten wird XML verwendet. Historisch gesehen stellt XML-RPC den Vorgänger zu SOAP (vgl. 2.2) dar, ist im Gegensatz zu diesem jedoch wesentlich schlanker und schneller zu verstehen.
Wie der Name „XML-RPC“ schon besagt, wurde es explizit für die Verwendung im Remote Procedure Call (RPC)-Stil entworfen. (Richardson und Ruby 2007, 21)
Der Autor teilt die Ansichtsweise von Dix. Um dies besser verständlich zu machen wird im folgenden Unterabschnitt auf den Begriff serviceorientiertes Design (SOD) (vgl. 2.3) eingegangen.
2.3 Serviceorientiertes Design
Bei SOD geht es darum, Systeme zu bauen, welche Funktionalitäten um logische Funktionen und Businesspraktiken gruppieren. Services sollten so gebaut werden, dass sie miteinander funktionieren, sprich fähig sind, miteinander zu arbeiten. Diese Services sollten in einem System wieder verwendet werden können und eventuell auch in anderen Systemen zum Einsatz kommen können. Ein Ziel von SOD ist es, eine Applikation in einzelne Komponenten aufzuteilen. An diesen Komponenten können EntwicklerInnen anschließend individuell, iterativ weiter entwickeln und neue Features dazu bauen oder beispielsweise Refactorings durchführen. Dabei muss nicht immer das „große Ganze“ angefasst, getestet und geändert werden, um nur eine einzige Komponente zu erweitern. Durch verschiedene Services wird einerseits die Komplexität des Ganzen herabgestuft, andererseits während der Entwicklung die Arbeit anfangs komplexer. Sobald alles eingespielt ist und die Grundstruktur steht, wird die Arbeit erleichtert. (Dix 2011, 27)
Große Ruby on Rails-Applikationen, die über eine Code-Base verfügen und somit an einem Stück sind (keine verschiedenen Komponenten, Services, etc.), werden mit der Zeit in die Enge entwickelt. Sie werden zu komplex, sind schwer zu deployen und beinhalten meistens viel Overhead. Große Applikationen müssen deshalb laut Dix in kleinere Segmente aufgeteilt werden. Diese Segmente bzw. Komponenten können anschließend einzeln weiterentwickelt, getestet und deployed werden.
Dix legt mit der Erstellung von einfachen und agilen Service-Ebenen, welche ohne große Generatoren, strikte Schemata oder alte Technologien auskommen können, ein weiteres Ziel von SOD fest. Dies folgt im Prinzip den Ansätzen des Ruby on Rails Frameworks „Convention over Configuration“, „Einfachheit vor Komplexität“.
Paul Dix kommt aus der Ruby Welt und ist ein Experte in der Ruby- und Rails-Entwicklung. Des Weiteren konnte er über die Jahre viel Erfahrung im Aufbau von großen Applikationen sammeln. Aus diesem Grund entnimmt der Autor aus den Ausführungen von Dix, dass SOD das „leichtgewichtige SOA“ (vgl. 2.2) für die Ruby-Welt ist.
SOD ist im Vergleich zu SOA somit für den Autor die konzeptionelle Ansicht des gesamten Forschungsgebiets. Da der Begriff SOA als anerkannter Fachbegriff gilt und auch wörtlich beschreibender klingt, hat der Autor den Titel dieser Masterarbeit bewusst mit „Serviceorientierte Architektur am Beispiel JAMES“ gewählt und auf den Begriff SOD im Titel verzichtet. SOD ist hier trotzdem angeführt, da auch die konzeptionelle Ansicht, nach der Meinung des Autors, wichtig ist. Die Vollständigkeit soll gewahrt werden.
2.4 Orchestrierung und Choreographie
In diesem Unterkapitel wird auf zwei weitere Grundlagen-Begriffe in Bezug auf SOA bzw. SOD eingegangen. Zuerst werden sie allgemein erklärt und anschließend erfolgt ein Vergleich mit einer Abgrenzung.
2.4.1 Service Orchestrierung
Das Wort „Orchestrierung“ kommt vom englischen „Orchestration“ und bedeutet soviel wie „Inszenierung“ oder „Instrumentierung“. Als Service Orchestrierung wird die flexible Kombination von mehreren verschiedenen Services zu einer Gesamtheit, einer sogenannten Komposition, bezeichnet. Ein Business-Ablauf muss in verschiedene Services aufgeteilt werden, welche organisiert und zusammengestellt werden müssen. Dieser Vorgang wird gemeinhin als Orchestrierung bezeichnet. Ein solcher Orchestrierungs-Prozess präsentiert verschiedene Services, welche effizient in einem Vorgang zusammengesetzt werden, um anschließend einen Geschäftsprozess auszuführen. (Karande, Karande und Meshram 2011, 225)
Abbildung 1: Service Orchestrierung: Beispiel für Orchestrierung-Prozess
Abbildung 1 beinhaltet ein Beispiel für einen Service Orchestrierungs-Prozess. In diesem Beispiel werden die involvierten Services von einem Controller-Service koordiniert. Dieser überwacht und bestimmt den Prozessfluss aller beteiligten Services. Diese vier Services bilden somit zusammen eine Einheit und könnten von Außen als nur ein einziger Service angesehen werden. (Karande, Karande und Meshram 2011, 225) Jeder einzelne hat dabei einen eingeschränkten Sichtbereich, einen sogenannten Scope, und kann nur für Prozesse innerhalb dieses Scopes Entscheidungen treffen. Der Sichtbereich des Controller-Services würde in diesem Beispiel sich selber und Service 1, Service 2 und Service 3 mit einbeziehen. Service 2 hat nur sich selber in seinem Scope. Die Sichtbereiche sind in der Abbildung durch die türkisen Umrandungen und Pfeile gekennzeichnet.
Dieses Beispiel könnte hierarchisch natürlich in ein komplexeres Konstrukt von Services eingegliedert sein. Für den einzelnen (Unter)-Service würde sich allerdings nichts weiter ändern.
Orchestrierung kann in serviceorientierten Umgebungen Business-Logik in standardisierter und servicebasierter Art und Weise repräsentieren bzw. ausführen. Wenn nun serviceorientierte Lösungen gebaut werden, entsteht dadurch eine attraktive Möglichkeit das Steuern und Ausführen der Prozesslogik zu automatisieren. (Erl 2005, 200)
Des Weiteren stellt Orchestrierung potentielle Integrations-Endpunkte in die Prozesse. Dies hilft den Services zusammenzuarbeiten und fördert damit die Interoperabilität von unterschiedlichen Services. Ein wichtiger Aspekt, wie sich Orchestrierung innerhalb von SOA-Systemen positioniert, ist die Tatsache, dass Orchestrierungen selber als Services existieren. (Erl 2005, 201)
Durch Service Orchestrierung kann Business-Logik abstrahiert werden. Dies bietet unter anderem folgende Vorteile: (Karande, Karande und Meshram 2011, 225)
- Anwendung- und Business-Services können frei gestaltet werden, um Prozess-agnostisch und wiederverwendbar zu sein, d.h. die einzelnen Anwendungen sind von einzelnen Prozessen unabhängig und können auch in anderen Bereichen ohne Veränderungen zum Einsatz kommen.
- Die Business-Prozess-Logik ist an einem Punkt zentralisiert, anstatt auf viele Services verteilt zu sein. Dies hat den Vorteil, dass ein zentraler Punkt angesprochen werden kann, um anschließend Aktionen zu starten.
Business-Prozess-Logik stellt die Wurzel von automatisierten Lösungen dar. Service Orchestrierung liefert ein automatisiertes Modell, welches die Prozess-Logik zwar zentralisiert, sie aber trotzdem noch erweiterbar und flexibel macht. Wird Service Orchestrierung in serviceorientierten Umgebungen eingesetzt, wird die Applikation von sich aus erweiter- und adaptierbar. (Erl 2005, 205)
Durch die vielen Vorteile, welche Service Orchestrierung für SOA bringt, ist laut Erl Orchestrierung das Herz von SOA geworden. (Erl 2005, 206)
2.4.2 Service Choreographie
In einer perfekten Welt wären alle Organisationen damit konform, wie interne Prozesse strukturiert sein sollten. Würde es zu einer Zusammenarbeit von verschiedenen Organisationen kommen, wäre die Interaktivität miteinander bereits von alleine gelöst und eine automatisierte Lösung bereits von Anfang an vorhanden. Dies ist in der Realität aber leider nicht möglich. Jede Organisation bzw. Applikation hat (normalerweise) ihre eigene Struktur und ist eigenständig implementiert worden. Dadurch werden Interaktionen zwischen Services einzelner, unterschiedlicher Applikationen zunehmend schwieriger und komplexer. Noch schwieriger wird es, wenn diese Interaktionen innerhalb einer Kooperation stattfinden und verschiedene Services von verschiedenen Applikationen zusammenarbeiten müssen, da ein gemeinsames Ziel erreicht werden muss. (Erl 2005, 208)
Während der Design-Zeit garantiert Service Choreographie, von einer globalen Perspektive aus gesehen, die Zusammenarbeitsfähigkeit zwischen einem Set von verschiedenen gleichrangigen Services, d.h. alle diese Services werden gleich behandelt. Ein Choreographie-Modell beschreibt dabei Kollaborationen und fokussiert sich auf den Nachrichtenaustausch. Ist beispielsweise ein Web Service in einer Choreographie involviert, so weiß dieser immer genau, wann er ausgeführt wird und mit welchem anderen Service er dabei interagiert. (Karande, Karande und Meshram 2011, 226)
Öffentlicher Nachrichtenaustausch, sprich Nachrichtenaustausch über ein internes System hinaus, ist somit eine wichtige Eigenschaft von Service Choreographie. Ziel von Service Choreographie ist es, eine Art „organisierte Zusammenarbeit“ von verschiedenen Services unterschiedlicher Systeme, zu schaffen. Dabei kontrolliert aber nicht ein System die gesamte Kollaboration, sondern diese Organisation findet über die Service Choreographie statt. Um dies umsetzen zu können, bieten Service Choreographien das Potential für den Aufbau von Interaktions-Mustern für die gemeinsame Nutzung und den gemeinsamen Austausch zwischen verschiedenen Organisationen. (Erl 2005, 209)
Innerhalb einer Service Choreographie hat ein Service verschiedene vordefinierte Rollen. Diese legen fest, welche Aufgaben ein Service übernimmt und was ein Service innerhalb eines bestimmten Business-Tasks ausführen darf. Sie können, je nach eingesetzter Technologie, beispielsweise an eine WSDL-Definition (vgl. 2.2) gekoppelt sein.
Jede einzelne Aktion, welche innerhalb einer Service Choreographie definiert ist, kann auf einen Nachrichtenaustausch zwischen zwei Services herunter gebrochen werden. Jeder Austausch ist somit eine Kommunikation zwischen zwei Services. Gemeinsam mit der dafür definierten Aktion werden sie als „Relationship“ bezeichnet. Die zwei Services stehen somit in einer durch die Service-Choreographie definierten Beziehung zueinander. Sie kommunizieren in einem so genannten „Channel“ miteinander. Dieser Kanal charakterisiert den Nachrichtenaustausch zwischen den zwei spezifischen Service-Rollen.
Zu guter Letzt ist die eigentliche Logik eines Nachrichtenaustausches zwischen zwei Services innerhalb einer so genannten „Interaction“ eingekapselt. Diese Interaktionen bilden Grundblöcke einer Service Choreographie, da sie die momentanen Fortschritte innerhalb einer Service Choreographie definieren. (Erl 2005, 210)
2.4.3 Abgrenzung Orchestrierung - Choreographie
Laut Karande, Karande und Meshram beschreibt Service Orchestrierung einen Prozessablauf zwischen Services aus der Perspektive einer TeilnehmerIn. Diese TeilnehmerIn ist beispielsweise eine Webapplikation, welche verschiedene Services umfasst und die komplette Kontrolle des Systems zentralisiert. Service Choreographie dagegen behandelt die Definition und Kontrolle von Nachrichtenaustausch in einem System, welches aus verschiedenen Applikationen mit unterschiedlichen Services besteht. Dabei findet die Kontrolle nicht zentralisiert, sondern dezentralisiert statt. Die Kontrolle haben somit alle gemeinsam, und nicht eine Applikation bzw. ein Service alleine. (Karande, Karande und Meshram 2011, 226)
Erls Ausführungen stimmen im Groben und Ganzen mit denen von Karande, Karande und Meshram überein. Er beschreibt beide als komplexe Muster bzw. Patterns für den Nachrichtenaustausch, welche sich voneinander unterscheiden. Service Orchestrierung drückt einen organisationsspezifischen Business Workflow aus, d.h. die Organisation bzw. Applikation kontrolliert die Logik hinter der Orchestrierung. Sollten Interaktionen mit externen Business PartnerInnen stattfinden und diese in das System involviert sein, so bleibt die Kontrolle trotzdem bei der eigentlichen Organisation und ist somit zentral an einem Punkt vereint. Bei der Service Choreographie ist die Kontrolle nicht auf eine Applikation zentralisiert, sondern auf mehrere verteilt. Service Choreographie tritt eher als Community-Austausch-Pattern auf und wird von kollaborativen Services und von verschiedenen Organisationen verwendet. (Erl 2005, 211)
2.5 Loose Coupling
Loose Coupling bedeutet soviel wie „lose Koppelung“. Dieses Konzept wird typischerweise dafür eingesetzt, um mit den Anforderungen an die Skalierbarkeit und Flexibilität einer Anwendung, sowie der Fehlertoleranz umgehen zu können. Laut Josuttis ist das Ziel von Loose Coupling die Minimierung von Abhängigkeiten einzelner Komponenten innerhalb einer SOA. Gibt es weniger Abhängigkeiten zwischen einzelnen Services, haben Veränderungen und Modifizierungen innerhalb eines Teilsystems auf andere Teile und Services deutlich weniger Konsequenzen. Ein flexibleres Arbeiten ist möglich. (Josuttis 2007, 36) Die Änderungen an einem Service haben, im besten Fall, auf diesen nur lokale Auswirkungen. Wäre die Koppelung nicht lose, sondern fest bzw. eng, würden einzelne Modifizierungen Auswirkungen auf das Gesamtsystem haben. Dadurch würden die Flexibilität und die Vorteile einer SOA wegfallen.
Loose Coupling ist weder ein Werkzeug noch eine Checkliste. Loose Coupling ist ein Prinzip von SOA. Wird eine SOA-Struktur entworfen und designt, liegt es, laut Josuttis’ Ausführungen, an den EntwicklerInnen, inwiefern Loose Coupling im System Platz findet und welche Gewichtung dafür festgelegt wird. Josuttis nennt einige typische Themen, welche beim Entwurf einer Architektur in Bezug auf Loose Coupling überlegt werden sollten. (Josuttis 2007, 36)
Tight Coupling | Loose Coupling | |
---|---|---|
Physikalische Verbindung | Punkt − zu − Punkt | Via Mediator |
Kontrolle von Prozesslogik | Zentrale Kontrolle | Verteilte Kontrolle |
Plattform | Starke Abhängigkeiten | Plattform unabhängig |
Deployment | Gleichzeitig | Zu verschiedenen Zeiten |
Tabelle 1 beinhaltet einen Auszug aus Josuttis’ Ausführungen. Es muss beachtet werden, dass diese Tabelle weit davon entfernt ist, vollständig zu sein. Die hier angeführten Möglichkeiten für Loose Coupling sind typisch für große, verteilte Systeme. EntwicklerInnen müssen allerdings aufpassen, dass diese Fakten nicht als allgemeine Checkliste gelten und nicht in jedem System passend Platz finden. Es gibt auch keinen Faktor, der besagt es müssen beispielsweise fünfzig Prozent der angeführten Möglichkeiten in einer guten SOA vorhanden sein. Dies ist gemäß Josuttis von Applikation zu Applikation individuell zu behandeln. Allerdings wäre es seltsam, wenn kein einziger Aspekt in einem Systementwurf vorkommen würde. (Josuttis 2007, 36) „Tight Coupling“ steht in Tabelle 1 für eine enge Koppelung. So wäre beispielsweise eine Architektur, bei der zwei Services direkt über ein physikalisches Kabel miteinander verbunden sind, nicht nach dem Loose Coupling Prinzip. Ein weiteres Beispiel aus Tabelle 1 wäre das Deployment. Kann jede einzelne Komponente bzw. jeder einzelne Service einer SOA einzeln deployed werden, so würde in diesem Fall von einer Loose Coupling Form gesprochen werden.
Loose Coupling hat nicht nur Vorteile, sondern es gibt auch negative Seiten dieses Prinzips. Alle Formen von Loose Coupling in einem System zu benutzen, macht die Architektur weit komplexer. Das bedeutet, es entsteht dadurch mehr Entwicklungsarbeit und Wartungsaufwand. (Josuttis 2007, 37)
Das hier vorgestellte Prinzip wird durch den Einsatz von Verträgen („Service contracts“) zwischen einzelnen Services implementiert. Diese Verträge erlauben verschiedenen Services über vordefinierte Parameter miteinander zu interagieren. Innerhalb einer lose gekoppelten Architektur koppeln interessanterweise Verträge Services eng an Operationen. Dadurch entstehen Operation-zu-Service Beziehungen. Die Services, welche an diesen Operationen hängen, verknüpfen sich dadurch auch mit anderen Services. Da die Verknüpfung aber über den Vertrag und nicht direkt stattfindet wird von loser Koppelung gesprochen. (Erl 2005, 297)
2.6 Serviceorientiertes Architektur-Manifest
Dieses Unterkapitel soll die Grundlagen und die Einführung in das Forschungsgebiet dieser Masterarbeit abschließen, um anschließend in weiteren Kapiteln und Unterabschnitten vertiefend in das Thema einsteigen zu können.
“Service orientation is a paradigm that frames what you do. Service-oriented architecture (SOA) is a type of architecture that results from applying service orientation. We have been applying service orientation to help organizations consistently deliver sustainable business value, with increased agility and cost effectiveness, in line with changing business needs.” (Arsanjani et al. 2013)
Dieses Zitat ist Bestandteil des SOA-Manifests, welches von namhaften Personen aus diesem Fachbereich unterschrieben wurde. Zu diesen Autoren des Manifests zählen unter anderem auch einige, welche in dieser Masterarbeit mit ihren Werken zitiert werden (Erl, Chappell, Josuttis, etc.).
Das SOA-Manifest legt einige Leitprinzipien fest, welche für Erl u.a. in Bezug auf SOA (vgl. 2.2) eingehalten werden sollten. Diese Masterarbeit beschäftigt sich mit dem Thema SOA bzw. SOD. Deshalb sind die Grundansichten des Manifests für diese Masterarbeit wichtig. Um den LeserInnen dieser Arbeit einen näheren Einblick in die Leitprinzipien geben zu können, werden sie in Folge kurz aufgezählt: (Arsanjani u. a. 2013)
- Respektiere die Sozial- und Machtstruktur der Organisation.
- Erkenne an, dass SOA letztlich Veränderung auf vielen Ebenen bedeutet.
- Der Bereich, in dem SOA eingeführt wird, kann unterschiedlich ausfallen. Halte die Aufwände in einem überschaubaren und sinnvollen Rahmen.
- Produkte und Standards alleine werden weder SOA liefern noch das serviceorientierte Paradigma umsetzen.
- SOA kann mit unterschiedlichen Technologien und Standards umgesetzt werden.
- Etabliere einheitliche Unternehmensstandards und -richtlinien auf der Basis von Industrie- und De-facto-Standards sowie Standards der SOA-Gemeinde.
- Strebe nach außen Einheitlichkeit an, aber lasse nach innen Vielfalt zu.
- Identifiziere Services durch Zusammenarbeit zwischen fachlichen und technischen InteressenvertreterInnen.
- Maximiere die Anwendbarkeit von Services durch Berücksichtigung der derzeitigen und zukünftigen Anwendungsgebiete.
- Stelle sicher, dass Services fachlichen Anforderungen und Zielen dienen.
- Services und deren Ausgestaltung sollten sich anhand der Art und Weise, wie sie wirklich genutzt werden, entwickeln.
- Trenne die verschiedenen Aspekte eines Systems, die sich unterschiedlich häufig ändern.
- Reduziere implizite Abhängigkeiten und publiziere alle externen Abhängigkeiten, um Robustheit zu fördern und die Auswirkungen von Veränderungen zu reduzieren.
- Organisiere jeden Service auf jeder Abstraktionsebene in oder anhand einer zusammenhängenden und überschaubaren Funktionseinheit.
Damit sind die Grundlagen und Prinzipien festgelegt und es kann ein Übergang zur tieferen Materie erfolgen. Das nächste Kapitel beschäftigt sich mit der Kommunikation von Services innerhalb einer SOA.
3. Services - Kommunikation
Das Kapitel „Services - Kommunikation“ beschäftigt sich damit, wie einzelne Services innerhalb eines SOA-Systems miteinander interagieren und kommunizieren können. Das Kapitel ist in die Unterkapitel „Event-basierte Kommunikation“ und „Methoden-basierte Kommunikation“ aufgeteilt. Es geht dabei darum, die unterschiedlichen Möglichkeiten der Kommunikation aufzuzeigen. In der Literatur verwenden AutorInnen oft unterschiedliche Bezeichnungen für die gleiche „Art“ der Kommunikation. Der Verfasser dieser Arbeit hat aus diesem Grund für die verschiedenen Untertypen jeweils versucht, mehrere Namen zusammen zu tragen. Dadurch soll LeserInnen, denen bereits Methoden unter bestimmten Bezeichnungen bekannt sind, entgegen gekommen werden.
Es gibt verschiedene Möglichkeiten Daten innerhalb eines verteilten Systems auszutauschen. Um mit Unterschieden zwischen Datenpaketen umgehen zu können, sollten Datenblöcke kategorisiert werden. Solche Datenblöcke werden im Fachjargon „Messages“ genannt. So genannte Message Exchange Pattern (MEP) entstehen durch Unterteilung und Kategorisierung der unterschiedlichen Möglichkeiten Daten auszutauschen. MEPs definieren die Sequenz von Messages innerhalb eines Service-Aufrufs oder einer Service Operation und spezifizieren dabei die Reihenfolge, Richtung und Mächtigkeit dieser Messages. (Josuttis 2007, 123) MEPs sind laut Josuttis ein generelles Konzept, um die Kommunikation zwischen verschiedenen Systemen zu beschreiben.
Nachfolgend werden einige solche Kategorisierungen vorgestellt. Die verschiedenen Kommunikationsarten können nicht nur innerhalb von SOA-Systemen, sondern auch in monolithischen Anwendungen vorkommen. In der Folge werden sie in Bezug auf SOA erläutert.
3.1 Methoden-basierte Kommunikation
Wird in einem Programm eine „HelloWorld“-Methode aufgerufen, gibt diese beispielsweise „Hello World“ als String zurück. Dieser String wird im Programm weiterverarbeitet. Die Methoden-basierte Kommunikation folgt diesem Prinzip und kann folgendermaßen aussehen:
- Request / Response - synchrone Kommunikation
- Request / Callback - asynchrone Kommunikation
Laut Josuttis ist Request / Response (Anfrage - Antwort) das wahrscheinlich wichtigste Entwurfsmuster in Bezug auf SOA. Bei diesem Pattern schickt der Client eine Anfrage-Message an den Service und wartet bis dieser eine Nachricht als Antwort schickt. Die Antwort-Message kann dabei beispielsweise die angeforderten Daten sowie eine Erfolgsbestätigung beinhalten. Aus der Sicht des Clients ist solch ein Service-Aufruf wie ein RPC-Aufruf zu verstehen, d.h. für den Client, dass er, bis die Antwort vom Service kommt, blockiert ist. (Josuttis 2007, 124) Aufgrund dieser Blockierung wird hier oft auch von synchroner Kommunikation gesprochen.
Um dies bildhafter zu erklären, könnte als Beispiel ein Telefongespräch herangezogen werden. Eine GesprächspartnerIn stellt eine Frage am Telefon und wartet bis die Person am anderen Ende der Leitung eine Antwort gibt. Bis dahin wird nichts von der fragenden GesprächspartnerIn gesprochen. Earl bestätigt Josuttis’ Ausführungen und beschreibt dieses Pattern ebenfalls als das populärste. (Erl 2005, 163)
Größter Vorteil dieser Variante Messages auszutauschen, ist die Einfachheit. Der dafür notwendige Programmcode kann einfach gehalten werden. Ein Service-Aufruf wird wie ein Funktionsaufruf behandelt. Wird etwas benötigt, wird die Anfrage durchgeführt, die Antwort abgewartet und anschließend weiter gearbeitet. (Josuttis 2007, 124)
Auf der anderen Seite hat diese Art Messages auszutauschen auch einen großen Nachteil. Die Zeit zwischen Anfrage (Request) und Antwort (Response) kann nicht für andere Programmabläufe genutzt werden. Der Prozess ist blockiert. Hier spielt der Zeitfaktor der entsprechenden Applikation bzw. Aktion eine große Rolle. Kommt beispielsweise nie eine Antwort vom Service, wäre das Programm für ewig gesperrt. Natürlich können ProgrammiererInnen Exception Handling oder Timeouts einsetzen, dies wiederum verkompliziert allerdings den Quellcode. (Josuttis 2007, 124f)
Oft benötigt ein Prozess bzw. Thread einige Daten oder eine Bestätigung. Er muss dafür aber nicht, wie beim Request / Response Pattern, zwingend blockiert werden bis ihn die benötigten Details erreichen. Ist dies der Fall, findet eine asynchrone Kommunikation statt. Laut Josuttis könnte diese Art zu kommunizieren auch als nonblocking Request / Response oder asynchrones Request / Response bezeichnet werden. In seinen Ausführungen nennt er es Request / Callback. Vielen LeserInnen wird diese Variante als asynchrone Kommunikation bekannt sein.
Für dieses Kommunikationsmuster werden oft so genannte „Callback“-Funktionen verwendet. Diese Funktionen werden aufgerufen, wenn die Antwort des Services den Client erreicht. Durch die Verwendung dieses Musters müssen ProgrammiererInnen asynchrone Programmcodes schreiben. Asynchron ist meist komplexer als synchron. (Josuttis 2007, 128) Hier ist erkennbar, dass sich im Prinzip die Vor- und Nachteile zum Request / Response Muster umkehren. Ein großer Vorteil wird allerdings durch dieses MEP erreicht. Es wird eine Form von Loose Coupling (vgl. 2.5) eingeführt. Ein Service muss beim Absenden der Client-Anfrage nicht verfügbar sein und kann die Arbeit erst später aufnehmen. Dies erleichtert zum Beispiel den Deploy des entsprechenden Services, was in der Folge einer Form von Loose Coupling entspricht.
Ein konkretes Beispiel für asynchrone Kommunikation ist die Authentifizierung über einen Oauth-Provider. Verwendet eine EntwicklerIn in einer Online-Applikation beispielsweise den Facebook-Login, so wird eine BenutzerIn nach Klicken eines entsprechenden Links, Buttons, etc. auf die Facebook-Startseite umgeleitet. Anschließend erfolgt der Login bei Facebook und die Weiterleitung auf einen definierten Uniform Resource Locator (URL) der Ursprungsseite. Bei dieser Weiterleitung schickt Facebook einen Code zur Authentifizierung mit. Der mitgeschickte Code wird in der Callback-Funktion, welche über die definierte URL aufgerufen wird, verarbeitet und die BenutzerIn wird, bei korrekten Daten, eingeloggt. Die Anfrage (Request) ist somit nicht direkt von der Antwort abhängig und die Seite wird nicht blockiert.
3.2 Event-basierte Kommunikation
Die Event-basierte Kommunikation oder die Event-gesteuerte Architektur stützt sich auf Events innerhalb einer SOA. Das Event-System ist ein Software Architektursystem, welches den Nachrichtenaustausch über Events gestaltet. Events sind typischerweise Benachrichtigungen von signifikanten Änderungen, welche von anderen Services dringend benötigt werden. Die Services, welche auf die Notifications angewiesen sind, müssen dementsprechend auf die neuen Änderungen reagieren können und deshalb benachrichtigt werden. (Josuttis 2007, 134)
Durch den Einsatz von Events als Message-Typ kann eine SOA in eine Prozesskette gegliedert werden. Beispielsweise holt sich Service 1 neue Daten aus dem Internet über eine RSS-Quelle. Hat er die benötigten Daten gesammelt, benachrichtigt Service 1 per Event Service 2. Service 2 nimmt die neuen Daten von Service 1 entgegen und verarbeitet sie weiter. Anschließend wird von Service 2 ein Event getriggert, welches Service 3 benachrichtigt. Service 3 ruft die Daten ab und stellt sie auf einer Webapplikation BenutzerInnen zur Verfügung. Durch diese Kette und die Events weiß jeder Service immer, wann es etwas zu tun gibt und geht dann seiner Arbeit nach.
Das Event-System ist eine weitere Möglichkeit, die Kommunikation von Services innerhalb einer SOA-Applikation zu gestalten. In gewisser Form kommt dadurch auch Loose Coupling zum Einsatz. Die einzelnen Services sind unabhängig voneinander. Um auf das vorige Beispiel zurückzukommen: Service 1 tangiert es nicht, ob Service 2 benachrichtigt wird oder auch noch Service 2b, welcher auch Daten verarbeitet. Die Flexibilität ist durch diese Kommunikationsvariante gewährleistet. Einziges Problem könnte sein, dass zu viele Services benachrichtigt werden, die nichts mit dem Event anfangen können. Dieses Problem zu lösen liegt im Aufgabenbereich der EntwicklerInnen.
Laut Josuttis zählen folgende Kommunikationsarten zu Event-basierten Systemen:
- One-Way - Fire and forget
- Single-Destination-Pattern
- Multi-Cast-Pattern
- Broadcast-Pattern
- Publish / Subscribe - PubSub
- Bus / Service-Bus
Das One-Way-Entwurfsmuster kommt zum Einsatz, falls keine Antwort vom Server benötigt wird. Vielen LeserInnen wird diese Variante als „fire and forget“ bekannt sein. Dabei sendet ein Client eine Anfrage an einen Server (können auch beides einzelne Services sein) und hat seine Arbeit damit auch gleich wieder beendet. Der Absender wartet nicht auf die Antwort des Servers. (Josuttis 2007, 125)
Earl spezifiziert das One-Way-Entwurfsmuster in seinen Ausführungen und unterteilt es genauer. Diese Spezifizierung mündet in der Aufteilung des Patterns in verschiedene Untermuster. Zu diesen Untermustern zählen: (Erl 2005, 165)
Bei diesem Muster sendet der Client eine Nachricht an nur exakt einen Service.
Hierbei sendet der Client eine Nachricht an ein vordefiniertes Set von EmpfängerInnen (Services). Eine solche Gruppe kann aus zwei bis mehreren hundert Services bestehen. Dies hängt von der jeweiligen Applikation ab. Das Set muss allerdings vorher definiert sein.
Dieses Pattern verhält sich ähnlich wie das Multi-Cast-Pattern. Einziger und entscheidender Unterschied ist, dass dabei nicht an ein vordefiniertes Set gesendet wird, sondern alle Services als EmpfängerInnen dienen.
Dieses Entwurfsmuster entspricht dem umgekehrten One-Way-Entwurfsmuster, d.h. es sendet nicht ein Client eine Anfrage an den Service, sondern ein Service sendet von sich aus eine Nachricht an andere Services oder Clients. In SOA-Infrastrukturen wird hier oft von „Notifications“ gesprochen. (Josuttis 2007, 129) Dieses Muster wird gerne mit „PubSub“ abgekürzt.
Als konkretes Beispiel für Publish / Subscribe kann folgendes aufgezeigt werden: Ein Service kalkuliert Wetterdaten neu und erstellt dafür Charts. Ein anderer Service stellt diese Charts BenutzerInnen auf einer Weboberfläche zur Verfügung. Ist der erste Service mit seiner Aufgabe fertig und neue Charts sind verfügbar, informiert der erste Service den zweiten darüber, dass neue Charts abrufbar sind.
Ein weiteres Beispiel, welches den meisten LeserInnen geläufig sein sollte, ist das Newsletter-System. BenutzerInnen melden sich für einen Newsletter an und erhalten diesen bei Neuerscheinung per E-Mail zugesandt. Dies ist ein praktisches Beispiel außerhalb einer SOA, welches verdeutlichen soll, dass diese Kommunikationsarten nicht nur innerhalb von SOA-Systemen verwendet werden können.
Earl meint, dass Services dadurch neue Rollen bekommen. Es gibt „Publisher“ und „Subscriber“. Die „Publisher“ stellen „Etwas“ zur Verfügung und die „Subscriber“ sind an diesem „Etwas“ interessiert. (Erl 2005, 166)
Den meisten LeserInnen wird in Bezug auf dieses Entwurfsmuster der Enterprise Service Bus (ESB) (vgl. 4.2) bekannt sein. Diese Technologie wird im nächsten Kapitel erläutert, dieser Abschnitt soll als allgemeine Erklärung dienen, Details folgen im besagten Unterkapitel „ESB“ (vgl. 4.2). Bus-Systeme können auch unabhängig vom ESB zum Einsatz kommen.
Mit „Bus“ wird in der allgemeinen Daten- und Elektrotechnik ein Untersystem bezeichnet. Ein solches Untersystem überträgt Daten oder Energie zwischen Teilen des Komplettsystems. Im Falle dieses Kommunikationsentwurfsmusters wird über den Bus ein zentraler Kommunikationskanal definiert. Dieser verbindet die einzelnen Komponenten miteinander. Innerhalb einer SOA können einzelne Services über diesen Bus bzw. Service-Bus kommunizieren. Dabei kann die Kommunikation zwischen einzelnen oder mehreren Services stattfinden. Der zentrale Kommunikationskanal ist für alle Services zugänglich und kann jederzeit genutzt werden. Services können jederzeit in den Bus „einsteigen“ oder „aussteigen“. Die Kommunikation findet allerdings in jedem Fall über den zentralen Kommunikationskanal statt.
Diese erläuterten Kommunikationsarten (Methoden- sowie Event-basiert) können beispielsweise über Web Services mit REST (vgl. 4.3), SOAP (vgl. Services - SOAP 2.2) oder andere Technologien realisiert werden. Einige dieser Technologien werden in Folge noch detaillierter vorgestellt.
4. Kommunikation - Technologien
Das Kapitel „Kommunikation - Technologien“ beschäftigt sich mit Möglichkeiten, wie ein SOA-System entwickelt werden kann. Diese Masterarbeit sucht nach einer Lösung für eine SOA-Implementierung für die Programmiersprache Ruby. In den Unterkapiteln „Common Object Request Broker Architecture (CORBA)“ (vgl. 4.1), „Enterprise Service Bus“ (vgl. 4.2) und „Web Services mit REST“ (vgl. 4.3) werden generelle Entwicklungsideen bzw. Technologien vorgestellt, wie die Kommunikation in einem SOA-Framework umgesetzt werden kann.
4.1 Common Object Request Broker Architecture
CORBA ist laut Heinzl ein Standard der Object Management Group (OMG). (Heinzl 2005, 197) Henning sagt in einem Paper aus dem Jahre 2008, dass CORBA ca. fünfzehn Jahre alt ist. (Henning 2008, 53) Somit ist es derzeit (14.06.2013) ca. zwanzig Jahre alt und nach der Meinung des Autors kann daher aufgrund der Schnelllebigkeit der heutigen IT-Welt von einer alten Technologie gesprochen werden. Henning erklärt den historischen Ablauf der Entwicklung von CORBA wie folgt:
“During its lifetime, CORBA has moved from being a bleeding- edge technology for early adopters, to being a popular middleware, to being a niche technology that exists in relative obscurity.” (Henning 2008, 53)
Für den Autor ist aus diesem wörtlichen Zitat von Henning zu entnehmen, dass CORBA in früheren Zeiten populärer gewesen ist als heute (14.06.2013). Der Vollständigkeit halber findet diese Technologie trotzdem Platz in dieser Arbeit.
Die Computertechnik hat sich laut Neubauer u.a. in den letzten Jahren rasant entwickelt. So wurden anfangs ausschließlich Rechner einzeln und unabhängig von anderen Rechnern verwendet, um Programmieraufgaben zu erledigen. Mittlerweile dominieren vernetzte Rechnerkonfigurationen, bei denen mehrere Rechner gemeinsam an einer Gesamtlösung arbeiten. Neubauer u.a. führen des Weiteren aus, dass sich hier zwei unterschiedliche Arten von Rechnerzusammenschlüssen erkennen lassen. Zum Einen werden homogene und zum Anderen heterogene Verbünde installiert. Bei homogenen Verbünden arbeiten meist baugleiche Rechner über die selbe Netzwerktechnologie zusammen. Heterogene Netzwerke beinhalten Rechner mit unterschiedlichen Architekturen, Betriebssystemen und Netzwerktechnologien. Hierbei ist die Kommunikation zwischen den einzelnen Computern deutlich schwieriger, als bei einem homogenen Zusammenschluss einzelner Maschinen. (Neubauer, Ritter und Stoinski 2004, 3f) CORBA ermöglicht diese Kommunikation.
Objektorientierte Programmiersprachen ermöglichen eine klare Trennung von Daten und Zugriffsschnittstelle. (Neubauer, Ritter und Stoinski 2004, 4) Dadurch kann beispielsweise bei Webanwendungen die Applikation in client- und serverseitig aufgeteilt werden. Um diese Aufteilung leichter realisieren zu können bzw. die nachfolgende Kommunikation zu erleichtern, kombiniert CORBA das Client-Server-Modell mit den Konzepten der objektorientierten Programmierung. CORBA ermöglicht dabei eine von jeglicher Plattform unabhängige Entwicklung von Software. (Neubauer, Ritter und Stoinski 2004, 5)
Die Struktur von CORBA wird durch das CORBA-Referenzmodell verdeutlicht. Dieses Modell besteht aus folgenden Komponenten:
- Object Request Broker (ORB)
- Object Services
- Naming Service
- Interface Repository
- Life Cycle Service
- Common Facilities
- Application Objects
Der ORB ist die zentrale Komponente für die Kommunikation von verteilten Objekten. Wichtig ist dabei, dass auch diese zentrale Komponente mit all ihren Unterkomponenten auf verteilten Systemen laufen kann. (Heinzl 2005, 197) Der ORB ist für die Codierung und Decodierung von Netzwerknachrichten zur Übermittlung von Operationsaufrufen und Operationsantworten verantwortlich. Er stellt grundlegende unterstützende Laufzeitdienste für CORBA-Objekte bereit. (Neubauer, Ritter und Stoinski 2004, 6)
Die Object Services stellen eine Sammlung von Diensten, welche gut für die Realisierung von verteilten Anwendungen herangezogen werden können, dar. Heinzl zählt unter anderem folgende zu solchen Diensten: (Heinzl 2005, 197f)
Dieser Service dient Clients dazu herauszufinden, an welchen Rechner in einem verteilten System sie ihre Anfragen senden können.
Das Interface Repository ist eine Art „Pool“ von Objekt-Interfaces. Die Interfaces werden in diesem „Pool“ hinterlegt, um zur Programmlaufzeit einen dynamischen Zugriff auf die entsprechenden Objekte zu ermöglichen.
Dieser Service dient dazu, Konventionen zu definieren. Solche Konventionen können beispielsweise Erzeug-, Kopier-, Lösch- oder Verschiebe-Operationen von Objekten festlegen.
Unter Common Facilities sind verschiedene Dienstleistungen zusammengefasst. Diese können laut Heinzl für unterschiedliche Anwendungen herangezogen werden. Allerdings ist hier der große Unterschied zum vorig bereits angesprochenen Punkt „Object Services“, dass die unter Common Facilities zusammengefassten Dienstleistungen deutlich weniger wichtig sind. Es existieren zum Beispiel Spezifikationen für Internationalisierung und Zeitzonen. (Heinzl 2005, 198)
Für die Application Objects gibt es laut Heinzl keine Spezifikation. Diese Objekte sind für die eigentliche Funktionalität verantwortlich, die verteilt angeboten wird. Die jeweilige ProgrammiererIn der Applikation ist für deren Entwurf verantwortlich. (Heinzl 2005, 198)
Abbildung 2: Common Object Request Broker Architecture: Referenzmodell
Abbildung 2 beinhaltet die erklärten Punkte des Referenzmodells in grafischer Form. Der bereits angesprochene ORB bildet in der Mitte der Abbildung die zentrale Komponente des Modells. Rundherum angeordnet befinden sich die anderen Komponenten.
In der CORBA-Technologie wird über den ORB kommuniziert. Dabei wendet sich ein Client, der einen Dienst von einem entfernten Objekt anfordern möchte, direkt an den ORB. Dieser wiederum ermittelt, wo sich das entfernte Objekt befindet und leitet die Anfrage entsprechend weiter. (Heinzl 2005, 198) Der Client muss somit nicht direkt wissen, wo das gewünschte Objekt aufzufinden ist, was einen großen Vorteil darstellt. Allerdings muss er wissen, wie der ORB gefunden und angesprochen werden kann. (Heinzl 2005, 199) Die Kommunikationswege innerhalb des Systems werden mithilfe der Pfeile in der Abbildung verdeutlicht.
Eines der Grundprinzipien bei der Entwicklung von Anwendungen mithilfe von CORBA ist laut Neubauer u.a. die Unabhängigkeit von jeglicher Programmiersprache. So müssen EntwicklerInnen bei der Implementierung von Clients und der Realisierung eines Dienstes (Server) nicht auf die gleiche Programmiersprache setzen. Der Client könnte beispielsweise mit Ruby und Hypertext Markup Language (HTML) entwickelt werden, und der Server könnte eine Java-Anwendung sein. In CORBA wurde dafür die sogenannte Interface Definition Language (IDL) entwickelt. Die IDL ist keine Programmiersprache, sondern eine Sprache zur Beschreibung von Objektschnittstellen, Datentypen und Operationen. Zentrales Konzept der IDL ist das CORBA-Interface. Dieses Interface definiert die Schnittstelle eines CORBA-Objekts. Die IDL ermöglicht damit die Programmiersprachen-unabhängige Beschreibung von Objektinterfaces. Ein CORBA-Interface stellt einen Vertrag zwischen CORBA-Objekt und Client über deren Nutzung dar. Das CORBA-Interface beinhaltet gruppierte Attribute und Operationen. Die Attribute stellen dabei konfigurierbare Werte eines CORBA-Objekt dar. Die im Interface definierten Operationen, können von Clients aufgerufen werden und müssen vom CORBA-Objekt selber implementiert sein. (Neubauer, Ritter und Stoinski 2004, 10)
Laut Henning wird CORBA heutzutage hauptsächlich dazu verwendet, Komponenten miteinander zu verknüpfen, die sich innerhalb eines Firmennetzwerks befinden. Die Kommunikation ist dabei durch eine Firewall von der Außenwelt geschützt. CORBA kommt ebenfalls für Echtzeitanwendungen, sowie Embedded-System-Entwicklung zum Einsatz. CORBA ist mittlerweile, nach den Ausführungen von Henning, eine Nischentechnologie geworden. (Henning 2008, 55)
Um das Augenmerk auf eine aktuellere Technologie zur Umsetzung der Kommunikation zu setzen, folgt hiermit der Übergang zum nächsten Unterkapitel „Enterprise Service Bus“ (vgl. 4.2).
4.2 Enterprise Service Bus
Der Enterprise Service Bus (ESB) ist laut Daigneau ein typisches prominentes Infrastruktur-Entwurfsmuster von SOA, um die Kommunikation bewerkstelligen zu können. (Daigneau 2012, 221) Ein ESB vernetzt, gemäß Starkes Ausführungen, sämtliche technische Beteiligten einer SOA-Implementierung miteinander. Möchte ein Client (jemand der den Service benutzt) mit dem Server (AnbieterIn des Services) zusammenkommen und in Kontakt treten, werden die Kommunikationsdetails durch den ESB übernommen. Auf den ersten Blick ähnelt dieser Ansatz stark dem ORB der vorig erläuterten CORBA- Technologie, allerdings verfügt er über einige zusätzliche Facetten. (Starke 2011, 319) Zu diesen zählen:
- Es wird eine technische Verbindung aller Komponenten veranlasst. Die dafür notwendigen Netzwerk- und Protokolldetails werden vom ESB ebenfalls übernommen.
- Ein Service-Bus muss sogenannte Brücken zwischen heterogenen (nicht genau gleichen) Technologien bilden können. Beispielsweise ist ein Client möglicherweise in einer anderen Programmiersprache entwickelt worden, als ein Server, der den Service zur Verfügung stellt.
- Es findet eine Kapselung von verschiedenen Kommunikationskonzepten statt. Dies ermöglicht, dass der ESB beispielsweise synchrone und asynchrone Komponenten miteinander kommunizieren lassen kann. Ein weiteres Beispiel ist die Übersetzung zwischen unterschiedlichen Protokollen.
- Zu den zusätzlichen Facetten zählt auch die Bereitstellung von technischen Diensten durch den ESB. Logging oder eine Autorisierung innerhalb eines SOA-Systems können hier als Beispiele genannt werden.
Daigneau bestätigt Starkes Ansichten über die Facetten und formuliert diese als „Nachrichten Routing“, „Nachricht Übersetzung“, und „Protokoll Übersetzung und Transport Mapping“. Laut Daigneau ist eine Hauptfunktion vom ESB das Routing der Nachrichten innerhalb eines Systems. Dabei geht es darum, Anfragen von Clients an die passenden Services weiterzuleiten und die Antworten der jeweiligen Services zurück an den Client zu liefern. Das Augenmerk liegt dabei darauf, Clients die Opportunität zu geben, Services aufzurufen. Die Abhängigkeit zwischen dem Client und der bestimmten Service-Implementierung soll dadurch minimiert werden.
Der ESB stellt eine Schicht zur Verfügung, die es Services ermöglicht, hinzugefügt, upgegraded, ersetzt oder entfernt zu werden. Während dieser Vorgänge wird dabei die Wirkung auf Client Applikationen minimiert. Dies ist durch den bereits von Starke angesprochenen Service-Bus möglich. Die Clients senden ihre Anfragen an den Bus und kommunizieren nicht direkt mit den Services, d.h. die Abhängigkeit der Clients wandert vom jeweiligen Service zum entsprechenden Bus. Clients senden regelmäßig Nachrichten durch sogenannte Virtual Services. Diese erscheinen für den Client wie die tatsächlichen Ziel-Services, bilden aber nur die Kommunikationsschnittstelle über einen definierten Endpunkt. (Daigneau 2012, 222)
Chappell fügt zu den Erläuterungen von Starke und Daigneau hinzu, dass der ESB eine auf Standards basierende Integrationsplattform ist, welche den Nachrichtenaustausch, Web Services, Daten-Transformationen (Nachrichten Übersetzung) und ein intelligentes Routing-System miteinander verbindet und dabei die Interaktion einer signifikanten Anzahl an verschiedenen Applikationen im Enterprise Bereich unterstützt. Den Enterprise Bereich nennt Chappell wörtlich „extended enterprise“. Unter diesem Begriff versteht er beispielsweise ein Unternehmen mitsamt seinen WirtschaftspartnerInnen. Die einzelnen Unternehmen sind dabei sowohl kaufmännisch als auch physikalisch unabhängig voneinander. (Chappell 2004, 1) Das führt dazu, dass die einzelnen Applikationen der Unternehmen voneinander getrennt sind, aber mitunter trotzdem kommunizieren können müssen.
Das Konzept des ESB überträgt den Ansatz der Verteilung auf Busse (vgl. Event-basierte Kommunikation 3.2 - Service-Bus) auf das Gebiet einer unternehmensweiten IT-Architektur. Dabei wird die normalerweise komplizierte Struktur eines Netzes aus direkten Koppelungen durch eine Kommunikationsinfrastruktur ersetzt. Diese Kommunikationsinfrastruktur wird anschließend von allen gemeinsamen Clients und Servern benutzt. Ein ESB besteht im Kern folglich aus einem Kommunikationsbus, über den Nachrichten ausgetauscht werden können. Services verbinden ihre Schnittstellen über definierte Endpunkte mit dem Bus. Clients kommunizieren mit den Services über den Austausch von Nachrichten über den Bus.
Abbildung 3: Enterprise Service Bus: Beispiel Lagerverwaltung
Quelle: http://jaxenter.de/artikel/Enterprise-Service-Bus
Abbildung 3 beinhaltet ein grafisches Beispiel, wie ein ESB-System aussehen könnte. Dieses Beispiel soll verdeutlichen, dass unterschiedlichste Programmiersprachen und Protokolle zum Einsatz kommen und diese trotzdem auch zusammen funktionieren können. So können PartnerInnen-Applikationen, wie Chappell sagen würde über das „extended enterprise“, genauso mit einem internen System kommunizieren wie eine Mailsystem-Applikation im selben Unternehmen.
Das folgende praktische Beispiel soll zum Verständnis der soeben genannten theoretischen Ausführungen dienen: Die Lagerverwaltung einer Werkstatt muss neue Bestellungen tätigen und für diese E-Mails an LieferantInnen versenden. Eine VerwalterIn loggt sich in die Online-Applikation der Lagerverwaltung ein und prüft die Bestände. Sie erkennt, dass neue Reifen für die Werkstatt bestellt werden müssen. Die Bestellung erfolgt über das interne Mailsystem. Sie öffnet ein Client-Programm und schreibt ihre E-Mails, welche anschließend verschickt werden. Im Hintergrund spricht beim Öffnen des Mailsystems die Lagerverwaltung über Java Message Service (JMS) mit dem Service-Bus des ESB. Der ESB übersetzt das JMS-Format in ein passendes Datenformat und gibt die Anfrage an das Mailsystem weiter. Dieses nimmt über Simple Mail Transfer Protocol (SMTP) die Anfrage auf, verarbeitet sie und gibt eine Antwort an den Service-Bus zurück. Der Bus leitet diese Antwort an die Lagerverwaltung weiter. Auf der Client-Applikation der Lagerverwaltung für E-Mails erscheint für die VerwalterIn eine Meldung wie „Bestellungen erfolgreich getätigt.“ Der Prozess ist abgeschlossen und die Kommunikation beendet.
An diesem Beispiel ist die Rolle des ESB klar zu erkennen. Er definiert die Kommunikationsstruktur innerhalb eines Komplettsystems und stellt den Kern dar. Implementiert könnte der ESB wiederum in den verschiedensten Programmiersprachen sein, für die „angedockten“ Services und Clients spielt dies keine Rolle.
4.3 Web Services mit REST
In diesem Unterkapitel wird Generelles über das Thema „Web Services mit REST“ erklärt. Die LeserIn soll dadurch bereits mit dieser Methodik vertraut gemacht werden. In den nachfolgend vorgestellten Frameworks und Tools (vgl. 5) wird teilweise auf das REST-Prinzip (beispielsweise „Ruby on Rails“ (vgl. 5.1)) gesetzt.
Ein Web Service definiert sich laut dem W3C Standard wie folgt:
“A Web service is a software system designed to support interoperable machine-to-machine interaction over a network. It has an interface described in a machine-processable format (specifically WSDL). Other systems interact with the Web service in a manner prescribed by its description using SOAP messages, typically conveyed using HTTP with an XML serialization in conjunction with other Web-related standards.” (Boot et al. 2004)
D.h. ein Web Service ist ein Service, welcher online verfügbar ist und zum Beispiel über HTTP angesprochen werden kann. Das zur Verfügung gestellte Interface ist in einem von Maschinen lesbaren Format gehalten. Das W3C spricht hier im Speziellen von WSDL (vgl. 2.2). Das Interface eines Web Services lässt sich auch durch REST definieren. Um dies besser verstehen zu können, muss zuerst der Begriff „REST“ näher spezifiziert und erläutert werden:
- REST
- Client-Server
- Stateless (Zustandslosigkeit)
- Cache
- Uniform Interface
- Layered System
- Code-On-Demand
Der Begriff REST bezeichnet einen Programmierstil für Webanwendungen. Das Konzept hinter REST sowie die Begriffsdefinition stammt aus der Dissertation von Roy Thomas Fielding aus dem Jahre 2000. (Fielding 2000) Laut Fielding soll das REST-Paradigma ein Modell darstellen, das das moderne Web repräsentiert. (Fielding 2000, 75) REST ist die Grundarchitektur des World Wide Webs. Werden die REST-Prinzipien bei der Implementierung einer Webapplikation eingehalten, kann sich die Applikation in die bereits vorhandene Umgebung gut integrieren. Mit vorhandener Umgebung ist die Hardware (Server, etc.) sowie die Software (Browser, Caches, etc.) gemeint. Um dies besser verstehen zu können, wird in Folge auf die REST-Prinzipien näher eingegangen.
Laut Fielding sollte die Client-Server Architektur voneinander getrennt sein. Durch die Trennung von User Interface und Datenspeicher wird die Übertragbarkeit des User Interfaces von der einen auf die andere Anwendung verbessert. Des Weiteren wird die Skalierbarkeit des Backends aufgrund der getrennten Server Komponente erleichtert. Besonders hervorzuheben ist im Web-Bereich, dass durch diese Trennung der beiden Bereiche (Client-Server) die jeweilige Komponente unabhängig von einer anderen entwickelt werden kann. Dadurch steigert sich klarerweise die Flexibilität und ProgrammiererInnen haben mehrere Möglichkeiten einzelne Komponenten weiter zu entwickeln. (Fielding 2000, 78)
Ein zweites Prinzip bzw. Bedingung für REST ist die Zustandslosigkeit. Dies bedeutet, dass bei der Kommunikation von Client-Server keine Zustände am Server gespeichert werden müssen. Die benötigte Information, um die Anfrage des Clients korrekt ausführen zu können, muss immer komplett übertragen werden. Der gesamte Session-Status wird dafür im Client gespeichert. Diese Bedingung liefert die Eigenschaften der Transparenz, Verlässlichkeit und Skalierbarkeit. Unter Transparenz wird dabei verstanden, dass die einzelnen Anfragen für ein Monitoring-System beispielsweise sichtbarer werden. Dieses System kann leichter den kompletten Verlauf eines Requests überblicken, da eine Anfrage alle Informationen enthält, die notwendig sind. Das Programm bzw. die Webanwendung wird verlässlicher, da einzelne Fehler leichter auffindbar sind. Die Skalierbarkeit wird dadurch verbessert, dass einzelne Anfragen nicht von gespeicherten Session-Daten am Server abhängig sind. Der Server kann leichter Ressourcen freigeben, er muss keine Daten über verschiedene Anfragen managen.
Dieses REST-Prinzip hat nicht nur Vorteile, sondern bringt auch einige Nachteile mit sich. Dazu zählt zum Beispiel die Kontrolle über die Session. Liegt diese komplett beim Client, könnte dieser sie manipulieren. Performance-Verlust könnte bei einigen Applikationen ein weiterer Nachteil sein. Da immer die gesamte Information bei einer Anfrage übertragen werden muss, entsteht mitunter ein unnötiger Overhead. (Fielding 2000, 78f)
Fielding fügt in seinen Ausführungen das „Cache“-Prinzip hinzu, um die Netzwerkeffizienz zu verbessern. Jede Antwort des Servers auf eine Client-Anfrage wird implizit oder explizit mit „cacheable“ oder „non-cacheable“ gekennzeichnet. Ist eine Response „cacheable“, wird dem Client die Möglichkeit und das Recht gegeben, die Antwort zwischenzuspeichern und für spätere, gleiche bzw. ähnliche Anfragen wieder zu verwenden. Der Vorteil von Caching ist dabei klar erkennbar. Durch Caching können gewisse Client-Server-Interaktionen komplett umgangen werden und es ist nicht mehr nötig, diese überhaupt auszuführen. Die Effizienz des Netzwerks, die Skalierbarkeit und die, für die jeweilige BenutzerIn, wahrgenommene Performance der Applikation, steigert sich. Caching hat das Problem, dass die Zuverlässigkeit die aktuellsten Daten zu erhalten, deutlich sinken kann. Sollten beispielsweise veraltete Daten im Cache liegen und dieser nicht ablaufen, könnten alte Daten an Clients ausgeliefert werden. (Fielding 2000, 79f)
Uniform Interface ist das zentrale Merkmal, welches REST von anderen netzwerkbasierten Designs unterscheidet. Mit Uniform Interface ist die einheitliche Schnittstelle zwischen den Komponenten gemeint. Durch diese Schnittstelle wird die Systemarchitektur vereinfacht und die Sichtbarkeit von Interaktionen verbessert. Implementierungen werden von den Services, welche sie anbieten, entkoppelt. Dadurch entsteht Unabhängigkeit. Dieses Uniform Interface verringert jedoch die Effizienz, da die Information in standardisierter Form übertragen und nicht passend genau auf die spezielle Applikation zugeschnitten wird. Das REST-Interface ist so designet, dass es für große Datenübertragungen effizient zum Einsatz kommen kann - optimiert für das Web. Für andere architektonische Interaktionen ist das nicht optimal. Das Uniform Interface ist durch vier Interface-Bedingungen definiert. Dazu zählen Ressourcen-Identifikation (z.B. jede Ressource ist über eine spezielle Uniform Resource Identifier (URI) abrufbar), Manipulationen von Ressourcen durch unterschiedliche Darstellungen (beispielsweise im HTML- und XML-Format) und selbst beschreibende Nachrichten. (Fielding 2000, 81f)
Fielding führt mit Layered System ein weiteres REST-Prinzip ein. Dieses Prinzip erlaubt es, das Verhalten für Internet-Skalierung-Anforderungen weiter zu verbessern. Eine layered System-Architektur umfasst verschiedene hierarchische Ebenen. Diese Ebenen können nur die Ebenen sehen, mit denen sie interagieren. Die restlichen Ebenen des Komplettsystems sind nicht sichtbar. Ebenen können hinzugefügt, geändert, entfernt oder umgeschichtet werden, um die passendste Struktur erlangen zu können. Der Hauptnachteil in einer geschichteten Struktur ist der Overhead und die Latenzzeit, die entstehen könnte, um die Daten zu verarbeiten. Dies führt dazu, dass für die BenutzerIn die gefühlte Performance der Anwendung sinkt. (Fielding 2000, 82f)
Das letzte von Fielding beschriebene Prinzip heißt „Code-On-Demand“. REST erlaubt eine Erweiterung der Client-Funktionalität durch das Herunterladen und Ausführen von Code in Form von Applets oder Skripten. Das vereinfacht den Client durch eine Reduzierung der Anzahl von Features, welche bereits von Anfang an vorinstalliert sein müssen. Durch die Opportunität, Features nach einem Deployment herunter zu laden, wird die System-Erweiterbarkeit deutlich verbessert. Andererseits wird die Transparenz des Systems reduziert, weshalb Fielding dieses Prinzip als optional anführt. (Fielding 2000, 84f) Erl u.a. stimmen in ihren Ausführungen Fielding zu und sehen dieses Prinzip ebenfalls als optional an. (Erl u. a. 2012, 57)
Web Services mit REST verbinden somit die Vorteile von Online-Services, welche über definierte Standards, wie HTTP, ansprechbar sind und Konventionen, welche generell die Vorteile der Web-Architektur nutzen.
Wie genau ein Web Service aufgebaut sein kann, hängt von den jeweiligen Anforderungen der konkreten Aufgabenstellung des Services ab. Bei jedem Web Service mit REST ist wichtig, dass die REST-Prinzipien eingehalten werden. Dadurch ergeben sich enorme Vorteile. Ein Web Service könnte beispielsweise als eine API für andere Services dienen. Die API-Definition lässt sich aus den REST-Prinzipien ableiten. Jede Ressource ist über eine bestimmte URL abrufbar. Z.B. könnten über folgende URL http://www.my-web-service.com/books.html alle Bücher einer Online-Bibliothek verfügbar gemacht werden. Wird das „books.html“ durch „books.json“ ausgetauscht, sind die Bücherdaten im JavaScript Object Notation (JSON)-Format abrufbar. Dieses Beispiel spiegelt die obigen erklärten REST-Prinzipien wider.
Ein Web Service lässt sich beliebig in ein SOA-System einbetten. Dabei kann er sowohl als einer der Hauptservices, aber auch als kleiner Hilfsservice für andere Services fungieren. Generell ist festzuhalten, dass die Kombination von Web Services mit REST ein starkes Werkzeug für die Entwicklung von Online-Plattformen bildet.
Da die REST-Designphilosophie populärer wird, tauchen auch immer mehr Frameworks (vgl. 5) auf, welche den REST-konformen Entwurf erleichtern. Folglich wird auch das Entwickeln von Web Services durch diese Frameworks deutlich einfacher. Zu diesen aufstrebenden Frameworks zählen unter anderem „Ruby on Rails “, welches in Ruby implementiert ist, „Restlet“, ist in Java entwickelt und „Django“, eine Python-Implementierung. (Richardson und Ruby 2007, 391f).
Da es das Ziel dieser Masterarbeit ist, eine Lösung für eine SOA-Implementierung in Ruby zu suchen, passt das von Richardson und Ruby genannte Beispiel „Ruby on Rails“ gut für die Überleitung zum nächsten Kapitel, welches „Ruby on Rails“ (vgl. 5.1) in einem Unterkapitel vorstellt.
5. Frameworks und Tools
Dieser Abschnitt beinhaltet einige Frameworks und Tools aus der Ruby-Entwicklung und aus der Java-Welt. Die einzeln vorgestellten Frameworks bzw. Tools könnten jeweils als Baustein für eine SOA-Implementierung in Ruby fungieren. Die Vor- und Nachteile sowie die Parallelen aus der Ruby-Entwicklung und der Java-Entwicklung sollen herausgearbeitet werden. Mit den gewonnenen Erkenntnissen soll später die Beantwortung der Forschungsfrage dieser Masterarbeit vollzogen werden. So könnten beispielsweise bereits existierende Vorteile aus Java-Frameworks, wenn möglich, „ruby-like“, in die Ruby-Implementierung übernommen werden.
Als erstes Framework wird im nächsten Unterkapitel „Ruby on Rails“ (vgl. 5.1) vorgestellt. Dies soll auch die Überleitung vom vorigen Unterkapitel „Web Services mit REST“ auf die Frameworks herstellen und die Verknüpfungen der technischen Realisierung der Kommunikation innerhalb eines SOA-Systems mit den Tools verdeutlichen.
5.1 Ruby on Rails
Ruby on Rails ist ein mächtiges Web-Framework, welches ProgrammiererInnen dabei hilft, schnell moderne Webapplikationen zu entwickeln. (Bigg und Katz 2012, 1) David Heinemeier Hansson, seinerseits Erfinder des Ruby on Rails Frameworks, erwähnt in seinem Vorwort des Buches „The Rails 3 Way“, dass Ruby on Rails mehr als ein Programmier-Framework ist, um Webapplikationen zu bauen. Es stellt ebenso ein Framework dar, mit Hilfe dessen es möglich ist, über diese Applikationen nachzudenken. Somit bietet es das, was die meisten ProgrammiererInnen am häufigsten benötigen, um Projekte entwickeln zu können. (Fernandez 2011, xxxiii)
Das Framework Ruby on Rails wurde in der Programmiersprache Ruby konstruiert und im Jahre 2004 von David Heinemeier Hansson während der Implementierung des Projektes „Basecamp“ in der Firma „37signals“ entwickelt. Das Projekt Ruby on Rails ist mittlerweile längst ein Open Source Framework und wird von einer großen Community unterstützt. Die Version 3 des Frameworks stellt einen signifikanten Meilenstein in der Projektgeschichte dar. (Bigg und Katz 2012, 2) Es wurde fast komplett umgeschrieben und neu strukturiert, um in Zukunft einfacher und modularer Features hinzufügen zu können. Es kann mittlerweile schon fast von einem Grundprojekt mit Plugin-System gesprochen werden.
Die aktuellste Version ist zum heutigen Stand (02.07.2013) die Version 4.0.0. Der Sourcecode befindet sich auf Github unter folgender URL (Heinemeier Hansson 2013): http://www.github.com/rails/rails
Ruby on Rails setzt bei der Entwicklung auf „Convention over Configuration“. Dieses Konzept beschreibt, wie eine ProgrammiererIn beispielsweise Dateien benennen muss, damit Abläufe bereits Out-of-the-Box funktionieren. Die Konvention steht im Vordergrund und nicht eine Konfiguration über diverse Config-Files oder Ähnliches. Ruby on Rails bietet zu Beginn einen Generator, welcher die Basis-Struktur (Dateien, Ordner, etc.) anlegt, um die anschließende Arbeit zu erleichtern. (Bigg und Katz 2012, 2)
Der Einsatz des „Convention Over Configuration“-Patterns ist laut Olsen ein Schlüssel zum Erfolg des Ruby on Rails Frameworks. (Olsen 2008, 313). Er bestätigt mit seinen Ausführungen die Erläuterungen von Bigg und Katz und verdeutlicht diese aus der AnwenderInnen- und EntwicklerInnensicht folgendermaßen:
“The Convention Over Configuration pattern focuses on applying these same principles to the design of applications and frameworks APIs. Why save all of the good techniques for the end user? The engineers who are trying to configure your application or program to your API are users, too, and they could use a hand as well. Why not provide a good interface for all your users?” (Olsen 2008, 315)
Die komplette Struktur einer Ruby on Rails-Anwendung zu erklären würde den Rahmen dieser Masterarbeit deutlich sprengen. Für genauere Informationen zu diesem Thema kann folgende URL abgerufen werden: http://guides.rubyonrails.org/getting_started.html
Der Autor möchte hiermit darüber informieren, dass im Grunde genommen jede Ruby on Rails-Applikation mit dieser Grundstruktur nahezu gleich aussieht. Es kann von Anwendung zu Anwendung eventuell individuelle, zusätzliche Ordner und Dateien geben. Der generelle Aufbau bleibt aber bei allen Ruby on Rails-Applikationen derselbe. Die angesprochenen zusätzlichen Ordner und Dateien werden sich aber alle wiederum an dafür definierten Plätzen befinden, sodass auch hier eine Konvention gilt und diese auch eingehalten werden sollte. Somit schließt sich der Kreis des „Convention Over Configuration“-Patterns und das obige Zitat von Olsen wird nochmals verständlicher.
Die Applikationsstruktur des Ruby on Rails Frameworks setzt, wie bereits angesprochen, auf das „Convention Over Configuration“-Patterns. Programmiertechnisch arbeitet Rails nach dem Model-View-Controller (MVC)-Prinzip. Dieses Design-Pattern bildet neben „Convention Over Configuration“ den Kern einer Ruby on Rails-Applikation. Durch den Einsatz dieses Patterns werden das Design und die Logik voneinander getrennt, wodurch ProgrammiererInnen die Arbeit erleichtert wird. (Bigg und Katz 2012, 3)
Um das Prinzip einer Ruby on Rails-Anwendung besser verstehen zu können, folgt ein kurzer Exkurs und eine Erläuterung des MVC-Patterns:
- MVC
Das MVC-Entwurfsmuster besteht aus drei Arten von Objekten. Das „M“ steht für „Model“, das „V“ für „View“ und das „C“ für „Controller“. Ein Model stellt ein Applikation-Objekt dar und verarbeitet Daten. Datenbankzugriffe erfolgen hauptsächlich über diese „M“-Komponente. Die View ist zuständig für die Präsentation der Applikation und ist somit als Frontend-Schicht zu verstehen. Der Controller definiert wie Interaktionen von BenutzerInnen Auswirkungen auf die Applikation haben. (Gamma u. a. 1995, 14)
Ein Ablauf könnte zusammengefasst so aussehen: Beispielsweise klickt eine BenutzerIn auf einen Link, der Controller gibt an das Model weiter, welche Daten für den entsprechenden Link geladen werden müssen. Das Model lädt diese Daten und gibt sie an den Controller weiter. Der Controller benachrichtigt eine View und füttert selbige mit den weitergegebenen Daten. Die View stellt anschließend die Seite für die BenutzerIn dar und gibt die Daten, entsprechend dem vorhandenen Design, Interface, etc., aus. Im Falle des Ruby on Rails Frameworks schaltet sich noch eine zusätzliche Komponente zwischen Controller und View, der Router. Dieser definiert, unter welcher URL welcher Controller wie angesprochen werden soll.
Durch das MVC-Pattern kann ein Programmcode besser strukturiert werden. Die Strukturierung und Abstraktion ist dabei sehr wichtig. Durch die Aufteilung in die einzelnen Komponenten finden sich auch unterschiedliche EntwicklerInnen in neuen Projekten leichter zurecht. Laut Gamma u.a. werden durch das MVC-Pattern Logik und Design sowie verschiedene programmtechnische Abläufe voneinander entkoppelt, wodurch weit mehr Flexibilität entsteht. Einzelne Komponenten können auch in anderen Programmen und Applikationen leichter zum Einsatz kommen und somit wiederverwendet werden. (Gamma u. a. 1995, 14)
Abbildung 4: Ruby on Rails: MVC-Pattern einer Ruby on Rails-Applikation
Quelle: http://rubyonrailslink.blogspot.co.at/2010/09/mvc-architecture-mvc.html
Abbildung 4 beinhaltet das MVC-Pattern einer beliebigen Ruby on Rails-Applikation. Die drei bereits im „MVC-Exkurs“ beschriebenen Komponenten werden grafisch dargestellt. Zusätzlich zum bereits oben erwähnten Router, kommt noch ein Webserver sowie der Dispatcher ins Spiel. Ruft nun beispielsweise eine BenutzerIn im grau dargestellten Browser eine Ruby on Rails-Applikation auf, so wird der Aufruf zuerst an den Webserver weitergegeben. Das nächstfolgende Routing bestimmt über den Dispatcher, welcher Controller aufgerufen wird. Der Dispatcher initialisiert dabei den Controller. Der weitere Ablauf wurde bereits im obigen allgemeinen Beispiel beschrieben. Diese Grafik soll somit den genauen Ablauf innerhalb einer Ruby on Rails-Applikation verdeutlichen. Zwischen Webserver und Routing können seit den neueren Rails Versionen (Rails 3 und 4) auch sogenannte Middlewares eingefügt werden. Eine solche Middleware kann beispielsweise die angesprochene URL für eine neuere API, welche im Backend bereits läuft, umcodieren oder andere Schritte ausführen. Diese Middlewares werden als Rack Middleware Module implementiert. Zum Thema „Rack“ erfolgt später noch eine genauere Erklärung (vgl. 5.3).
Das MVC-Pattern in Ruby on Rails wird durch REST (vgl. 4.3) unterstützt. REST bildet die Konvention für das Routing in Ruby on Rails-Applikationen. Diese Konvention wird RESTful Routing genannt und stelt ein Kernstück jeder Webapplikation dar, welche mit Ruby on Rails entwickelt wurde. (Bigg und Katz 2012, 4)
Das Framework Ruby on Rails könnte Bestandteil einer SOA-Implementierung für die Programmiersprache Ruby sein. Durch die Konventionen, wie beispielsweise das RESTful Routing, können Vorteile in eine größere Architektur übernommen werden. Eine Ruby on Rails-Applikation kann zum Beispiel als ein Web Service (vgl. 4.3) innerhalb einer SOA-Implementierung Platz finden.
Ruby on Rails ist eines der populärsten Frameworks für Ruby geworden. (Richardson und Ruby 2007, 391f) Es gibt aber auch andere Werkzeuge, um Ruby-Anwendungen bauen zu können. Aufgrund dessen folgt hier die Überleitung zum nächsten Ruby-EntwicklerInnentool.
5.2 Sinatra
Sinatra ist eine domänenspezifische Sprache um Websites, Web Services und Webapplikationen in Ruby zu entwickeln. (Harris und Haase 2012, 1) Domänenspezifisch bedeutet in diesem Zusammenhang, dass Sinatra eine formale Sprache ist, die besonders für eine bestimmte Domäne bzw. ein bestimmtes Problemfeld entworfen wurde. Diese Domäne stellt in diesem Fall die Programmiersprache Ruby dar.
Sinatra setzt seinen Schwerpunkt darauf, möglichst minimalistische Ansätze für die Entwicklung zu liefern. Diese Ansätze sollen dabei nur Grundlegendes für die Behandlung eines HTTP-Requests und die Lieferung von Antworten an Clients bereitstellen. Das EntwicklerInnentool Sinatra ist in der Programmiersprache Ruby umgesetzt worden und mit 2000 Codezeilen im Verhältnis zum vorher vorgestellten Framework Ruby on Rails (vgl. 5.1) einfach und unkompliziert. Für den geringen Projektumfang bietet Sinatra trotzdem sehr viel Praktisches, um beispielsweise einen auf Ruby basierenden Web Service zu implementieren. (Harris und Haase 2012, 1)
Die aktuellste Version von Sinatra ist derzeit (08.07.2013) die Version 1.4.3. Der Sourcecode befindet sich auf Github unter folgender URL (Mizerany u. a. 2013): https://github.com/sinatra/sinatra
Bei diesem Tool handelt es sich um eine Open Source Lösung. Sinatra wird im Gegensatz zum bereits erläuterten Tool Ruby on Rails (vgl. 5.1) nicht als Framework eingestuft. In das Werkzeug Sinatra ist beispielsweise kein Object-relational mapping (ORM)-Tool integriert. Bei Ruby on Rails hingegen ist mit ActiveRecord ein solches vorhanden. Sinatra hat keine Code-Generatoren und keinen Projektordner, sollte dieser nicht händisch anlegt werden. Sinatra-Applikationen sind sehr flexibel und typischerweise nicht größer als nötig. Sie können leicht als ein Gem ausgeliefert werden. (Harris und Haase 2012, 2)
Sinatra unterscheidet sich in einem weiteren Punkt zu Ruby on Rails (vgl. 5.1). Dieses Werkzeug implementiert nicht das MVC-Pattern (vgl. 5.1). Es wird auch nicht verlangt, dass EntwicklerInnen auf dieses Pattern oder jegliches andere Entwurfsmuster setzen müssen, um Sinatra verwenden zu können. Sinatra ist ein leichtgewichtiger Wrapper um eine Rack (vgl. 5.3) Middleware und unterstützt eine enge Beziehung zwischen Service-Endpunkten und den HTTP-Schlüsselwörtern. Dadurch eignet sich dieses Tool sehr gut für den Einsatz von Web Services oder APIs. (Harris und Haase 2012, 2)
Um die Funktionsweise dieses Tools besser verstehen zu können, folgt ein kurzes, konkretes Beispiel für eine kleine, leichtgewichtige Sinatra-Applikation.
# server.rb require "sinatra" get "/" do "Hello this is the start page of this little sinatra application!" endListing 1: Sinatra Tool: Einfache Sinatra-Applikation
Listing 1 beinhaltet eine ganz einfache Sinatra-Applikation. In der Datei „server.rb“ befindet sich dieser Code. Um diese Applikation starten zu können, müssen folgende Schritte ausgeführt werden:
- Sinatra installieren mit „gem install sinatra“.
- Webserver installieren, falls noch nicht vorhanden. Hier kann jeglicher Ruby-Webserver verwendet werden. Harris und Haase empfehlen in ihren Ausführungen „Thin“: „gem install thin“
- In der Kommandozeile „ruby server.rb“ ausführen.
Als Ergebnis wird anschließend auf der URL „localhost:4567“ „Hello this is the start page of this little sinatra application!“ ausgegeben. (Harris und Haase 2012, 4f)
Dieses Beispiel kann natürlich ausgebaut und trotzdem als Single-File-Applikation ausgeliefert werden. Statische Seiten lassen sich so praktisch bauen. Es können aber auch dynamische Webapplikationen entstehen. Bei dieser kleinen Demo-Applikation tritt sofort der Aufruf „get“ (vgl. Listing 1) hervor. Dieser Call wird vom Webserver als HTTP-GET Aktion interpretiert.
Sinatra setzt somit auf den HTTP Standard und interpretiert, ähnlich wie Ruby on Rails (vgl. 5.1), die entsprechenden Schlüsselworte wie „GET“, „POST“, „PUT“ und „DELETE“. Dies muss festgehalten werden, da es in Bezug auf das Routing von Sinatra äußerst wichtig ist. (Harris und Haase 2012, 17) Das Routing generell ist ein Kernkonzept jeder Sinatra-Anwendung. Sinatra setzt beim Handling der Routes, wie auch Rails, auf die entsprechenden „HTTP“-Konzepte. (Harris und Haase 2012, 15)
Sinatra kann aber nicht nur als Wrapper für das Routing verwendet werden, auch jegliche andere Art von Ruby Code kann ausgeführt werden. Es können somit auch große Webapplikationen mit Sinatra gebaut werden. Sinatra unterstützt unter anderem Caching über HTTP-Headers oder ETags. (Harris und Haase 2012, 38f) Des Weiteren unterstützt dieses Tool auch Sessions. (Harris und Haase 2012, 42) Wichtig bei diesen Features ist, dass der zugrunde liegende HTTP-Standard zu Hilfe genommen wird. Aufgrund dessen sind die aufgeführten Unterstützungen von Sinatra allesamt auf HTTP zurückzuführen.
Eine Sinatra-Applikation wird in der Praxis meistens mithilfe der nachfolgend erklärten Technologie „Rack“ (vgl. 5.3) entwickelt. Rack fungiert dabei als eine Art Wrapper für die gesamte Sinatra-Anwendung. Um dies besser verstehen zu können, folgt hiermit der Übergang zum Unterkapitel „Rack“.
5.3 Rack
Rack ist ein modulares Interface um Web Requests entgegenzunehmen und zu verarbeiten. Rack ist in Ruby implementiert und unterstützt verschiedene Webserver. Mithilfe von Rack wird das HTTP-Request-Handling abstrahiert. Die Ausführung kann mit einem einzigen Aufruf, einer so genannten „call“-Methode, durchgeführt werden. (Fernandez 2011, 86)
Nach der Meinung von Harris und Haase ist Rack ein extrem einfaches Protokoll, welches spezifiziert wie ein HTTP-Server mit einem Applikation-Objekt umzugehen hat. Die eindeutigen Schnittstellen werden praktisch durch Rack definiert. Rack selber weiß dabei nichts vom Ruby-Script oder der kompletten Ruby on Rails-Applikation, welche die bereits angesprochene „call“-Methode ausführt. Rack definiert somit das übergeordnete Vokabular, welches Hardware (Server) und Software (Ruby-Script, Ruby on Rails-Applikation, Sinatra-Anwendung, etc.) für die Kommunikation verwenden kann. (Harris und Haase 2012, 58)
class HelloWorld def call(env) [200, {"Content -Type" => "text/plain"}, ["Hello World"]] end endListing 2: Rack Tool: call-Methode
Listing 2 beinhaltet ein Code-Snippet mit besagter „call“-Methode. Diese Methode wird über einen HTTP-Request aufgerufen und ein Hash mit Umgebungsvariablen wird übergeben. Die „call“-Methode gibt dann ein Array mit drei Elementen als Antwort zurück. Das erste Array-Element, in diesem Fall „200“, gibt den HTTP-Statuscode zurück, das zweite Element einen Hash von Response-Headern und das letzte Element den Body des Requests. (Fernandez 2011, 86)
Durch diese Methode kann alles an ein Rack-Interface übergeben werden. So benutzt beispielsweise Ruby on Rails (vgl. 5.1) ein solches Rack-Interface um Middlewares zu behandeln. Diese können nach einer Konfiguration in den Rails Stack zwischen geschaltet werden. (Fernandez 2011, 86f) Folgendes, konkretes Beispiel soll dies verständlicher darstellen:
# app/middlewares/old_url_redirector.rb class OldUrlRedirector def initialize app @app = app end def call env path = env["PATH_INFO"] if parts = path.match(/^\/Werk\/(\d*)\/(.*)/) old_title = parts[2].gsub("+", " ") new_title = Project.my_magic_function(old_title) new_path = "/project/#{parts[1]}-#{new_title}" header = {} header["Location"] = new_path status = 301 response = ["This resource has permanently moved to #{new_path}"] else status , header , response = @app.call(env) end [status , header , response] end endListing 3: Rack Tool: Beispiel für Middleware (OldUrlRedirector)
In der Vorstellung des Frameworks Ruby on Rails (vgl. 5.1) wurde bereits angesprochen, dass eine Middleware für den Einsatz von URL-Redirects in Frage kommen könnte. Listing 3 beinhaltet ein Beispiel für einen solchen Fall. In der Vergangenheit wurde eine Applikation entwickelt, die datenbankspezifische URLs für Projekte generiert hat. Durch eine Überarbeitung und ein Redesign haben sich diese URLs geändert. Mithilfe dieser Middleware können die alten URLs automatisch auf die neuen umgeleitet werden. In diesem Listing ist die vorher erklärte „call“-Methode angeführt. Diese nimmt die Pfadinformationen auf und verarbeitet sie. Sollte die Information nicht matchen, wird der komplette Aufruf einfach an die nächste Middleware weitergereicht und immer „status“, „header“ und „response“ zurückgegeben.
# config/initializers/old_url_redirector.rb Rails.application.config.middleware.insert_before(0, OldUrlRedirector)Listing 4: Rack Tool: Integration einer Middleware (OldUrlRedirector) in eine Ruby on Rails-Applikation
Mithilfe des in Listing 4 angeführten Initializers kann die Middleware (vgl. Listing 3) in eine Ruby on Rails-Applikation integriert werden. Der Code besagt, dass diese Middleware an oberste Stelle geschoben und dann als Erstes ausgeführt wird.
Im Unterkapitel „Sinatra“ wurde bereits angesprochen, dass Rack als eine Art Wrapper für eine Sinatra-Anwendung fungieren kann (vgl. 5.2). Das bedeutet, dass beispielsweise anstatt des OldUrlRedirectors ein Sinatra Modul an eine Rack Middleware übergeben werden kann. Damit würde die komplette Sinatra-Applikation als Rack Middleware zur Verfügung stehen. (Harris und Haase 2012, 62f)
Diese ausführliche Erklärung zum Thema Rack soll aufzeigen, dass mit Rack ein Tool entwickelt wurde, welches sich ideal in bereits bestehende Ruby-Frameworks und Tools integrieren lässt bzw. teilweise bereits tief in der Struktur des Frameworks (Ruby on Rails) verankert ist. Dadurch eignet sich Rack auch als Bestandteil in einer SOA-Implementierung für Ruby zu fungieren. Die Kommunikation über HTTP wird durch den Einsatz von Rack erleichtert.
Nach der Meinung des Autors ist es möglich, mithilfe von Rack, Sinatra und Ruby on Rails eine gute Balance zu finden, um eine SOA-Implementierung für Ruby entwickeln zu können. Werden alle Vorteile miteinander verbunden, könnte Großartiges entstehen. Auf der anderen Seite gibt es nicht nur Ruby als Programmiersprache, sondern beispielsweise auch Java. Im Java Bereich ist SOA bereits länger ein wichtiges Thema und es sind über die Jahre einige Technologien, Frameworks und Tools entstanden, um gute serviceorientierte Strukturen für Webanwendungen entwickeln zu können. Aus diesem Grund wird in den folgenden Unterkapiteln der Schwerpunkt auf Java gelegt.
5.4 Java EE
Java Platform Enterprise Edition (Java EE) ist das erste Tool, welches aus dem Java-Bereich vorgestellt wird. Java EE ist laut Earl eine von zwei primären Plattformen um derzeit Unternehmenslösungen mit Web Services zu entwickeln und zu bauen. (Erl 2005, 668) Java EE ist eine Entwicklungs- und Laufzeitumgebung, basierend auf der Programmiersprache Java. Diese Plattform ist standardisiert und viele AnbieterInnen unterstützen sie mit der zur Verfügungsstellung von EntwicklerInnentools, Server Runtimes und Middleware-Produkten für die Erstellung und Entwicklung von Java-Lösungen. (Erl 2005, 668)
Java EE ist laut Goncalves Ende der 1990iger Jahre auf den Markt gekommen und hat für die Programmiersprache Java eine robuste Software-Plattform für Enterprise Entwicklungen gebildet. Jede einzelne Version musste verschiedene Herausforderungen überwinden, wurde von manchen falsch interpretiert oder missbraucht und stand mit Open Source Frameworks im Wettstreit. Java EE wurde als schwergewichtige Technologie eingestuft. Durch die laufende Kritik wurden Vorteile für die Entwicklung geschaffen und mittlerweile liegt der Fokus, gemäß Goncalves’ Ausführungen, auf Einfachheit. (Goncalves 2013, xxxiii)
Java EE 7 ist der aktuellste Release der populären Java Plattform von Oracle. (Friesen 2011, xvii) Diese Version bleibt dabei aktuell. Beispielweise unterstützt diese Version HTML5 und hat weitere neue Features. Alle Features hier aufzuzählen würde den Rahmen dieser Masterarbeit sprengen. Sollte eine LeserIn genauere Informationen über die Neuheiten der Version 7 benötigen, können unter folgender URL weitere Details abgerufen werden: http://www.oracle.com/technetwork/java/javaee/overview/index.html
Die Entwicklung der aktuellsten Version 7 bezeichnet Goncalves als bedeutenden Meilenstein. (Goncalves 2013, 1)
Java EE ist Bestandteil des Java Development Kit (JDK)7 und wird verwendet um komponentenbasierte Enterprise Applikationen zu entwickeln. Die einzelnen Komponenten inkludieren dabei sogenannte „servlets“. Diese können laut Friesen äquivalent zu Applets wie Server zu Client gesehen werden. (Friesen 2011, 4) Die weiteren Teile des JDK7 sind für diese Arbeit nicht relevant.
Die Java Plattform Java EE setzt sich aus einer Reihe von Spezifikationen, welche als unterschiedliche Container implementiert sind, zusammen. Ein Container ist hierbei eine Java EE Runtime-Umgebung. Diese Container bieten den Komponenten, welche sie hosten, verschiedene Dienstleistungen an. (Goncalves 2013, 2) Zu diesen gehören beispielsweise folgende: (Goncalves 2013, 24)
- Dependency Injection
- Life-Cylce Management
Dependency Injection ist ein Design Pattern, welches voneinander abhängige Komponenten entkoppelt. Dabei werden die Abhängigkeiten eines Objekts erst zur Laufzeit organisiert. Benötigt ein Objekt zum Beispiel bei der Initialisierung ein anderes Objekt, so ist die Abhängigkeit dieser beiden Objekte an einem zentralen Ort hinterlegt, d.h. es wird nicht vom initialisierten Objekt selbst erzeugt. Der Begriff Dependency Injection wurde von Martin Fowler eingeführt. Um den Begriff im Zusammenhang mit Java EE besser erklären zu können, folgt ein Zitat von Goncalves:
“Instead of an object looking up other objects, the container injects those dependent objects for you. This is the so-called Hollywood Principle, „Don’t call us?“ (lookup objects), „we’ll call you“ (inject objects).” (Goncalves 2013, 24)
Bei der Java EE Plattform übernimmt somit die Organisation der Abhängigkeiten der Container durch Dependency Injection.
Der Lebenszyklus eines Java-Objekts ist laut Goncalves sehr einfach. Eine Java EntwicklerIn erstellt beispielsweise über das Schlüsselwort „new“ eine neue Instanz einer Klasse. Für die Zerstörung des Objekts ist anschließend der Garbage Collector zuständig. Unter Umständen ist das Schlüsselwort „new“ nicht erlaubt und der Container ist für das Managen des Lebenszyklus (Erstellung bis Zerstörung) einer neuen Instanz verantwortlich.
Diese Komponenten benutzen klar definierte Richtlinien, um mit der Java EE Infrastruktur und anderen Komponenten kommunizieren zu können.
Die Java EE Laufzeitumgebung definiert vier Komponenten-Typen, welche eine Implementierung unterstützen muss. Diese Typen werden im Folgenden kurz erläutert: (Goncalves 2013, 3)
- Applets
- Applications
- Web applications
- Enterprise applications
„Applets“ sind Graphical user interface (GUI) Applikationen, welche im Browser ausgeführt werden.
Als „Applications“ werden die Komponenten bezeichnet, welche auf einem Client-Gerät zum Einsatz kommen können.
„Web applications“ werden in einem Web-Container ausgeführt und horchen auf das HTTP-Protokoll. Sie bestehen aus den bereits weiter oben angesprochenen „Servlets“ und unterstützen SOAP (vgl. 2.2) und RESTful Web Service Endpunkte (Web Service vgl. 4.3).
Diese Applikationen werden in sogenannten Enterprise JavaBeans Containern ausgeführt. Enterprise JavaBeans sind über Container organisierte Komponenten für die Verarbeitung von Business Logik über größere Anwendungen. Sie können lokal oder remote über HTTP, SOAP und RESTful Web Services angesprochen werden.
Die Java EE Infrastruktur ist in sogenannte Container partitioniert. Jeder dieser Container hat eine spezielle Rolle, unterstützt eine Reihe von APIs und stellt Dienstleistungen für Komponenten zur Verfügung. Container verstecken dabei technische Details und Komplexität um die Portierbarkeit erhöhen zu können. Java EE stellt vier Typen von Containern zur Verfügung. Zwischen diesen und den Komponenten-Typen lassen sich teilweise Parallelen erkennen: (Goncalves 2013, 3)
- Applet Containers
- Application client container
- Web container
- Enterprise JavaBeans container
Wie der Name bereits erraten lässt, kann der „Applet Container“ Applet Komponenten im Browser ausführen. Entwickelt eine ProgrammiererIn beispielsweise eine Webapplikation, kann sie sich auf die visuellen Aspekte konzentrieren. Der Applet Container stellt dabei eine sichere Umgebung zur Verfügung.
Dieser Container beinhaltet eine Reihe von Java-Klassen, Bibliotheken und anderen Dateien. Er kommuniziert über HTTP.
Der „Web container“ stellt die zugrunde liegenden Services zur Organisation und Ausführung von Web Komponenten bereit. Er ist verantwortlich für die Initialisierung und Instanziierung sowie für das Aufrufen von Servlets. Dieser Container unterstützt sowohl HTTP als auch Hypertext Transfer Protocol Secure (HTTPS).
Der „Enterprise JavaBeans Container“ ist zuständig für die Ausführung von Enterprise Applikationen. Dabei erstellt dieser Container neue Instanzen, organisiert Lebenszyklen und stellt Services, die Themen wie Transaktionen oder Sicherheit behandeln, zur Verfügung.
Die bisher aufgezählten Punkte umfassen die Grundlagen der Java EE Plattform. Diese bietet noch viele weitere Eigenschaften und Errungenschaften. Für eine spezifischere Auseinandersetzung mit dieser Materie empfiehlt der Autor das Referenzwerk „Beginning Java EE 7“ von Antonio Goncalves.
Alle Java EE Eigenschaften lassen sich auch auf SOA portieren. Laut Earl könnte dies folgendermaßen aussehen: (Erl 2005, 681)
- Service Datenkapselung
- Loose Coupling
- Messaging
Innerhalb der Java EE Plattform lassen sich unabhängige Einheiten durch Enterprise JavaBeans oder Servlets gestalten. Servlets können hierbei eine kleine bis große Menge an Applikationscode beinhalten. Servlets können wiederum zusammengesetzt werden um größere Tasks zu erledigen oder alleine fungieren um kleinere Aufgaben zu absolvieren. Enterprise JavaBeans und Servlets können sich durch die Benutzung von Web Services (vgl. 4.3) einkapseln. Ein Ergebnis davon ist die Entstehung von klar definierten Services, welche in eine SOA eingegliedert werden können.
Durch die Verwendung von Interfaces innerhalb der Java EE Plattform können Abstraktionen erzielt werden, wie beispielsweise die Trennung von Metadaten und der aktuellen Komponenten-Logik. Durch die Zusammensetzung von frei verfügbaren oder auch firmeninternen Messaging-Technologien kann „Loose Coupling“ (vgl. 2.5) realisiert werden.
Bevor Java EE Web Services (vgl. 4.3) akzeptiert hat, wurde bereits Messaging über den JMS unterstützt. Dadurch wurde der Nachrichtenaustausch zwischen Servlets und JavaBeans ermöglicht.
Somit ist nicht schwer zu erkennen, dass Java EE für SOA geeignet ist. Teilweise ist Java EE bereits als SOA implementiert bzw. stellt es die passenden fertigen Lösungen zur Verfügung. Mitunter können somit für die Implementierung des Prototyps für ein Ruby-SOA-Tool auch Bestandteile aus der Java EE Entwicklung entnommen werden.
5.5 Spring
Spring ist laut Walls ein Open Source Framework für die Java Plattform. Mithilfe von Spring soll die Java und Java EE Entwicklung deutlich vereinfacht werden. Die Ursprungsprinzipien dieses Frameworks liegen bei Rod Johnson, welche er in seinem Buch „Expert One-on-One: J2EE Design and Development“ behandelt und entwickelt hat. Um die Komplexität der Entwicklung von Unternehmensapplikationen in den Griff zu bekommen, wurde Spring entwickelt. Durch Spring ist es möglich normale JavaBeans zu verwenden und dadurch Programme erstellen zu können, wofür vorher nur Enterprise JavaBeans herangezogen werden konnten. Die Brauchbarkeit von Spring ist nicht nur auf die serverseitige Entwicklung limitiert. Jede einzelne Java-Applikation kann, gemäß Walls’ Ausführungen, von Spring in Bezug auf Einfachheit, Testbarkeit und Loose Coupling (vgl. 2.5) profitieren. (Walls 2011, 4)
Ein Leitspruch bzw. eine Mission von Spring lautet wie folgt: „Spring simplifies Java development“. Um dies erreichen zu können, stellt das Framework einige grundlegende Ideen und Ansätze zur Verfügung. Spring beschäftigt sich mit den folgenden vier Kernstrategien: (Walls 2011, 5)
- Es soll eine leichte und minimal-invasive Entwicklung mit „guten alten“ Java Objekten stattfinden.
- Loose Coupling soll durch Dependency Injection und Interface Orientation vollzogen werden.
- Deklarative Programmierung soll durch Aspekte und gemeinsame Konventionen durchgeführt werden. Unter deklarativen Programmierung ist ein Programmierentwurfsmuster zu verstehen. Bei diesem Pattern liegt die Beschreibung des Problems im Vordergrund. Der Lösungsweg wird dadurch automatisch entwickelt.
- Durch Aspekte und bereits bestehende Vorlagen soll die Entwicklung von Boilerplates Code reduziert werden. Boilerplate Code sind Codefragmente, welche an vielen verschiedenen Stellen einer Applikation in mehr oder weniger unveränderter Form zum Einsatz kommen. In Bezug auf eine Webapplikation könnte dies beispielsweise ein HTML-Template, welches die Seitennavigation beinhaltet, oder generell der Rumpf (Header + Footer) von HTML-Seiten sein. Durch geschickten Einsatz von Vorlagen sollte dies in Spring realisiert werden.
Fast alle Aktionen von Spring können, laut Walls, mehr oder weniger auf diese vier Strategien zurückgeführt werden. (Walls 2011, 5) Der Autor ist der Meinung, dass aus den vier Grundbausteinen bzw. Ideen von Spring Erkenntnisse für die Implementierung des Prototyps für ein Ruby-SOA-Tool gezogen werden können.
package com.habuma.spring; public class HelloWorldBean { public String sayHello () { return "Hello World"; } }Listing 5: Spring Framework: Beispiel eines einfachen Java-Objekts
Listing 5 beinhaltet ein Beispiel für „gute alte“ Java Objekte aus der obigen Liste. Die angeführte Klasse ist in ein Spring Projekt integriert, muss allerdings nicht zwingend von der Spring API irgendwelche anderen Klassen importieren oder erweitern (extend). Dieses Beispiel stammt aus dem Referenzwerk von Walls. Er drückt die Bedeutung dieser Beispiel-Klasse wie folgt aus:
“HelloWorldBean is lean, mean, and in every sense of the phrase, a plain-old Java object.” (Walls 2011, 5)
Das soll heißen, dass auch mit „guten alten“ Java Objekten mit Spring zusammengearbeitet werden kann. Es werden nicht immer alle möglichen Erweiterungen und Features benötigt.
Spring unterstützt neben Dependency Injection, welches bereits im Kapitel „Java EE“ (vgl. 5.4) erläutert wurde, auch Aspect-oriented programming (AOP). Um diesen Ansatz von Spring besser verstehen zu können, folgt ein kurzer Exkurs und eine Erläuterung von AOP:
- AOP
AOP ist ein weiteres Programmierparadigma für die objektorientierte Programmierung. Dabei geht es darum generische Funktionalitäten über mehrere Klassen hinweg verwenden zu können. Dies beschreibt Walls als „cross-cutting concerns“. (Walls 2011, 85) Bei diesem Pattern werden logische Aspekte einer Applikation bzw. eines Programmes von der eigentlichen Geschäftslogik getrennt.
Walls zählt das Thema „Sicherheit“ als Beispiel für ein „cross-cutting concern“ auf. Viele Methoden einer Applikation können Sicherheitsregeln beinhalten, welche auf sie angewendet werden. Ist beispielsweise eine Anwendung in diverse Services (Service vgl. 2.1) aufgeteilt, können mehrere dieser Services gleiche Themen inkludieren. Zu diesen Bereichen zählt auch das von Walls genannte Beispiel „Sicherheit“. Mithilfe von AOP können die Teile, welche sich überschneiden, in Klassen aufgesplittet werden. Diese werden „aspects“ genannt. Jene Klassen können dann explizit in den bestehenden Services bzw. Modulen deklarativ definiert werden. Dies bringt den Vorteil, dass zum einen die Services einen schönen und aufgeräumten Code beinhalten, der tatsächlich für die Bewältigung der Aufgabe eines gewissen Services von Nöten ist. Zum anderen befindet sich der Code für die Sicherheitsmodule an einem festgesetzten Platz und kann somit auch leichter gewartet werden. (Walls 2011, 86) AOP ermöglicht dadurch eine Vereinheitlichung und eine gute Strukturierung.
Spring ist laut Walls ein auf Container basiertes Framework. Wird Spring beim Start eines neuen Projektes nicht konfiguriert, stellt es einen leeren Container dar, was für EntwicklerInnen nicht unbedingt hilfreich ist. Spring muss somit zu Beginn zuerst konfiguriert werden. Es muss festgelegt werden, welche Beans aufgenommen werden und wie diese miteinander verbunden werden sollen, um später zusammenarbeiten zu können. (Walls 2011, 32)
Zwischen Spring und dem Framework Ruby on Rails (vgl. 5.1) sind hier bereits signifikante Unterschiede erkennbar. Bei Spring muss konfiguriert werden, während Ruby on Rails auf Konventionen setzt. Der Prototyp für das Ruby-SOA-Tool könnte diese beiden Ansätze womöglich miteinander verbinden.
Mit Spring ist es somit möglich Webapplikationen zu bauen, die dem MVC-Prinzip entsprechen, die auf Dependency Injection oder AOP setzen. Spring Projekte können mit remote Services zusammenarbeiten und unterstützen REST. Zusätzlich kann noch Messaging für die Kommunikation eingesetzt werden. Auf all diese angesprochenen Features, welche Spring inkludiert, einzugehen, würde den Rahmen dieser Arbeit deutlich sprengen. LeserInnen finden detaillierte Informationen im Referenzwerk von Walls oder unter folgender URL: http://www.springsource.org/
Es sollte jedoch nicht außer Acht gelassen werden, dass Spring all dies unterstützt. Zwischen Ruby on Rails (vgl. 5.1) und Spring sind einige Parallelen erkennbar. In manchen Ansätzen gehen diese beiden Frameworks komplett unterschiedliche Wege, in anderen wiederum verfolgen sie die gleichen Aspekte. Der Autor erhofft sich durch die neuen Aspekte, welche Spring mit sich bringt, Erkenntnisse für die Ruby-Implementierung des Prototyps ziehen zu können.
5.6 Open Services Gateway initiative
Das Open Services Gateway initiative (OSGi) Framework definiert laut Hall u.a. ein dynamisches Modulsystem für die Java-Entwicklung. Durch den Einsatz dieses Tools wird den EntwicklerInnen mehr Kontrolle über ihren Programmcode gegeben, die Fähigkeit den Code-Lifecycle dynamisch zu managen wird verbessert und Loose Coupling kann eingesetzt werden. Des Weiteren verfügt OSGi über eine komplette Dokumentation, welche in einer aufwändigen Spezifikation deklariert ist. (Hall u. a. 2011, 1)
OSGi besteht aus zwei Teilen: dem OSGi Framework und dem OSGi Standard Services. Das Framework ist dabei die Runtime, es implementiert die OSGi Funktionalitäten und stellt diese zur Verfügung. Der Standard Service definiert wiederverwendbare APIs für allgemeine Aufgaben wie das Logging oder Systemeinstellungen. (Hall u.a. 2011, 9) Die OSGi Spezifikation definiert drei verschiedene Ebenen innerhalb dieses Frameworks. Dazu zählen folgende:
- Module Layer
- Lifecycle Layer
- Service Layer
Diese Ebene ist dafür zuständig Code zu sharen und zu bündeln, d.h. hier laufen die verschiedenen Codestränge ineinander und werden zu Paketen zusammengefasst. So ein Paket bzw. Modul wird „Bundle“ genannt. Ein solches beinhaltet Klassen-Dateien und deren zusammenhängende Ressourcen. Das Bundle selber ist ein Java Archive (JAR)-File. Diese JAR-Dateien beinhalten keine kompletten Applikationen, sondern ausschließlich die logischen Module, mit denen Applikationen anschließend geformt werden können. Die Bundle-Files stellen somit keine normalen JAR-Dateien dar, sondern besitzen bestimmte Vorteile. Einer davon ist beispielsweise, dass in einem Bundle explizit deklariert werden kann, welche Packages von außen her sichtbar sind und welche nicht. Des Weiteren kann festgelegt werden, von welchen externen Paketen das Bundle jeweils abhängig ist. (Hall u. a. 2011, 10)
Hier kann ein Bezug zur Ruby-Programmierung hergestellt werden. Der Autor würde diesen Layer mit dem Gem-Management von Ruby vergleichen. Dabei kann mithilfe des Gems „bundler“ und einer bestimmten „Gemfile“-Datei ebenfalls ein projektspezifisches Bundle erstellt werden. Ein „Gem“ kann mit einer externen Java-Library verglichen werden. Sollten LeserInnen mehr Informationen über diese Vorgangsweise benötigen, können diese unter folgender URL abgerufen werden: http://bundler.io/
Dies wird hier erwähnt um Parallelen zwischen Java und Ruby aufzuzeigen. Jene Querverbindungen könnten später zur Beantwortung der Forschungsfrage dieser Arbeit beitragen.
Der Lifecycle Layer ähnelt dem Lifecylce Management von Java EE (vgl. 5.4). Er stellt ein Modul-Management für Ausführungszeiten und Zugriff zum darunterliegenden OSGi Framework zur Verfügung, d.h. hier kann eingeteilt werden, wann welche Module in welcher Reihenfolge ausgeführt werden. Diese Ebene definiert somit, wie die einzelnen Bundles aus der vorigen Schicht dynamisch installiert und organisiert werden. (Hall u. a. 2011, 11)
Die bisher vorgestellten Ebenen können bildhafter wie folgt erklärt werden: Eine Familie baut sich ein Haus. Der Module Layer stellt dabei die Grundlage und Struktur dar. Der Lifecycle Layer übernimmt die elektrische Verdrahtung innerhalb des Hauses. Er ist dafür zuständig, dass alle Abläufe korrekt funktionieren.
Die letzte Ebene der OSGi Schichten-Architektur trägt den Namen „Service Layer“. Dieser Layer ist für die Kommunikation und Interaktion zwischen Modulen verantwortlich. Dabei setzt er auf das Konzept von SOA. Insbesondere geht es in diesem Layer darum, dass einzelne Services veröffentlicht werden, sprich im Gesamtsystem registriert werden. Anschließend muss eine Möglichkeit geschaffen werden, dass diese Services gefunden werden können. Hall u.a. sprechen in diesem Zusammenhang vom „publish, find and bind interaction pattern“, d.h. ein Service Provider publiziert Services in einer Service-Registrierung. In der gleichen Zeit suchen Service-Clients in dieser Registrierungs-Stelle nach verfügbaren Services. Wird der passende Service gefunden, kann er verwendet werden. (Hall u. a. 2011, 11)
Dieser Layer ist intuitiv, da er einem interfacebasierten Entwicklungsansatz folgt. Es wird zwischen Interface und Implementierung eine Trennung vollzogen. OSGi Services sind Java Interfaces, welche einen konzeptionellen Vertrag zwischen Service Provider und Service Clients repräsentieren. Dadurch wird der Service Layer leichtgewichtiger. Die Service Provider sind einfache Java Objekte, welche über direkte Methodenaufrufe zur Verfügung stehen. Sucht jetzt ein Service Client einen bestimmten Service, so muss innerhalb der Registrierungs-Stelle nur nach dem entsprechenden Service, welcher das passende Interface zur Verfügung stellt, gesucht werden. Die tatsächliche Implementierung wird durch den Methodenaufruf angesprochen.
Diese drei Schichten zusammen ergeben die Grundbausteine für das OSGi Framework. Mithilfe dieser Architektur könnte beispielsweise folgendes Programm erstellt werden:
Es wird eine Klasse erzeugt, welche verschiedene Greetings ausgibt. Dafür wird ein einfaches Interface erzeugt und im Module Layer in ein passendes JAR-File gesteckt. Im Lifecycle Layer wird diese Klasse mit einem OSGi Bundle Activator gestartet und gestoppt. In der Service Schicht wird die Greeting-Klasse registriert und steht anderen Services von nun an zur Verfügung. Diese Service Clients können nach dem Interface von Greetings suchen und den Service aufrufen. Anschließend wird das entsprechende Greeting ausgegeben. Dies ist eine stark verkürzte Version des HelloWorld-Beispiels von Hall u.a. (Hall u. a. 2011, 12f). Dieses Beispiel in kompletter Länge vorzustellen würde den Rahmen dieser Masterarbeit sprengen. Sollten LeserInnen weitere Informationen dazu benötigen, können diese aus dem zitierten Referenzwerk entnommen werden.
Dieses Unterkapitel schließt das Kapitel „Frameworks und Tools“. Angefangen wurde mit dem, nach der Meinung des Autors, wichtigsten Framework für die Programmiersprache Ruby - Ruby on Rails (vgl. 5.1). Beendet wurde das Kapitel mit dem, nach der Ansicht des Autors, wichtigsten Framework für Java - OSGi. Dies soll den Kreis der Frameworks und Tools schließen. Die gewonnenen Erkenntnisse sollen verwendet werden, um einen Prototyp für ein Ruby-SOA-Tool formen zu können.
Es kann hier bereits vorweg genommen werden, dass einige der vorgestellten Frameworks bzw. Werkzeuge Eigenschaften mitbringen, welche in der endgültigen Implementierung eine wichtige Rolle spielen. Die Implementierung des Ruby-SOA-Tools findet anhand der Beispielapplikation JAMES statt. JAMES wird im nächsten Kapitel vorgestellt. Um direkt darauf eingehen zu können folgt hiermit der Übergang zum entsprechenden Kapitel „JAMES - Grundlagen“.
6. JAMES - Grundlagen
Dieses Kapitel stellt im ersten Abschnitt die Beispielapplikation JAMES vor und erläutert dabei den Status Quo der Anwendung. Dies ist wichtig, um den LeserInnen einen Überblick verschaffen und bei der späteren Umstrukturierung Unklarheiten vermeiden zu können. Anschließend folgt ein kurzer Exkurs zu den Zielen und Anforderungen der neuen SOA-Variante von JAMES, die durch diese Masterarbeit entstehen soll.
Den LeserInnen sollen durch dieses Kapitel die Grundlagen der Beispielapplikation nähergebracht werden, um später auf die Möglichkeiten einer Umstrukturierung eingehen zu können.
6.1 Status Quo
JAMES ist eine News-Reader Applikation, bei der verschiedene Quellen von einer BenutzerIn hinzugefügt werden können, um anschließend eine Art persönliche Online-Tageszeitung zu erhalten. Bisher unterstützt JAMES Facebook, Twitter und RSS-Feeds als Quellen. Artikel können bewertet werden, um den eingebauten Sortieralgorithmus seitens der BenutzerInnen beeinflussen zu können. Des Weiteren ist es möglich, Artikel zu teilen und zu kommentieren. JAMES bietet einen „PERSONAL“- und einen „LATEST“-Bereich. Im ersten Bereich werden die Artikel gemäß persönlicher Vorlieben sortiert und im zweiten zeitlich absteigend angezeigt. JAMES befindet sich zurzeit (25.07.2013) in einer geschlos- senen Beta-Phase, d.h. BenutzerInnen können sich mit einem Beta-Schlüssel anmelden und die Applikation benutzen. Die öffentliche Registrierung ist noch nicht möglich. Soll- ten LeserInnen noch genauere Informationen über JAMES benötigen, können diese Online unter folgenden URLs abgerufen werden:
JAMES ist zurzeit (26.07.2013) eine monolithische Ruby on Rails-Applikation (Ruby on Rails vgl. 5.1), d.h. die Applikation besteht aus einer Code-Basis, welche das gesamte Projekt umfasst. Bei JAMES kommt der dokumentenorientierte Datenbanktyp MongoDB (http://www.mongodb.org) zum Einsatz. Für die Message-Pipeline wird zusätzlich noch Redis (http://redis.io) als Key-Value-Store verwendet. Für den Nachrichtenaustausch in der Message-Pipeline kommt RabbitMQ (http://www.rabbitmq.com) zum Tragen. LeserInnen denen die einzelnen Technologien nicht bekannt sind, finden bei den angeführten Links jeweils weitere Informationen zu dieser Technologie. JAMES wurde testgetrieben entwickelt.
Aus den bisher aufgezählten Eigenschaften könnten manche LeserInnen meinen, dass SOA sowieso schon im Einsatz sei. Es wurde von einer Message-Pipeline und von RabbitMQ zum Austausch von Nachrichten gesprochen. Dies alles ist allerdings an einem Stück in einer einzigen Ruby on Rails-Applikation integriert. Aus diesem Grund ist JAMES noch monolithisch und nicht serviceorientiert. Werden beispielsweise in der Message-Pipeline Änderungen durchgeführt, müssen immer alle Tests gestartet werden, um zu überprüfen, ob die Anwendung noch korrekt funktioniert. Beim Deploy muss die gesamte Applikation neu online gestellt werden. Alle Hintergrundprozesse müssen auch bei einer Änderung am Frontend neu gestartet werden. Dies alles sind deutliche Beispiele für den monolithischen Ansatz der Architektur von JAMES. Der weitere Aufbau der Applikation sieht wie folgt aus:
- Models
- Views
- Controllers
- Lib-Ordner
- Pipeline
- Scoring
JAMES beinhaltet eine Reihe von Models um die Daten repräsentieren zu können. Den Hauptteil der Models stellen die unterschiedlichen Entry-Typen dar. Ein Entry-Typ ist beispielsweise Entry::Rss, dieser repräsentiert einen Eintrag aus einem RSS-Feed.
Abbildung 5: Beispielapplikation JAMES: Auflistung aller Models
Abbildung 5 beinhaltet eine vollständige Liste aller JAMES Models. Diese lassen sich in drei große Bereiche untergliedern: User und Authentifizierung, Entry-Typen, Scoring.
Die Views setzen sich zum einen aus normalen Rails-Views zusammen, welche beispielsweise die Startseite, Login, Registrierung und statische Seiten beinhalten. Zum anderen muss gesagt werden, dass die Frontend-Applikation selber, bei eingeloggtem Zustand einer BenutzerIn, eine Ember-Applikation ist. Diese Applikation ist eigenständig, aber trotzdem direkt in die normale Ruby on Rails-Applikation eingegliedert.
Es gibt bereits einige Templates für eine mobile Version der Webapplikation. Dies ist zum jetzigen Zeitpunkt allerdings nicht relevant für die Masterarbeit.
JAMES besitzt einige Controller, wie beispielsweise einen für statische Seiten wie das Impressum oder einen für die Landing Page. Ansonsten ist noch der Entries-Controller erwähnenswert. Dieser ist für einige Ajax-Actions verantwortlich. Zur vollständigen Übersicht beinhaltet Abbildung 6 eine komplette Liste aller JAMES Controller.
Abbildung 6: Beispielapplikation JAMES: Auflistung aller Controllers
Die bisher genannten Punkte sind Rails-Standard. Im lib-Ordner von JAMES wird es, nach der Meinung des Autors, sehr interessant. Hier befindet sich die bereits oben angesprochene Message-Pipeline sowie das Scoring-System der Applikation.
Die Message-Pipeline besteht aus einzelnen Workern, welche jeweils Teile des Gesamtkonstrukts darstellen. Die Pipeline dient dazu, dass von einer BenutzerIn neue Artikel zu den hinterlegten Quellen verarbeitet werden können. Dieser Teil von JAMES besteht aus den Workern BasicWorker, StreamFetcher, ExistenceChecker, FetchDetails, Rank und StoreDb. Teilweise besitzen die einzelnen Worker noch Unterklassen, auf die im Rahmen der vorliegenden Arbeit nicht näher eingegangen wird. Jeder einzelne Quelltyp (Facebook, Twitter, RSS) legt bei seiner Verarbeitung den selben Weg durch die Worker zurück. Hier wird der monolithische Ansatz der Pipeline ersichtlich. Während der Entwicklung wurde nachträglich der Typ Twitter eingebaut. Um dies realisieren zu können, mussten verschiedene Worker umgebaut werden, die eigentlich nur für den Typ Facebook relevant gewesen sind. Dies sollte in Zukunft mit einer SOA im Hintergrund nicht mehr passieren.
Abbildung 7: Beispielapplikation JAMES: Pipeline-Konzept
Abbildung 7 verdeutlicht nochmals die Aufteilung der einzelnen Worker und deren Zusammenspiel innerhalb der Message-Pipeline. Dieser Teil bietet sich, nach der Meinung des Autors, an um in einzelne Services aufgeteilt zu werden. Wie dies im Detail aussehen könnte, wird zu einem späteren Zeitpunkt erklärt.
Das Scoring beinhaltet alle relevanten Teile um die verschiedenen Artikel entsprechend dem BenutzerInnen-Input und den textlichen Inhalten bewerten zu können. Dabei kommen kollaborative sowie inhaltsbasierte Analysen der Texte und Fakten zum Einsatz, um eine bestimmte Artikelbewertung abgeben zu können. Dieser Teil von JAMES ist für diese Masterarbeit nicht allzu relevant, könnte aber in einen eigenständigen Service ausgelagert werden.
Abbildung 8: Beispielapplikation JAMES: monolithische Architektur
Abbildung 8 fasst die Architektur von JAMES nochmals zusammen. LeserInnen können auf einen Blick die monolithische Architektur erkennen. Einige Ansätze sind bereits so gegeben, dass sie einfacher in ein SOA-System übernommen werden können, andere müssen neu strukturiert und angepasst werden.
Welche Ziele und Anforderungen bei der Umstrukturierung von JAMES festgelegt wurden, wird im nächsten Unterkapitel erläutert.
6.2 Ziel und Anforderungen
Während des Masterstudiums des Autors wurde JAMES mit einem neunköpfigen EntwicklerInnenteam erstellt. Zusätzlich haben noch drei StudentInnen unterstützende Arbeiten durchgeführt. Innerhalb dieser eineinhalb bis zwei Jahre Entwicklungsdauer sind einige Probleme aufgetaucht und es konnte mitunter nicht gegengesteuert werden. Zu diesen Problemen zählten beispielsweise die voneinander abhängigen Tests der Applikation. Viele Tests sind nur sporadisch durchgelaufen und haben ohne jegliches Schema zu einem anderen Zeitpunkt wieder zu Fehlern geführt. Dies ist für eine testgetriebene Entwicklung natürlich ein „No-Go“.
Durch die Umstrukturierung der Architektur von JAMES in eine SOA sollen einzelne, voneinander möglichst unabhängige Services entstehen. Diese Services können anschließend komponentenweise eigenständig getestet werden. Laut Dix kann durch diesen Ansatz eine hohe Isolation von einzelnen Modulen entstehen. Durch Isolation wird es deutlich einfacher einen Service zu optimieren und zu organisieren. Dadurch können sich auch einzelne ProgrammiererInnen auf gewisse Services und Bereiche spezialisieren. Eine deutlich höhere Qualität der Produktentwicklung kann erreicht werden. (Dix 2011, 30)
Zusätzlich zur Isolation einzelner Komponenten bzw. Services sollte die Anwendung durch die neue Struktur weit robuster werden. Gut designte Services erzielen, gemäß Dix’ Aus- führungen, diese Robustheit der gesamten Applikation. Services können leichter ausgetauscht werden, ohne dass Clients dies überhaupt bemerken. (Dix 2011, 34)
Durch die Umstrukturierung soll die Forschungsfrage dieser Arbeit, „Wie kann die Struktur einer monolithischen Ruby on Rails-Applikation in eine SOA umgewandelt werden?“, beantwortet werden. Dieser Punkt wurde bereits im Kapitel „Fragestellung und Ziel“ (vgl. 1.2) angesprochen. Die neuen Anforderungen sind, dass JAMES durch die neue Architektur einfacher weiterentwickelt werden kann, die Skalierbarkeit der Anwendung verbessert werden und vor allem die Unabhängigkeit von Komponenten eingeführt werden soll.
Um die Neuordnung der Architektur von JAMES durchführen zu können, wurde ein Prototyp für ein Ruby-SOA-Tool entwickelt. Dieses Werkzeug wird im nächsten Kapitel vorgestellt.
7. Prototyp
Dieses Kapitel stellt den im Rahmen dieser Masterarbeit entwickelten Prototyp für ein Ruby-SOA-Tool vor. Zuerst wird auf den Aufbau und die Konzepte dieses Werkzeugs eingegangen. Anschließend folgt eine Einführung mithilfe eines einfachen Beispiels. Die tatsächliche Umsetzung und Präsentation der Resultate anhand von JAMES erfolgt im nächsten Kapitel. Der Grund für diese Vorgangsweise liegt darin, dass das entstandene Tool nicht nur für JAMES funktionieren soll. Es soll auch für andere Ruby-Applikationen, welche auf eine SOA setzen, relevant sein.
Der Autor hält gleich zu Beginn dieses Kapitels fest, dass es sich um einen Prototyp handelt. Dieser sollte zum jetzigen Zeitpunkt (13.08.2013) noch nicht in Production zum Einsatz kommen, da manche Funktionalitäten noch nicht ausgereift sind. Das Tool wurde nicht testgetrieben entwickelt und manche Features sind vom jeweiligen Betriebssystem abhängig. Zusätzlich sollte die Sicherheit in Produktionsumgebungen noch deutlich verbessert werden (Stichworte: HTTPS, Passwortverschlüsselungen, etc.) Die erläuterten Punkte stellen allerdings für diese Masterarbeit keine Probleme dar, die Rahmenbedingungen wurden im Vorhinein so definiert und festgelegt.
7.1 Aufbau und Konzepte
Der entwickelte Prototyp besteht aus zwei, in der Programmiersprache Ruby, implementierten Komponenten. Der erste Teilbereich ist eine Ruby on Rails-Applikation und trägt den Namen „Servicesmaster“. Bei der zweiten Komponente handelt es sich um ein Gem namens „Servicesregistry“. Der Servicesmaster inkludiert seinerseits das Servicesregistry-Gem.
- Servicesmaster
- Servicesregistry
Diese Komponente ist die zentrale Verwaltungsstelle einzelner Services innerhalb eines SOA-Systems. Services mit verschiedenen Umgebungen (z.B. Development, Production, Staging, etc.) können angelegt, geändert und gelöscht werden. Im Entwicklungsmodus können die einzelnen Quellcode-Dateien für den jeweiligen Service für eine solide Grundstruktur automatisiert erstellt werden. Theoretisch ist dies auch in anderen Umgebungen möglich, macht allerdings, nach der Meinung des Autors, wenig Sinn.
Der Servicesmaster unterstützt zurzeit zwei unterschiedliche Arten von Ruby-Services. Dazu zählt der Ruby on Rails- und der Gem-Service. Der Ruby on Rails- Service beinhaltet eine herkömmliche Ruby on Rails-Applikation, welche als Service fungieren kann. Der Gem-Service entspricht einem pure Ruby-Konstrukt, welches ProgrammiererInnen jegliche Freiheiten zur Ruby-Entwicklung innerhalb eines definierten Rahmens gibt. Eine Besonderheit des Gem-Services ist, dass er durch die unterstützte Sourcecode Generierung automatisch in eine Rack-Applikation (Rack vgl. 5.3) gepackt wird. Dies hat den Vorteil, dass er sofort, mithilfe der nachfolgend erklärten Servicesregistry-Komponente, mit anderen Services interagieren kann. Bei der Sourcecode Generierung werden bereits alle Grundvoraussetzungen geschaffen, um einen neuen Service in das Gesamtkonstrukt integrieren zu können, d.h. EntwicklerInnen müssen dem Service selber zwar noch Funktionalitäten hinzufügen, die Basis für Themen wie Kommunikation, Integration und Zusammenspiel ist allerdings bereits von Anfang an automatisch gegeben. Wie dies genau funktioniert, wird im nächsten Unterkapitel (vgl. 7.2) genauer erklärt.
Das bereits vorgestellte Tool „Sinatra“ (vgl. 5.2) wurde vorerst nicht direkt in den Servicesmaster und die automatische Code-Generierung aufgenommen. Da der Gem-Service als Rack-Applikation fungiert, kann ein solcher Service auch leicht in eine Sinatra-Applikation umgebaut werden. Dies obliegt den jeweiligen EntwicklerInnen.
Ein weiteres Feature des Servicesmasters ist die Möglichkeit des automatisierenden Startens und Stoppens aller Services. Dies funktioniert in der derzeitigen Variante allerdings nur unter Mac OSx und im Entwicklungsmodus. Für ProgrammiererInnen bringt dies trotzdem bereits jetzt beträchtliche Vorteile während der Entwicklung von einzelnen Services. Diese Behauptung kann der Autor selber bestätigen, da während der Erstellung des Prototyps und verschiedener Beispiel-Services das Starten und Stoppen der Dienste einiges an Zeit gekostet hat.
Da der Servicesmaster selber eine Ruby on Rails-Applikation ist, können die Vorteile dieses Frameworks (vgl. Ruby on Rails 5.1) direkt innerhalb dieser zentralen SOA-Managementstelle genutzt werden. So sind beispielsweise jederzeit Erweiterungen und Verbesserungen durchführbar. Das Hinzufügen von zentralen Modulen innerhalb der neu entstehenden SOA ist somit zu jedem Zeitpunkt möglich. Um hier die ersten Querverbindungen zu bereits theoretisch behandelten Themen ziehen zu können, könnte als zentrales Modul das Logging ins Spiel gebracht werden. Der Ansatz von Spring AOP (vgl. 5.5) zu verwenden würde dies widerspiegeln. Für jeden Service könnte ein extra Logging-Modul in den Servicesmaster integriert werden. Dies ist noch nicht implementiert, könnte aber in Zukunft noch hinzugefügt werden.
Die Servicesregistry ist die zweite Komponente des Prototyps. Bei diesem Modul handelt es sich um ein Gem. Der Servicesmaster hat dieses Gem in seinem Gemfile und somit in seinem Bundle inkludiert. Jeder einzelne Service der SOA hat dieses Gem ebenfalls in seinem Bundle. Die Servicesregistry ist dafür zuständig, dass die einzelnen Services sich registrieren und anschließend gegenseitig verwenden können. Die Servicesregistry hat eine Klasse „Services“, welche für die Initialisierung von neuen Service-Objekten zuständig ist. Mithilfe dieser Klasse können anschließend auch die unterschiedlichen Kommunikationsmethoden aufgerufen werden. Eine Klasse namens „Registry“ ermöglicht den neu initialisierten Service-Objekten die Registrierung. Diese erfolgt mithilfe des Hinzufügens der neuen Objekte zu einem Services-Array. Wie dies exakt vonstatten geht, wird im nächsten Unterkapitel (vgl. 7.2) genauer erklärt.
Das Servicesregistry-Gem stellt zwei Rack Middlewares zur Verfügung. Wie genau eine Rack Middleware funktioniert, wurde bereits erklärt (vgl. 5.3). Die erste Middleware fungiert als Adapter zwischen dem Servicesmaster und dem jeweiligen Service. Wird ein Service hochgefahren, so ruft er über diesen Adapter den Servicesmaster auf und kann dadurch seine Konfigurationsdatei erneuern. Diese beinhaltet alle im System erkannten und registrierten Services. Jeder Service ist im Besitz einer solchen Datei, um über die anderen Services Bescheid zu wissen. Dies ist im Prinzip die Service Choreographie (vgl. 2.4.2) des Prototyps. Die Organisation der einzelnen Services findet, zusätzlich zum Servicesmaster (der die Services in einer Datenbank hält), über jeden einzelnen Service statt. Dies hat den Vorteil, dass beim Ausfall des Masters die einzelnen Services trotzdem noch miteinander kommunizieren können. Ein solcher Ausfall könnte beispielsweise durch den Deploy einer neueren Servicesmaster-Variante verursacht werden. Diese Organisationsvariante ermöglicht die Unabhängigkeit aller Komponenten voneinander. Dies führt zum nächsten Bezugspunkt zur Theorie - Loose Coupling (vgl. 2.5).
Durch diese Konfigurationsdatei fallen die beiden Ansätze „Convention over Configuration“ von Ruby on Rails (vgl. 5.1) und die Grundkonfiguration von Spring (vgl. 5.5) zusammen. Es werden Vorteile von beiden Frameworks vereint.
Die Adapter-Middleware funktioniert aber nicht nur in eine Richtung, sondern wird auch vom Servicesmaster für Updates genutzt. Ändert beispielsweise eine EntwicklerIn in der Servicesmaster-Weboberfläche die URL eines Services, so werden über den Master alle anderen im System erfassten Services benachrichtigt und die Konfigurationsdatei angepasst. Die Kommunikation wird somit dauerhaft aufrecht erhalten. Ein kleines Zusatzfeature in Bezug auf die Adapter-Middleware ist noch zu erwähnen: Über den Adapter hat der Servicesmaster die Möglichkeit, jeden Service im System herunterzufahren.
Abbildung 9: Prototyp: Architektur inklusive Beispiel Adapter-Middleware
Abbildung 9 beinhaltet die Basis-Architektur des Prototyps inklusive einem Beispiel für den Einsatz der Adapter-Middleware. GemService1 und RailsService2 sind zwei Services, welche zusammen mit dem Servicesmaster ein Beispiel für eine kleine SOA darstellen. Die Abbildung zeigt auf, welche Daten der Servicesmaster in der Datenbank hält und welche Daten die einzelnen Services jeweils in der „services.yml“-Datei abgespeichert haben. Jede Komponente hat ein entsprechendes Bundle. Aktion1 und Aktion2 verdeutlichen, wofür die Adapter-Middleware verwendet wird. Die blau beschrifteten Pfeile deuten die Kommunikationswege an. Die rot gekennzeichneten Pfeile stellen die Integration von einer Komponente in eine andere dar (Bundle).
Die zweite Middleware ist für die Kommunikation zwischen den Services untereinander zuständig. Die Servicesregistry nutzt für RPCs das Typhoeus-Gem. Typhoeus kann parallel HTTP-Anfragen durchführen und dabei die Logik für die EntwicklerIn entkoppeln. Nähere Informationen zu diesem Gem können unter folgender URL https://github.com/typhoeus/typhoeus abgerufen werden.
Abbildung 10: Prototyp: Beispiel für Einsatz der Communication-Middleware
Abbildung 10 beinhaltet einen Auszug über den Kommunikationsablauf bei einem RPC. Ein Service kann einen anderen über „Servicesregistry.servicename(parameter)“ aufrufen. Innerhalb der Servicesregistry wird versucht, die Klassen-Methode „servicename“ zu finden. Wird diese nicht gefunden, so greift in der Programmiersprache Ruby die method_missing-Methode. Diese wurde vom Autor so überschrieben, dass der Methoden-Name „servicename“ einer Find-Methode übergeben wird, die innerhalb der Servicesregistry einen Service sucht, der genau diesen Namen trägt.
Wird er gefunden, ruft die interne Servicesregistry::Service-Klasse eine „execute“- Methode auf. Diese wiederum überprüft, ob sich der gesuchte Service lokal oder remote befindet (Abbildung 10, Schritt 1). Ist die Service-Klasse lokal auffindbar, wird direkt die Service-spezifische „execute“-Methode aufgerufen.
Ansonsten erfolgt über das Typhoeus-Gem ein HTTP-Call (Abbildung 10, Schritt 2a) an den externen Service. Dieser Aufruf verläuft über die Rack Middleware für die Kommunikation (Abbildung 10, Schritt 2b). Innerhalb dieser wird der übergebene Service wieder in der Servicesregistry gesucht (Abbildung 10, Schritt 3). Diesmal ist der Service eine lokale Klasse und der lokale Aufruf der „execute“-Methode erfolgt. Damit schließt sich der Kreis und der gewünschte Service mit der gewollten Funktion wird aufgerufen.
Ein Service kann folglich einen anderen Service über dessen URL plus den Zusatz „/sr-communication“ aufrufen. Im Einführungsbeispiel „NewsService“ (vgl. 7.2) wird dies später noch praxisnah erläutert. Wichtig ist dabei vor allem, dass diese Middleware automatisch über die Servicesregistry in jedem einzelnen Service zur Verfügung steht und dadurch kommuniziert werden kann. Diese Rack Middleware verfolgt somit teilweise Prinzipien, auf die im Abschnitt Web Services mit REST (vgl. 4.3) eingegangen wurde. Jeder Service ist über eine URL erreichbar und kann darüber kommunizieren.
Die Servicesregistry unterstützt derzeit RPC als Kommunikationsmittel. Das in JAMES bereits integrierte Messaging-System „Rabbitmq“ kann aber durchaus auch in einer neueren Version Platz finden. Die bereits erwähnte „Service“-Klasse muss nur dementsprechend erweitert werden. Dies wird durch den modularen Aufbau des Servicesregistry-Gems erleichtert.
Die Arbeitsweise der Servicesregistry kann ansatzweise mit der OSGi Schichten-Architektur (vgl. 5.6) verglichen werden. Jeder einzelne Service hat ein Gemfile und damit ein Bundle, in welches die Servicesregistry eingegliedert ist. Dieser Abschnitt hat der Autor aus dem OSGi Framework „Module Layer“ entnommen und dementsprechend umgesetzt. Der Service Layer ist direkt auf die Servicesregistry projiziert worden. Services können sich registrieren und stehen anschließend zur Verfügung. Die Verwaltung wurde allerdings auf alle Services, mit zusätzlicher zentraler Schnittstelle Servicesmaster, aufgeteilt.
Der Prototyp (Servicesmaster + Servicesregistry) funktioniert standardmäßig „stateless“ (vgl. Zustände 2.1.1). EntwicklerInnen können einzelne Services allerdings nach Belieben selbst gestalten. Hier wird mithilfe des Prototyps nur eine Basisstruktur geschaffen, alles weitere liegt in der Zuständigkeit der jeweiligen ProgrammiererIn.
Die Konzepte des implementierten Prototyps beziehen somit einige Eigenschaften der bereits früher in dieser Masterarbeit vorgestellten Bereiche mit ein. Ansätze aus der Ruby- sowie aus der Java-Programmierung werden herangezogen, um eine gute Grundbasis zu schaffen. Damit soll der Bogen zwischen der Theorie aus der Literatur und der praktischen Implementierung im Rahmen dieser Masterarbeit verdeutlicht werden.
7.2 Einführungsbeispiel NewsService
Dieses Unterkapitel beinhaltet ein allgemeines, einfaches Einführungsbeispiel für den gerade vorgestellten Prototyp. Dabei werden die einzelnen Schritte erläutert, welche eine EntwicklerIn tätigen muss, um dieses Miniprojekt umsetzen zu können. Es wird darauf eingegangen, was bei den jeweiligen Abschnitten passiert. Die Implementierung dieses Beispiels befindet sich vollständig auf der beiliegenden CD dieser Masterarbeit.
- Allgemeines
- Schritt 1: Eintragen von Services
- Schritt 2: Sourcecode Generierung
- Ruby on Rails Service
- Gem Service
- Schritt 3: Implementierung HighlightService
Das Einführungsbeispiel beinhaltet fünf einzelne Services, welche zusammen mit dem Prototyp eine SOA bilden. Es geht darum, dass auf einer Website „News“ dargestellt werden sollen. Der NewsService bezieht diese Neuigkeiten dabei von einem ArticlesService und einem BooksService und stellt die Ergebnisse auf einer Weboberfläche dar.
Der ArticlesService liefert dem NewsService Artikel, dabei wird der Body der einzelnen Artikel von einem HiglightService hervorgehoben. Der Titel jedes Artikels wird ohne Highlighting ausgeliefert.
Der BooksService übergibt dem NewsService verschiedene Bücher, welche jeweils ISBN, Autor sowie einen Titel beinhalten. ISBN und Autor durchlaufen wiederum den Highlight-Service, der Titel nicht.
Die Anzahl der Artikel und Bücher wird von einem Calculations-Service errechnet und ebenfalls gehighlighted an den NewsService übergeben. Der NewsService stellt das Ergebnis auf einer Website dar.
Beim News-, Articles- und Books-Service handelt es sich jeweils um Ruby on Rails-Services. Artikel und Bücher können in den jeweiligen Applikationen beliebig hinzugefügt, geändert und gelöscht werden. Eine Änderung hat sofortige Auswirkungen auf das Ergebnis des NewsServices. Wird beispielsweise ein Buch hinzugefügt und die NewsService Website neu geladen, scheint dieses Buch sofort in den Ergebnissen auf. Die Calculations- und Highlight-Service sind als Gem-Services implementiert.
Abbildung 11: Einführungsbeispiel NewsService: Architekturübersicht
Abbildung 11 beinhaltet die Architektur des einführenden Beispiels NewsService im grafischen Überblick. Hier können, wie in Abbildung 9, die einzelnen Services und ihre Verbindung zum Servicesmaster (Datenbank) sowie die jeweils in das servicespezifische Bundle integrierte Servicesregistry erkannt werden.
Dies sollte vorerst als allgemeine Erklärung ausreichen. In der Folge wird auf die einzelnen Schritte während der Implementierung eingegangen.
In diesem Schritt werden die einzelnen Services in den Servicesmaster eingetragen. Dabei ist zu beachten, dass die Daten vollständig hinzugefügt werden. Der Autor hat während der Entwicklungsphase öfters vergessen einzelne Umgebungen richtig zu definieren, wodurch einige Fehler aufgetreten sind. Es muss berücksichtigt werden, dass es sich um einen Prototyp handelt.
Für einen Service muss ein Name gesetzt werden, beispielsweise „Articles“. Der Name wird jeweils mit dem Kürzel „_service“ vervollständigt und in Kleinbuchstaben abgespeichert. Dies folgt dem Ansatz von Convention over Configuration, um eine ordentliche Namensgebung zu gewährleisten.
Das nächste Attribut eines Services ist der „klass_name“. Dieser spielt eine entscheidende Rolle beim Aufruf des jeweiligen Services. „Article“ als Klassen-Name würde später bedeuten, dass die Servicesregistry eine Klasse erwartet, die Article heißt, welche eine bestimmte Methode namens „execute“ beinhaltet. Dieser Ansatz richtet sich wiederum nach dem Convention over Configuration Prinzip. Wird beispielsweise ein Service „News“ benannt, die Klasse, welche aufgerufen werden soll, befindet sich allerdings in einem Ruby-Modul „News“ und heißt „Foo“, so muss der Klassen-Name wie folgt angegeben werden: „News::Foo“. Diese detailgetreue Beschreibung ist sehr wichtig für das Verständnis über die Funktionalität des Beispiels.
Eine Beschreibung des Services ist das nächste Attribut, welches ausgefüllt werden kann. Dies ist indes optional und soll EntwicklerInnen zu Dokumentationszwecken dienen. Das Attribut „create_parameters“ kann ausgefüllt werden, wenn bestimmte Grundeigenschaften bei der Code-Generierung bereits von Anfang an gesetzt werden sollen. Hierbei handelt es sich beim Ruby on Rails-Service um die üblichen Optionen zum Erstellen einer neuen Ruby on Rails-Applikation und beim Gem-Service um die Version des jeweiligen Gems. Weitere Details hierzu befinden sich direkt im Servicesmaster in der Weboberfläche in Textform beschrieben.
Das Attribut „create_directory“ beschreibt den Ort, an den die Sourcefiles generiert werden sollen. Hier gibt es einen Default-Wert, der seitens von EntwicklerInnen geändert werden kann. Wird die Checkbox „Create service application“ gesetzt, wird der Quellcode des jeweiligen Services (Ruby on Rails oder Gem) mit dem Abspeichern des neuen Services generiert. Diese Generierung kann auch zu einem späteren Zeitpunkt durchgeführt werden.
Beim Anlegen bzw. Eintragen von Services können beliebig viele Environments (z.B. Development, Staging, Production, etc.) mitangegeben werden. Eine solche Umgebung hat die Eigenschaften „name“, „url“, „auth_token“ und „auth_secret“. Der Name ist beispielsweise „development“ und die URL „http://localhost:1234“. Auth-Token und Auth-Secret sind optional. Diese können zum Beispiel für Services, welche mit Facebook- oder Twitter-Applikationen interagieren müssen, praktisch sein. Im Entwicklungsmodus muss beachtet werden, dass bei der URL ein Port mit angegeben wird. Da die jeweiligen Services auch lokal laufen sollten und es sich um Rack-Applikationen handelt, können diese mit einer Port-Angabe leicht gestartet werden und parallel hochgefahren sein.
Um auf das Einführungsbeispiel zurückzukommen, sollten die jeweilig passenden Daten erfasst und abgespeichert werden. Damit ist der Grundstein für den Servicesmaster gelegt. Die Sourcecode Generierung kann direkt oder zu einem späteren Zeitpunkt erfolgen. Hier wird davon ausgegangen, dass sie zu einem späteren Zeitpunkt durchgeführt wird.
Im zweiten Schritt wird der Sourcecode für die einzelnen Services generiert. In der Praxis wird dies wohl Schrittweise erfolgen, da eine Generierung aller Services automatisiert auf einmal nicht allzu sinnvoll erscheint.
Beim Ruby on Rails Service wird bei der Sourcecode Generierung eine neue Ruby on Rails-Applikation an der Stelle erzeugt, auf die das Attribut „create_directory“ des jeweiligen Services zeigt. Diese neu erstellte Anwendung wird in Folge mehrfach manipuliert, um die automatische Eingliederung in die bestehende SOA zu ermöglichen.
Zuerst wird eine Datei in das Projekt kopiert, welche die zurzeit (13.08.2013) passende Ruby-Version 2.0.0-p195 festlegt. Anschließend wird die bereits erwähnte Konfigurationsdatei, welche den Namen „services.yml“ trägt, generiert und im neuen Service abgelegt. Diese Datei beinhaltet sofort alle im Servicesmaster eingetragenen Services.
Nächster Manipulationsschritt stellt die automatische Bearbeitung des bestehenden Gemfiles der Ruby on Rails-Applikation dar. Diesem Gemfile wird das Servicesregistry- und das Typhoeus-Gem hinzugefügt. Für den Entwicklungsmodus werden zusätzlich noch ein paar Debugger-Gems angeheftet. Anschließend werden die bereits erwähnten Middlewares des Servicesregistry-Gems in der Ruby on Rails-Anwendung aktiviert. Dies folgt über die Manipulation von den Environments-Dateien.
Im vorletzten Schritt wird ein Initializer zu den bestehenden Rails-Initializern hinzugefügt. Dieser ist dafür zuständig, dass beim Hochfahren des Services automatisiert die Konfigurationsdatei über die Adapter-Middleware erneuert wird und die eingetragenen Services innerhalb des Services registriert werden. Der Vorteil dabei ist, dass zu einem späteren Zeitpunkt jeder Service unabhängig ist und sich selber konfigurieren kann.
Abschließend wird das Bundle installiert und der neue Service hochgefahren. EntwicklerInnen können sofort anfangen dem neuen Service Funktionalitäten hinzuzufügen.
Beim Gem Service erfolgt eine ähnliche Manipulation nach der Generierung des Quellcodes. Zu Beginn wird ein neues Gem-Skeleton erstellt. Da dieses über kein Gemfile verfügt, wird im nächsten Schritt ein passendes Gemfile generiert und in das neue Gem kopiert. Diese Datei beinhaltet die folgenden Gems: Servicesregistry, Typhoeus, Rack und Debugger-Gems.
Damit das Gem in eine Rack-Applikation gepackt werden kann, folgt im nächsten Manipulationsschritt das Hinzufügen einer so genannten „config.ru“-Datei. Diese verwandelt das Gem in eine Rack-Applikation. Zusätzlich beinhaltet selbige Datei den ähnlichen Code wie der Rails-Initializer, d.h. beim Hochfahren des jeweiligen Services wird die „config.ru“-Datei geladen und löst damit automatisch die Konfiguration des Services aus. Grundlage für die Rack-Applikation bilden die zwei Middlewares des Servicesregistry-Gems, sie werden ebenfalls im „config.ru“-File aktiviert.
Zum Abschluss der Gem-Code Generierung werden wie beim Ruby on Rails Service noch die Ruby-Versionsdatei und Konfigurationsdatei erstellt und in das Gem kopiert. Zu guter Letzt erfolgt die Installation des Bundles und der neue Service wird hochgefahren und aktiviert.
Im Einführungsbeispiel wird in diesem Schritt für alle Services (News, Articles, Books, Calculations, Higlight) der Sourcecode generiert. Jeder Service kann über seine URL aufgerufen werden. Sollten LeserInnen das einführende Beispiel mit den bisher erläuterten Schritten mit implementiert haben, sollten sie zu diesem Zeitpunkt im Falle des News-, Articles- und Books-Service eine Standard-Willkommensseite des Ruby on Rails Frameworks angezeigt bekommen. Beim Aufruf des Calculations- sowie Highlight-Services sollte Folgendes annonciert werden: „calculations_service is up and running!“ bzw. „highlight_service is up and running!“.
Die folgenden Schritte werden abgekürzt erklärt, da Implementierungsdetails von der beiliegenden CD entnommen werden können. Es wird nur auf entscheidende Passagen näher eingegangen.
Zuerst muss eine Klasse im lib-Ordner des Gem-Services angelegt werden. Listing 6 beinhaltet die entsprechende Klasse. Diese wiederum beinhaltet die von der Servicesregistry erwartete „execute“-Methode. Die neue Klasse muss in der Root-Klasse des neuen Services required werden. Die Root-Klasse ist „HighlightService“.
module HighlightService class Css def self.execute(content) { :result => ["<div class=’highlighted’><p>#{content}</p></div>","with Greetings from the HighlightService"] } end end endListing 6: Einführungsbeispiel NewsService: HighlightService CSS-Klasse
Damit ist dieser Service bereits fertig implementiert. Wie er anschließend verwendet werden kann, wird im Rahmen des NewsServices erläutert.
Der CalculationsService bekommt eine Klasse „Addition“. Diese beinhaltet eine „execute“-Methode, welche zwei übergebene Parameter miteinander addiert und diese über den HighlightService hervorheben lässt, um sie anschließend zurückzugeben. Die „Addition“-Klasse muss ebenfalls in der Root-Klasse required werden.
Beim ArticlesService wird zuerst ein Rails-Scaffolding erzeugt. Dies funktioniert über den Kommandozeilenbefehl „bundle exec rails g scaffold Article title:string body:text && bundle exec rake db:migrate“. Nach einem Neustart des Services sollte unter der Service-URL plus „/articles“ die passende Oberfläche erscheinen. Hier können Artikel angelegt, geändert und gelöscht werden. In diesem Beispiel hat der Autor die Route von „/articles“ auf die Root-URL gelegt und damit erscheint die Oberfläche beim Aufruf der normalen URL des Services. Das Article-Model wird mit einer „execute“-Methode ausgestattet. Diese dient dem NewsService später zur Auslieferung der Daten.
Der BooksService wird ähnlich wie der ArticlesService implementiert. Der Kommandozeilenbefehl für das Scaffolding lautet wie folgt: „bundle exec rails g scaffold Book title:string isbn:string author:string && bundle exec rake db:migrate“. Die Oberfläche für die Bücherverwaltung sieht ähnlich aus wie die des ArticlesServices. Das Book-Model bekommt ebenfalls eine „execute“-Methode.
Der NewsService bekommt eine Model-Klasse „News“. Diese enthält zwei Methoden, die erste kann die Daten vom ArticlesService abrufen, die zweite vom BooksService. Für die Präsentation der Daten wird ein Controller erzeugt, der über eine „index“- Action verfügt. In dieser „index“-Action werden die beiden Methoden aus der News-Klasse und der CalculationsService zum Zusammenzählen der Artikel und Bücher aufgerufen. In einer „index“-View wird das Ganze im Rahmen einer Weboberfläche dargestellt.
# app/models/news.rb class News def self.fetch_articles Servicesregistry.articles_service end def self.fetch_books Servicesregistry.books_service end end # app/controllers/news_controller.rb class NewsController < ApplicationController def index @articles = News.fetch_articles @books = News.fetch_books articles = JSON.parse(@articles) books = JSON.parse(@books) @articles_count = articles["result"]["result"].first.length @books_count = books["result"]["result"].first.length count = Servicesregistry.calculations_service(@articles_count , @books_count) @count = count end endListing 7: Einführungsbeispiel NewsService: Auszug aus dem NewsService
Listing 7 beinhaltet den relevanten Programmcode des NewsServices. Anhand diesem soll der Programmablauf dieses Beispiels für die LeserInnen erläutert werden.
In der „index“-Action erfolgen die einzelnen Aufrufe an die anderen Services (ArticlesService und BooksService). Beim Aufruf „News.fetch_articles“ wird innerhalb dieser Methode der Aufruf „Servicesregistry.articles_service“ getätigt, welcher in Folge innerhalb der News-Servicesregistry nach einem „ArticlesService“ sucht. Ist dieser gefunden, so wird geprüft ob es sich um eine lokale Klasse handelt oder nicht. Der ArticlesService mit der gleichnamigen Klasse ist allerdings als eigenständiger Services implementiert und innerhalb des NewsServices nicht verfügbar. Deshalb wird im nächsten Schritt der Service über die Communication-Middleware der Servicesregistry remote aufgerufen (vgl. Abbildung 10, Schritt 2a und 2b). Der weitere Ablauf folgt dem Beispiel aus Abbildung 10.
Der ArticlesService liefert als Ergebnis alle eingetragenen Artikel. Intern ruft er noch den HighlightService auf, um das „body“-Attribut hervorheben zu können. Dieser Aufruf läuft gemäß dem bereits erläuterten Schema ab.
Als nächstes erfolgt der Aufruf des BooksServices. Dieser funktioniert analog zum ArticlesService. Abschließend erfolgt das Zusammenzählen der Ergebnisse aus dem ArticlesService und dem BooksService über einen Aufruf des CalculationsServices. Der Ablauf dieses Aufrufs kann ebenfalls mit den erläuterten Schritten aus Abbildung 10 verglichen werden. Einziger Unterschied ist dabei, dass für diesen Call Parameter übergeben werden. Diese werden innerhalb des CalculationsService benötigt.
Sind alle aufgezählten Schritte durchgeführt worden, kann anschließend die URL des NewsServices aufgerufen werden und alle eingetragenen Artikel und Bücher der jeweils anderen Services sollten passend aufbereitet (hervorgehoben) erscheinen. Durch den Prototyp ist es möglich, jeden Service innerhalb eines anderen Services aufzurufen. Die einzelnen Services sind unabhängig voneinander und konfigurieren sich beim Eintreten in die SOA-Umgebung selber.
Hiermit ist das Einführungsbeispiel und der Ablauf innerhalb des Prototyps für ein Ruby-SOA-Tool erläutert und abgeschlossen. Um auf die Beantwortung der Forschungsfrage dieser Masterarbeit eingehen zu können, werden im nächsten Kapitel zuerst Möglichkeiten der Partitionierung aus der Literatur vorgestellt. Anschließend wird der entwickelte Prototyp auf die Beispielapplikation JAMES angewandt. Für die Partitionierung und Umstrukturierung der JAMES-Architektur werden die erläuterten Möglichkeiten aus den Referenzwerken herangezogen.
8. Partitionierung am Beispiel JAMES
Dieses Kapitel nimmt einige Ansätze aus der Literatur, wie die Umwandlung einer monolithischen Ruby on Rails-Applikation in eine SOA aussehen könnte. Auf diese theoretischen Aspekte aus den Referenzwerken wird in Folge anhand der Anwendung JAMES praxisnah eingegangen. Im letzten Unterkapitel erfolgt die tatsächliche Umstrukturierung der JAMES-Architektur in eine SOA und die Forschungsfrage der vorliegenden Arbeit wird beantwortet.
8.1 Einführung
Wird eine neue SOA entworfen, müssen EntwicklerInnen zuallererst Design-Entscheidungen über die bestehende Architektur treffen. Diese Entscheidungen beinhalten, wie die Applikation aufgeteilt werden könnte. Die Aufteilung wird „Partitionierung“ genannt. (Dix 2011, 59)
Dix erwähnt in seinen Ausführungen vier Fragen, welche EntwicklerInnen dabei helfen sollen, die angesprochenen Design-Entscheidungen richtig fällen zu können: (Dix 2011, 54)
- Welche Daten haben eine hohe Lese- und eine niedrige Schreib-Frequenz?
- Welche Daten haben eine hohe Schreib- oder Update-Frequenz?
- Welche Joins treten am häufigsten auf?
- Welche Teile der Applikation haben klar definierte Anforderungen und ein Grunddesign?
In einer normalen Standard Ruby on Rails-Applikation können laut Dix durch die Beantwortung der ersten drei der genannten Fragen Schlüsse gezogen werden, inwiefern Models aufgeteilt und verschiedenen Services zugeteilt werden können. Die letzte Frage hilft festzustellen, welche Bereiche in einer normalen Ruby on Rails Umgebung bleiben und nicht in Services aufgeteilt werden sollen. (Dix 2011, 54) Laut Dix kann die Aufteilung schwierig sein und sollte deshalb schrittweise erfolgen. Der erste Schritt einer Standard Ruby on Rails-Anwendung ist die Service-Implementierung auf Model-Ebene.
Da JAMES keine normale Standard Ruby on Rails-Applikation ist, sondern noch einige Hintergrundprozesse (Message-Pipeline, Scoring) unterstützt, wird die Aufsplittung nicht so einfach vollzogen werden können. Um den LeserInnen den grundlegenden Ansatz der Überlegungen erläutern zu können, werden in der Folge die vier obigen Prinzip-Fragen von Dix anhand von JAMES beantwortet:
- Welche Daten haben eine hohe Lese- und eine niedrige Schreib-Frequenz?
- Welche Daten haben eine hohe Schreib- oder Update-Frequenz?
- Welche Joins treten am häufigsten auf?
- Welche Teile der Applikation haben klar definierte Anforderungen und ein Grunddesign?
JAMES ruft für BenutzerInnen die eingetragenen Quellen laufend ab und speichert die neuen Daten in einer Datenbank. Aus einem Eintrag, welcher öfters im System vorkommen kann, wird für jede BenutzerIn ein sogenannter „RankedEntry“ erstellt und extra abgespeichert. Ein solcher Eintrag könnte beispielsweise ein RSS-Artikel eines RSS-Feeds, den mehrere BenutzerInnen abonniert haben, sein. Dann existiert für den Eintrag „X“ ein RankedEntry „X1“ für die erste BenutzerIn und ein RankedEntry „X2“ für die zweite BenutzerIn. Einziger Unterschied innerhalb der beiden RankedEntries ist die jeweilige, von der BenutzerIn abhängige, Bewertung des Artikels. Diese RankedEntries werden beim Besuch der Applikation aus der Datenbank gelesen und der AnwenderIn angezeigt. Dadurch entstehen für die RankedEntries eine hohe Lese- und eine im Verhältnis niedrige Schreib-Frequenz.
Wird ein neuer Eintrag von der externen Quelle geholt und verarbeitet, wird für das Scoring unter anderem eine inhaltsbasierte Analyse durchgeführt. Dabei werden Stoppwörter entfernt und der Text interpretiert. Die dadurch entstehenden GlobalWords haben eine hohe Schreib- und Update-Frequenz. RankedEntries verfügen während der automatisierten Neuberechnung der Bewertung über einen Cronjob ebenfalls über eine hohe Update-Frequenz. Da dies allerdings im Hintergrund stattfindet, ist es zwar nennenswert, aber nicht entscheidend.
Bei JAMES kommt ein dokumentenbasierter Datenbanktyp (MongoDB) zum Einsatz. Die für relationale Datenbanken üblichen Joins sind allerdings trotzdem im Schema vorhanden. Darunter zählt beispielsweise der Join zwischen einer BenutzerIn und einem RSS-Feed. Ein paar Scoring Punkte setzen ebenfalls auf Joins. Hauptsächlich verknüpfen diese Joins einzelne Ratings mit einem Eintrag und / oder mit einer BenutzerIn. Die User- und Entry-Models sind somit tief in der Anwendung verankert.
JAMES hat drei Hauptkomponenten: Message-Pipeline, Scoring und Ember Frontend-Applikation. Dabei ist das Scoring am meisten entkoppelt und kann als eine klar abgegrenzte Einheit angesehen werden. Die Message-Pipeline als Komponente ist mächtig und abgetrennt. Die innere Architektur der Pipeline dagegen ist nicht so einfach zu trennen und die einzelnen Quellen (Facebook, Twitter, RSS) sind tief ineinander verstrickt. Die Frontend-Applikation holt sich über Standard Ruby on Rails Controller die Daten aus dem Backend, ist codetechnisch allerdings extra gehalten.
Durch die Beantwortung der Fragen anhand von JAMES können bereits erste Einblicke und Möglichkeiten erkannt werden, wie die Umstrukturierung aussehen könnte. Aus der ersten Antwort ist zu entnehmen, dass womöglich ein Service für die Auslieferung der RankedEntries erstellt werden könnte. Dieser könnte auf schnelles Lesen spezialisiert werden. Die zweite Antwort deutet darauf hin, einen Service für die Textanalyse zu generieren. Die Erläuterungen zur dritten Frage lassen noch einiges offen, da dies wohl eine besonders schwierige Frage ist. Die Beantwortung der vierten Frage von Dix ergibt, dass sehr wahrscheinlich die Hauptkomponenten in Services aufgeteilt werden müssen. Wie konkret dies aussehen könnte und was für jeweilige Unterservices sich ergeben könnten, ist aber zum jetzigen Zeitpunkt noch unklar.
Nun sollten alle LeserInnen einen ersten Eindruck und erste Ideen entwickelt haben, wie die Umstrukturierung vollzogen werden könnte. Um auf die Unterteilung genauer eingehen zu können, folgen in den nächsten Unterkapiteln verschiedene Varianten, wie die Aufteilung bzw. Partitionierung aussehen könnte.
8.2 Partitioning on Iteration Speed
„Partitioning on Iteration Speed“ ist der erste von Dix beschriebenen Ansätzen zur Unterteilung einer monolithischen Ruby on Rails-Applikation in mehrere Services, um eine SOA Infrastruktur zu erhalten. Dem Namen dieser Variante ist bereits zu entnehmen, dass die Geschwindigkeit der Entwicklung einzelner Komponenten für diesen Ansatz ausschlaggebend ist. Hier müssen EntwicklerInnen entscheiden, welche Module einer monolithischen Anwendung welchen Iterationszyklus haben und dementsprechend Unterteilungen in Services vornehmen. Es gibt wahrscheinlich Komponenten, die nach erfolgreicher Entwicklung nie mehr geändert werden müssen. Andere hingegen benötigen laufende Wartungs-, Erweiterungs- oder Anpassungsarbeiten seitens der ProgrammiererInnen. Diese beiden unterschiedlichen Modul-Typen (einmalige vs. laufende Programmierung notwendig) können die Grundbausteine für verschiedene Services darstellen. (Dix 2011, 60)
Laut Dix sind existierende Ruby on Rails-Anwendungen, welche schrittweise in Services unterteilt werden, ein perfektes Beispiel für diese Art zu partitionieren. Anfangs wird die Anwendung monolithisch implementiert. Nach und nach erreichen einzelne Komponenten einen hohen Stabilitäts- und Reifegrad. Diese Bereiche können anschließend von der monolithischen Anwendung extrahiert und in einen Service gepackt werden. Dadurch wird die Code-Basis erleichtert und kann fokussiert werden. Das Wichtigste ist dabei, gemäß Dix’ Ausführungen, dass der extrahierte Bereich einzeln weiterentwickelt und verbessert werden kann. So kann die einmalig programmierte Komponente, welche im Gesamtsystem an und für sich nicht mehr verändert werden würde, in einzelner Form optimiert werden. Dies hat den großen Vorteil, dass Feinheiten herausgearbeitet, Performance verbessert und die Sicherheit verstärkt werden kann, ohne dabei die Gesamtanwendung bearbeiten zu müssen. Wäre die unterteile Komponente im Gesamtsystem geblieben, hätten EntwicklerInnen womöglich diese Optimierungen niemals, oder nur mit vielen Unannehmlichkeiten, durchgeführt. (Dix 2011, 60)
Bereiche, welche häufig und laufend geändert werde müssen, sollten bei diesem Ansatz in der normalen Anwendung bleiben und nicht aufgeteilt werden. Es ist laut Dix sinnvoller, diese innerhalb des Ruby on Rails Frameworks (Ruby on Rails vgl. 5.1) zu belassen. Features, welche ständig geändert werden müssen und somit schnelle Iterationszyklen haben, sollten die Standard Rails Komponenten (vor allem Views und Controllers) verwenden können. Dadurch kann der schnelle Rails-Style durchgeführt werden, um die schnelllebigen Module zu entwickeln.
Der Kern dieses Ansatzes ist die Identifikation der Teile einer Applikation, welche die Schlüsselfunktionalitäten beinhalten und zugleich kaum geändert werden müssen. Laut Dix muss allerdings aufgepasst werden, dass dadurch nicht wieder alles in die monolithische Anwendung zurückgedrängt wird.
Die Beispielapplikation JAMES ist für diesen Ansatz perfekt gewählt. Dix sagt, wie bereits oben angesprochen, dass bestehende monolithische Ruby on Rails-Applikationen nach und nach stabile Komponenten beinhalten. JAMES stellt eine bestehende Anwendung dar und die Kernkomponenten sind eindeutige Kandidaten für eine solche Partitionierung.
Wird diese Art zu partitionieren somit auf JAMES umgelegt, könnte sich Folgendes ergeben:
- Message-Pipeline
- Scoring-System
Die Message-Pipeline ist zum jetzigen Zeitpunkt (29.07.2013) eine stabile Komponente, welche nicht mehr häufig geändert wird. Würde jetzt beispielsweise ein neuer Quelltyp hinzugefügt werden, müsste die Pipeline angepasst werden. Diese Anpassung kann auch in einem weiteren Unterservice stattfinden. Diese Komponente ist somit ein eindeutiger Kandidat für eine Extra-Anwendung bzw. ein abgetrennter Service für den Ansatz „Partitioning on Iteration Speed“. Wahrscheinlich ist die gesamte Pipeline als ein Service zu groß und könnte weiter in Unterservices gesplittet werden.
Ein zweiter Kandidat ist, nach der Meinung des Autors, das Bewertungssystem von Artikeln. Dabei geht es um die Backend-Komponente und nicht um das Frontend. Das Frontend beinhaltet beispielsweise die Logik, wie eine BenutzerIn eine Artikel-Bewertung durchführen kann. Das Backend umfasst die Analyse und entsprechende Berechnung des Scores eines bestimmten Artikels. Dieses Modul lässt sich in einen Service auslagern. Zurzeit wird es nicht häufig abgeändert und läuft in einer stabilen Version. Bei späteren Anpassungen hätte ein extra Service wahrscheinlich nur Vorteile. Nur dieser Service muss abgeändert werden, andere Bereiche bleiben davon unberührt.
Nach der Meinung des Autors lassen sich nur diese zwei Komponenten von JAMES extrahieren. Die Bereiche Ember Frontend-Applikation und User-Verwaltung (Authentifizierung) sollten in der Ruby on Rails-Applikation verweilen. Grund dafür ist, das noch nicht ausgereifte Frontend sowie die Erkenntnisse aus der Beta-Phase, welche noch zu einigen Veränderungen des Designs führen könnten. Zu einem späteren Zeitpunkt macht eine Auslagerung des Frontends mehr Sinn, um die Vorteile der komponentenunabhängigen Programmierung durch Services nutzen zu können. Die Benutzerverwaltung ist für diese Art zu partitionieren zu tief in die Applikation integriert und ein einfaches Loslösen und Extrahieren in einen eigenen Service ist somit nicht so einfach möglich.
8.3 Partitioning on Logical Function
Der zweite Ansatz eine Applikation aufzusplitten, der von Dix in seinem Referenzwerk anführt wird, nennt sich „Partitioning on Logical Function“. Dabei geht es grundlegend darum, dass die Partitionierung in einzelne Services basierend auf deren logische Funktionen vonstatten geht, d.h. EntwicklerInnen müssen jede einzelne Funktionalität in einer Applikation überprüfen und entscheiden, welche Teile logisch zusammenpassen. (Dix 2011, 61) Für einige Bereiche ist dies eine leichte Entscheidung, andere wiederum lassen sich nicht so leicht einordnen.
Services, welche sich beispielsweise um E-Mail Sendungen kümmern, das Monitoring übernehmen, Statistik-Daten sammeln oder Verknüpfungen mit externen Daten-Entitäten herstellen, zählt Dix als Beispiele auf. (Dix 2011, 61) Diese Services separieren Logik von der restlichen Applikation. Sie können auch meistens in mehreren Applikationen, ohne größere Abänderungen, wieder verwendet werden.
Services, welche eher als „Lower-level“ eingestuft werden, sind mitunter auch basierend auf logischen Funktionen partitioniert. Darunter zählt beispielsweise ein Key-Value-Store, welcher für mehrere Services sinnvoll ist oder ein Message-System. Diese Arten von Services haben eine Funktion, welche sie anbieten. Diese Funktion kann über alle Services in einem System geteilt werden. Dix zählt als praktisches Beispiel dafür den Amazon S3 Service auf. (Dix 2011, 61f)
Laut Dix ist auch möglich, dass Teile wie Ruby on Rails-Views oder Controllers basie-rend auf deren logische Funktionen partitioniert werden können. Beispielsweise könnte eine Kommentarfunktion, welche Anmerkungen zu verschiedensten Ressourcen (z.B Seiten, Blogposts, Foreneinträge, etc.) ermöglicht, als extra Service gestaltet sein. Die Logik dahinter ist bei den unterschiedlichen „Clients“ (Seiten, Blogposts, Foreneinträge, etc.) die gleiche. Eine BenutzerIn kann einen Kommentar verfassen und diesen abschicken. Um die Kommentare passend zu den entsprechenden Einträgen anzeigen zu können, könnte in der Folge ein Aufruf direkt an diesen Service gehen. Dieser gibt als Antwort ein passendes HTML-Snippet zurück. Der Kommentar kann angezeigt werden. (Dix 2011, 62)
Der Autor stuft diese Art eine Anwendung zu partitionieren als detaillierter ein. „Partitioning on Iteration Speed“ war eine erste Grobunterteilung, bei dieser Variante wird das Ganze bereits feinfühliger. Wird diese Art zu partitionieren in der Folge konkret auf JAMES angewandt, könnten einzelne Teile wie folgt aussehen:
- Message-Pipeline - ExistenceChecker Worker
- Message-Pipeline - FetchDetails Worker Readability
- Message-Pipeline - Facebook - Twitter - RSS - URL
- User - Authentifizierung
- Statischer Inhalt
Der ExistenceChecker Worker überprüft, ob einzelne Artikel bereits in der Datenbank gespeichert sind oder nicht. Dabei stellt die Datenbankabfrage und Suche nach dem jeweiligen Artikel die zentrale Logik dar. Zusätzlich muss dieser Worker mit Redis arbeiten, eventuell Redis-Keys updaten und einzelne BenutzerInnen zu einem Artikel hinzufügen. Die Grundlogik ist bei jedem Typ (Facebook, Twitter, RSS) genau gleich. Dadurch wäre dieser Worker ein Kandidat für einen globalen ExistenceChecker Service. Dies würde beispielsweise im Falle einer Änderung des Datenbanktyps, sowie der Verwendung eines anderen Key-Value-Stores, von Vorteil sein. Andererseits können dadurch zusätzliche, zum Teil überflüssige, Calls entstehen. Sollte beispielsweise eine Aufteilung der Pipeline in einzelne Services für jeden Typ erfolgen, wäre vielleicht der ExistenceChecker pro Typ innerhalb des jeweiligen Services einfacher. Dies sind allerdings nur Spekulationen. Um am Beispiel für „Partitioning on Logical Function“ die Unterteilung durchzuführen, wäre ein globaler ExistenceChecker eine gute Möglichkeit.
Bis jetzt wird innerhalb von JAMES zur Aufbereitung von Inhalten ein externer Dienst namens „Readability“ verwendet. Dafür existiert eine Hilfsklasse innerhalb des FetchDetails Workers. Dieses Werkzeug zur Textaufbereitung beinhaltet eine abgeschlossene Logik und wäre ein Kandidat für einen eigenen Service, gemäß „Partitioning on Logical Function“.
Im Prinzip ist momentan jeder einzelne Quelltyp in jedem einzelnen Worker in der Pipeline vertreten. Eine Aufsplittung in jeweils einzelne Services für Facebook, Twitter und RSS sowie den Sondertypen URL (wird von den anderen Typen extrahiert) wäre eine sehr grobe Unterteilung. Jeder dieser Typen hat seine eigenen logischen Funktionen und Eigenschaften. Eine Partitionierung wäre somit auf logischer Funktionsebene durchaus möglich. Die eigentliche Logikbündelung müsste wahrscheinlich in jeweiligen spezifizierten Unterservices stattfinden.
Die Message-Pipeline bietet, nach der Meinung des Autors, eine Menge an Möglichkeiten eine Partitionierung in einzelne Services zu vollziehen. Dabei dürfen allerdings die anderen Komponenten der monolithischen Ruby on Rails-Applikation JAMES nicht vergessen werden. Das User-Model steht eng im Bezug zum Thema Authentifizierung. Zurzeit können sich BenutzerInnen über Facebook, Twitter und eine normale JAMES-Registrierung innerhalb der Anwendung authentifizieren. Die gemeinsame Logik in diesem Bereich ist somit deutlich erkennbar und ein eigener User-Authentifizierungsservice wäre durchaus möglich.
JAMES verfügt über ein paar statische Seiten, welche kaum geändert werden. Hierfür könnte ein StaticService implementiert werden. Die statischen Seiten verfügen über die gleiche Logik, sie verwenden alle den gleichen Controller und die gleichen View-Templates. Eine Auslagerung wäre demnach eine Möglichkeit. Da dies allerdings nur ein ganz kleiner Service wäre, hätte diese Aufsplittung nach der Meinung des Autors weniger Priorität als andere Services. Allerdings würde ein solcher StaticService trotzdem Vorteile bringen. In Zukunft könnten ja noch einige statische Seiten wie „Terms of Service“ oder „FAQ“ dazukommen.
Dies sind nur einige Möglichkeiten innerhalb der Applikation JAMES „Partitioning on Logical Function“ durchzuführen. Vielleicht kann der eine oder andere beschriebene Ansatz ins Ergebnis dieser Arbeit überführt werden.
8.4 Partitioning on Read / Write Frequencies
Ein Blick auf die Lese- und Schreib-Frequenzen (Read / Write) von Daten innerhalb einer Applikation ist auch eine Möglichkeit, die EntwicklerInnen helfen könnte zu entscheiden, wie die Partitionierung in einzelne Services am besten funktionieren könnte. Es könnte Daten geben, die oft upgedated werden, dafür fast nie gelesen werden. Andere werden einmal abgespeichert und dafür oft gelesen. Diese Frequenzen können natürlich unterschiedlich sein. Der Grund, warum aufgrund von solchen Lese- und Schreibfrequenzen partitioniert werden kann, liegt in den Möglichkeiten der Optimierungen einzelner Datenspeicher. (Dix 2011, 62) Die eine Datenbank ist für den Lesezugriff optimiert, bei der anderen liegt die Stärke in der Schreibgeschwindigkeit. Laut Dix sollte ein Service idealerweise nur mit einem einzigen Datenspeicher (z.B. einer MySQL Datenbank) zu tun haben und alle relevanten Datenzugriffe über diesen regeln.
Zukünftige Umsetzungsentscheidungen sollten mit der Partitionierung vonstattengehen, d.h. für Daten mit einer hohen Lese-Frequenz sollte die Caching-Strategie des Services optimiert sein. Services mit großen Schreib-Frequenzen müssen sich dagegen mit dem Thema Caching nicht beschäftigen und deshalb auch nicht darauf optimiert sein. Hier sind klare Vorteile in der Aufteilung in Services erkennbar. Eine solche Aufsplittung in verschiedene Services macht die Organisation von jedem einzelnen Service deutlich einfacher. (Dix 2011, 62) Die Optimierungen müssen nicht auf Lese- und Schreib-Frequenz ausgerichtet sein, sondern können speziell auf den jeweiligen Fall ausgelegt werden. Dies spart den EntwicklerInnen viel Implementierungszeit und macht den Code deutlich einfacher. Sollte ein Service jetzt auf beides (Read / Write) optimiert sein, hat dies oft größere Umstände zur Folge. Diese können auftreten, weil ein Service beispielsweise für jeden Schreibzugriff das Caching-Konstrukt mitbehandeln muss oder andere Extras notwendig sind.
Bei der Beispielapplikation JAMES könnte diese Variante der Partitionierung ebenfalls in die Überlegungen der Umstrukturierung in die neue SOA einfließen. Der Autor hat folgende Möglichkeiten anhand von JAMES herausgearbeitet:
- RankedEntries - Auslesung für Frontend
- Entries - Speicherung im Backend
- GlobalStream - Globaler RSS Feed
Besucht eine BenutzerIn ihren JAMES-Account, so werden alle Artikel zu den hinterlegten Quellen angezeigt. Um dies zu ermöglichen, müssen sie schrittweise aus der Datenbank ausgelesen werden. Dies erfolgt über einen normalen Rails Controller und zusätzlich für die Paginierung über eine Ajax-Action innerhalb des Controllers. Die RankedEntries selber werden somit oft gelesen und im Verhältnis nur einmal geschrieben und kaum verändert. Hier könnte somit „Partitioning on Read / Write Frequencies“ stattfinden. Für das Auslesen Caching zu verwenden ist nicht sinnvoll, da diese Daten auch aktuell gehalten werden müssen. Eventuell könnte ein Service implementiert werden, der auf einen speziell auf Lesezugriffe optimierten Datenbanktyp zugreift.
Neue Artikel (Entries) werden von externen Quellen geholt und in der Datenbank abgespeichert. Diese Daten werden kaum ausgelesen, sondern nur gespeichert. Dieses Speichern geschieht im StoreDb Worker. Dieser würde sich daher anbieten, in einen externen Service ausgelagert zu werden. Dieser Worker macht zusätzlich bei allen Typen fast das Gleiche und ist nicht tief in Unterklassen verstrickt. Die Abspeicherung von neuen Einträgen findet hier statt. Dieser Service sollte folglich auf schnelle Schreibzugriffe spezialisiert und optimiert sein.
Fügt eine BenutzerIn einen RSS Feed als Quelle hinzu, wird dieser abgespeichert. Gibt es ihn bereits in der Datenbank, so wird nur die Beziehung BenutzerIn - GlobalStream gesetzt, der GlobalStream-Eintrag selber bleibt dabei unverändert. Somit wäre wieder ein Ansatz vorhanden, um „Partitioning on Read / Write Frequencies“ durchzuführen und diese Komponente in einen einzelnen Service auszulagern.
Die erläuterten Beispiele bei JAMES „Partitioning on Read / Write Frequencies“ anzuwenden zeigen auf, dass diese Variante noch eine Spur mehr ins Detail der Anwendung geht. Die einzelnen Services, welche entstehen würden, wären bereits viel spezialisierter und kleiner als bei den Beispielen im Unterpunkt „Partitioning on Iteration Speed“. Nach der Meinung des Autors gehen die angeführten Beispiele bzw. Möglichkeiten, welche bei JAMES anfallen, bereits in die Kategorie „nice-to-have“. Dies bedeutet für den Autor, dass solche Optimierungen eher gegen Ende eines Neudesigns der Architektur von JAMES durchgeführt würden.
8.5 Partitioning on Join Frequency
Die in dieser Arbeit als Letztes vorgestellte Variante eine monolithische Ruby on Rails-Applikation in Services aufzuteilen trägt den Namen „Partitioning on Join Frequency“. Laut Dix ist diese Variante die verlockendste um Service-Verbindungen über Joins zu minimieren. Joins in relationalen Datenbanken kosten immer Zeit, deshalb sollten solche Joins auch nicht über verschiedene Services hinweg bestehen. EntwicklerInnen wollen nicht, dass ein Service mit einem anderen Service kommunizieren muss, um notwendige Daten zusammenzujoinen. Es sind natürlich deutlich weniger Anfragen nötig, wenn die Durchführung innerhalb der Grenzen eines Services möglich ist. Dies ist jedoch oft nicht handhabbar, da fast alle Daten in einer Applikation an anderen Daten hängen und mit diesen verbunden sind. (Dix 2011, 62)
Wird das Konzept der Join-Minimierung logisch herangezogen, endet die Architektur wieder in einem monolithischen Ansatz. Will eine EntwicklerIn allerdings unbedingt eine Aufteilung in Services, um bereits erwähnte Vorteile erzielen zu können, so müssen laut Dix klarerweise schwere Entscheidungen in Bezug auf Joins getätigt werden.
Laut Dix ist die Partitionierung nach logischen Funktionen und Lese-sowie Schreib-Frequenzen ein guter Startpunkt. Zuerst sollten Daten nach diesen Kriterien zusammengefasst werden. Um Joins gut einteilen zu können, sollte zuerst festgestellt werden, wie oft bestimmte Joins vorkommen. Ein Ansatz ist, die Daten an einer zentralen Stelle zu hinterlegen. Auf diese können anschließend die Services direkt zugreifen und müssen nicht über den jeweils anderen Service Daten abfragen.
Ein anderer Ansatz, um Joins zu minimieren, ist die Replikation von Daten über Services hinweg. Im Fachjargon von relationalen Datenbanken wird hierbei von „Denormalisierung“ gesprochen. Dabei werden notwendige Daten dupliziert, sodass jeder Service innerhalb seiner Grenzen alle Daten zur Verfügung hat. Ein anderer Service hat ebenfalls auf alle notwendigen Daten zugriff. Verändert sich ein Datensatz in der Datenbank von Service 1, so muss dieser auch in der Datenbank von Service 2 angepasst werden, dies erfolgt analog bei neuen und geänderten Datensätzen. Die gegenseitige Benachrichtigung über Veränderungen kann beispielsweise über Messaging gelöst werden. Die Services sind dadurch unabhängig, die gegenseitige Benachrichtigung kann allerdings nicht umgangen werden. (Dix 2011, 62)
JAMES verwendet, wie bereits angesprochen (vgl. 6.1), als Datenbank MongoDb und somit keinen relationalen sondern, einen dokumentenorientierten Datenbanktyp. Trotzdem befinden sich einige Joins in der Applikation. Aufgrund dessen ist auch diese Variante der Partitionierung bei JAMES möglich. Für den Autoren wäre folgender Punkt ein passendes Beispiel, bei dem „Partitioning on Join Frequency“ Sinn ergeben könnte:
- Entries - Users
Ein Artikel hat bei JAMES eine Verknüpfung zu vielen BenutzerInnen und zu vielen anderen Artikeln. Diese Verbindungen sind somit Joins, die im Kern des Datenbankschemas vorkommen. Würde jetzt beispielsweise der Ansatz „Partitioning on Logical Function“ und dabei der Punkt „Message-Pipeline - Facebook - Twitter - RSS - URL“ durchgeführt werden, so könnte jeder der neu entstehenden Services über eine extra Datenbank verfügen. Die Joins könnten über eine zentrale Datenbankstelle realisiert werden. Dies wäre eine, wahrscheinlich komplizierte, Möglichkeit die Unabhängigkeit der einzelnen Services zu wahren. Eine andere Möglichkeit wäre es, alle Typen in einer Datenbank zu verwalten und die Joins dadurch zu realisieren. Die Verbindung zur BenutzerIn könnte über ein extra Attribut bei der BenutzerIn vollzogen werden.
Damit sind die verschiedenen Varianten zu partitionieren erläutert und dieser Bereich abgeschlossen. Im nächsten Unterkapitel erfolgt die Umsetzung und Entwicklung der neuen SOA für die Beispielapplikation JAMES anhand des Prototyps. In diesem Abschnitt wird die Forschungsfrage der vorliegenden Masterarbeit beantwortet.
8.6 Umsetzung
In diesem Unterkapitel findet die Aufteilung der Software Architektur der Beispielapplikation JAMES statt. Dabei entsteht eine SOA. JAMES soll somit in einzelne Komponenten bzw. Services partitioniert werden. Der in den vorigen Kapiteln detailliert vorgestellte Prototyp definiert den Rahmen der neuen Architektur. Die einzelnen Services verwenden die Servicesregistry und den Servicesmaster. Der Ablauf funktioniert dabei gleich wie beim einführenden Beispiel „NewsService“, die Komplexität und Anzahl der Services wird aber wohl deutlich höher sein.
Der Autor muss hier anmerken, dass dieses Unterkapitel auf die konzeptionelle Umstrukturierung eingeht. Es wird erläutert, welche neuen Services entstehen und warum diese zum Einsatz kommen, jedoch wird nicht auf deren Implementierung eingegangen. Eine detaillierte Beschreibung jeder neuen Komponente in Bezug auf die programmiertechnische Umsetzung würde den Rahmen dieser Masterarbeit sprengen. Die neuen Services sind teilweise bereits vom Autor praktisch umgesetzt worden, andere Teilbereiche noch nicht.
In den vorigen Unterabschnitten wurden bereits einige Möglichkeiten der Partitionierung aufgezeigt. In diesem Unterkapitel können LeserInnen Querverbindungen und Bezüge zu diesen Möglichkeiten feststellen. Dies soll die theoretischen Ansätze mit der Praxis verbinden, um zu einer fundierten Beantwortung der Forschungsfrage zu kommen.
Die Partitionierung von JAMES startet mit einer Aufteilung der Message-Pipeline. Dies endet in folgenden neuen Service-Modulen:
- FacebookStreamFetcherService
- TwitterStreamFetcherService
- RssStreamFetcherService
- StreamFetcherService
- ExistenceCheckerService
- FacebookDetailsFetcherService
- TwitterDetailsFetcherService
- RssDetailsFetcherService
- UrlDetailsFetcherService
- ReadabilityService
- DetailsFetcherService
- StoreDbService
- RankService
- IntervalCheckerService
Dieser Gem-Service wird vom bestehenden „StreamFetcher“ extrahiert. Das hat den Vorteil, dass er unabhängig und eigenständig entwickelt werden kann. Dies folgt dem Ansatz von „Partitioning on Logical Function“ (vgl. 8.3). Die Logik wird dabei entkoppelt. In dieser Aufteilung kann auch eine Verbindung zum Ansatz „Partitioning on Iteration Speed“ (vgl. 8.2) gezogen werden. Dieser Bereich der Message-Pipeline kann als konstante Komponente betrachtet werden. Folgen Änderungen seitens Facebook an der API muss nur dieser Service dementsprechend abgeändert werden. Ansonsten wird er programmiertechnisch kaum verändert, was auf einen langsamen Iterationszyklus deuten lässt.
Innerhalb des Servicesmasters können die optionalen Attribute bei einer Environment (Auth-Token und Auth-Secret) genutzt werden, um die Daten für die Facebook-Applikation zu verwalten (vgl. 7.1).
Die neue Komponente TwitterStreamFetcherService wird ebenfalls aus dem „StreamFetcher“ extrahiert und in einen neuen Gem-Service verpackt. Der Grund dafür ist in den gleichen Bereichen wie beim bereits vorgestellten neuen Service zu finden. Zusätzlich spielt im Falle von Twitter die einfache Testbarkeit des Services eine große Rolle. Der FacebookStreamFetcherService sollte zwar auch auf automatisierte Tests gestützt sein, diese können im Falle von Facebook aber mithilfe eines Gems gut ausgemockt werden. Facebook stellt hierfür eine eigene Test-API zur Verfügung.
Twitter besitzt leider keine solche API und die Tests müssen mit angelegten Fake-Accounts durchgeführt werden. Dies bedeutet eine große und direkte Abhängigkeit von der Twitter-Plattform. Durch die Aufteilung in einen eigenen Service kann diese Abhängigkeit zwar leider nicht umgangen werden, allerdings ist nur mehr die einzelne Komponente davon betroffen und nicht mehr die gesamte Applikation.
Der RssStreamFetcherService ist der dritte neu entstehende Gem-Service innerhalb der neuen JAMES Architektur. Er umfasst das RSS Modul, welches RSS-Quellen einer BenutzerIn abfragt und neue Daten aufnimmt. Die vorig vorgestellten zwei Services dienen diesem Service als Vorlage. Er wird ebenfalls aus dem „StreamFetcher“ Worker entnommen und in einen eigenen Service gepackt.
Durch die bisherigen drei neuen Services fällt der „StreamFetcher“ Worker aus dem JAMES-Projekt heraus, da er unnötig geworden ist. Die drei neu entstandenen Services werden jetzt unter einen StreamFetcherService gereiht. Diese Reihung kann mit Abbildung 1 aus dem Abschnitt „Service Orchestrierung“ (vgl. 2.4.1) verglichen werden.
Der StreamFetcherService kontrolliert die Abläufe des FacebookStreamFetcherServices, des TwitterStreamFetcherServices und des RssStreamFetcherServices. Dabei geht die Kontrolle allerdings nur soweit, dass über diesen Service alle Unterservices auf einmal angesprochen werden können, um die Abfrage nach neuen Entries gestaffelt starten zu können. Der StreamFetcherService kann hier mit dem NewsService aus dem Einführungsbeispiel (vgl. 7.2) verglichen werden. Innerhalb dieses Services werden der Articles- und der Books-Service aufgerufen. Beim StreamFetcherService erfolgt der Aufruf der jeweiligen Unterservices.
Dies hat den Vorteil, dass jede einzelne Komponente unabhängig implementiert und gewartet werden kann. Die Ausführung wird durch diesen „Wrapper“-Service erleichtert. Wird in Zukunft ein neuer Quelltyp (z.B. Youtube) hinzugefügt, kann wieder ein passender Unterservice entwickelt werden. Dieser kann über den Stream-FetcherService aufgerufen werden. Zusätzlich ist das direkte Aufrufen der einzelnen Unterservices von anderen Services möglich. Dadurch entsteht für EntwicklerInnen eine hohe Flexibilität.
Der „ExistenceChecker“ Worker überprüft für jeden neuen Eintrag, ob dieser in der Datenbank abgespeichert oder schon in der Prozesskette in Bearbeitung ist. Zusätzlich überprüft er, ob BenutzerInnen mit diesem Eintrag verknüpft sind oder nicht. Gegebenenfalls werden die entsprechenden Verknüpfungen gesetzt. Um dies durchführen zu können, benötigt dieser Worker die BenutzerInnen-IDs, welche dem Eintrag zugeordnet sind. Dafür gibt es für jeden Quelltyp eine Unterklasse, die diese IDs liefert.
Die eigentliche Überprüfung funktioniert für Facebook-, Twitter-, RSS- und URL- Einträge gleich. Aus diesem Grund wird ein neuer ExistenceCheckerService eingeführt. Dieser lagert den bestehenden „ExistenceChecker“ Worker in eine eigenständige Komponente aus. Die Unterklassen, welche die IDs liefern, werden entfernt. Beim Aufruf des neuen Services müssen die IDs als Parameter übergeben werden. Da in der Prozesskette der vorige Service ein StreamFetcherService für den jeweiligen Quelltypen darstellt, ist die ID-Übergabe kein Problem. Der vorige Service übernimmt das Laden der IDs aus der Datenbank, da er sich bereits auf der Ebene des jeweiligen Typs befindet. Dieser Service ist als Gem implementiert.
Diese Vorgehensweise hat den Vorteil, dass der Programmcode reduziert und die Logik gebündelt wird. Eine Generierung von ExistenceCheckerServices für jeden Quelltypen macht an dieser Stelle, nach der Meinung des Autors, keinen Sinn. Zu viele Services bedeuten mehr Komplexität und die Abwägung hat für diesen Bereich der Message-Pipeline ergeben, dass ein Service für diese Aufgabe ausreichend ist.
Der „FetchDetails“ Worker funktioniert zurzeit als stabile Komponente, darf allerdings an keiner Stelle deutlich abgeändert werden, da ansonsten einige Klassen angepasst werden müssten, die mit der eigentlichen Änderung nichts zu tun haben. Aus diesem Grund wird ein neuer Service namens FacebookDetailsFetcherService eingeführt. Dieser entkoppelt die Facebook-Logik vom „FetchDetails“ Worker und folgt damit dem Ansatz „Partitioning on Logical Function“ (vgl. 8.3).
Durch diesen Gem-Service ergeben sich Vorteile in Bezug auf die Code-Übersicht, Wartbarkeit und Robustheit. Da der „FetchDetails“ Worker die gemeinsame Logik von Facebook, Twitter und RSS verbindet, entsteht das eine oder andere Code-Duplikat. Diese Vervielfältigungen müssen in Kauf genommen werden, um die angesprochenen Vorteile nutzen zu können.
LeserInnen können hier bereits Parallelen zu den StreamFetcherServices erkennen. Der TwitterDetailsFetcherService folgt dem Beispiel des FacebookDetailsFetcher-Services. Die Logik wird wiederum entkoppelt und aufgeräumt.
Auch dieser Service folgt den gleichen Prinzipien wie der FacebookDetailsFetcher-Service. Der Quelltyp RSS kann somit leichter verarbeitet werden. In diesem Bereich liegen auch noch potentielle Erweiterungsmöglichkeiten und Verbesserungen, welche eingebaut werden können. Der Autor denkt, dass diese Komponente noch des Öfteren abgeändert werden muss und sich dadurch ein schnellerer Iterationszyklus als wie bei dem FacebookDetailsFetcherService und TwitterDetailsFetcherService entwickeln könnte. Deshalb können hier auch Parallelen zum Ansatz „Partitioning on Iteration Speed“ (vgl. 8.2) gezogen werden.
Diese neue Komponente beinhaltet den Sondertypen „URL“, welcher sich aus der Aufbereitung der Inhalte der anderen Typen ergibt. Kommt innerhalb eines Facebook-Posts beispielsweise ein Link vor, welcher auf einen Online-Blog verweist, so wird diese URL aufgelöst und der Content des Online-Blogs aufbereitet. Dies geschieht im Rahmen des URL-Typs. Bisher war dieser Typ innerhalb des „FetchDetails“ Workers in einigen Unterklassen inkludiert und hat immer wieder zu if-else- Bedingungen im Code geführt.
Durch die Entkoppelung dieser Logik in einen separaten Service entstehen viele Vorteile. Die Sonderbedingungen müssen nicht in jeder Unterklasse beachtet werden, sondern werden in einen eigenen Service verpackt. Kommt beispielsweise im neuen Service FacebookDetailsFetcherService ein Eintrag vor, der eine URL beinhaltet, kann intern der UrlDetailsFetcherService aufgerufen werden und die if-else- Bedingungen können beseitigt werden.
Innerhalb des „FetchDetails“ Workers befindet sich eine Hilfsklasse zur Aufbereitung des HTMLs der einzelnen Einträge. Diese Hilfsklasse verwendet einen externen Dienst namens „Readability“. Diese Klasse wird in einen eigenen Gem-Service verpackt, um global in der Anwendung für die Aufbereitung eines HTML-Codes herangezogen werden zu können.
Dies hat den Vorteil, dass die Logik an einem zentral verfügbaren Ort gelagert ist. Zusätzlich kann der externe Service jederzeit ausgetauscht werden. Für diesen Austausch muss nur der eine Service abgeändert werden. Die anderen Services der neuen JAMES Architektur bleiben unverändert. Die Querverbindungen zu „Partitioning on Logical Function“ (vgl. 8.3) und „Partitioning on Iteration Speed“ (vgl. 8.2) können somit auch in diesem Service gezogen werden.
Der DetailsFetcherService übernimmt eine ähnliche Aufgabe wie der bereits vorgestellte StreamFetcherService. Er soll als „Wrapper“-Service für die einzelnen Quelltypen-DetailsFetcherServices dienen und übernimmt die Koordination und den gestaffelten Zugriff auf die Unterservices. Der „FetchDetails“ Worker wird aus dem JAMES-Projekt entfernt.
Der StoreDbService ersetzt den „StoreDb“ Worker. Dieser Service speichert den jeweiligen neuen Eintrag in der Datenbank ab und setzt ein Attribut namens „last_fetched_at“. Dieses Attribut dient dem „IntervalChecker“ Worker um feststellen zu können, wann ein Eintrag das letzte Mal abgerufen wurde. Dieses „last_fetched_at“ muss für jeden Quelltypen unterschiedlich gesetzt werden.
Die bisherige Lösung mit Unterklassen, welche typspezifisch das „last_fetched_at“ setzen, bleibt in der neuen Architektur bestehen. Der Grund dafür ist, dass der „StoreDb“ Worker, ähnlich wie der alte „ExistenceChecker“ Worker für jeden Quelltyp die gleiche Logik verfolgt. Aus diesem Grund wird der gesamte Worker einfach in eine eigene Komponente ausgelagert. Dieser Ansatz folgt dem „Partitioning on Logical Function“ (vgl. 8.3) Prinzip.
Der „StoreDb“ Worker besitzt zusätzlich eine Unterklasse „Layout“. Diese ist dafür zuständig, dass für einen Eintrag das passende Layout gesetzt wird. Dieser Bereich verbleibt ebenfalls im neuen StoreDbService. Der Grund dafür ist, dass sich diese Komponente mit den Erkenntnissen aus der Beta-Phase des Projekts deutlich verändern kann und daher noch öfters bearbeitet werden muss (Stichwort „Partitioning on Iteration Speed“ (vgl. 8.2)).
Aus diesem neuen Gem-Service erfolgt in Zukunft auch ein Aufruf an den ScoringService, dieser wird zu einem späteren Zeitpunkt vorgestellt.
Der bestehende „Rank“ Worker führt Scoring-spezifische Aufrufe durch, um einem Eintrag einen Score geben zu können. Dieser Worker wird in den RankService ausgelagert und fällt somit weg. Der RankService fungiert in Folge dessen als eine Art „Wrapper“-Service für die Scoring-spezifischen Services und führt im Rahmen der Prozesskette Aufrufe betreffend des Scorings durch.
Durch den RankService wird der Grad der Unabhängigkeit innerhalb der neuen JAMES-Architektur weiter erhöht. Diese Komponente wird sich kaum ändern, da sie nur weitere Services aufruft, selber allerdings kaum Logik enthält. Dadurch kann wiederum der Ansatz von „Partitioning on Iteration Speed“ (vgl. 8.2) herangezogen werden. Dies ist eine weitere stabile Komponente, welche als unabhängiges Modul funktioniert und dabei gut wartbar und robust ist.
Der IntervalCheckerService ersetzt den „IntervalChecker“ Worker. Dieser stößt in bestimmten Intervallen die Message-Pipeline an. In der neuen Architektur ruft der IntervalCheckerService den StreamFetcherService auf und startet damit die Prozesskette. Dieser Gem-Service macht Sinn, da er EntwicklerInnen zusätzliche Flexibilität und Einfachheit schaffen kann.
Damit ist die Aufteilung der Message-Pipeline in vierzehn neue Services erfolgreich beendet und es folgen die Erklärungen zur Aufteilung des Scoring-Systems in verschiedene Module.
- ScoringAnalyserService
- EntryScorerService
- WordScorerService
- AttributeScorerService
Der neue Gem-Service ScoringAnalyserService extrahiert die Logik der Scoring Unterklasse „Analyser“. Dabei geht es um die Analyse des Inhalts mithilfe von Solr. Es werden zum einen Wörter aus den Texten entnommen und gewichtet, zum anderen erfolgt eine Indexierung des Eintrags in Solr. Dies dient als Vorbereitung für eine spätere, genauere Spezifizierung des Scores. Hierbei wird der Ansatz von „Partitioning on Logical Function“ (vgl. 8.3) angewendet.
Das Scoring-Modul besitzt im Moment eine EntryScorer Unterklasse, welche in einen eigenen Gem-Service ausgelagert wird. Innerhalb dieser Klasse ist es möglich, unterschiedliche Scorer zu registrieren, welche anschließend automatisiert ins System eingegliedert werden können. Dies verfolgt somit bereits von Grund auf einen serviceorientierten Ansatz.
Deshalb wird diese Klasse in einen eigenen Service ausgelagert, um anschließend als Hauptservice für die verschiedenen weiteren Scorer-Services zu agieren. Dies stellt damit ein eigenes kleines „Unter-SOA-System“ innerhalb der neuen Architektur von JAMES dar. Im Servicesmaster wird nur dieser Service eingetragen und in der Servicesregistry erfasst. Die nachfolgend vorgestellten Unterservices beziehen sich auf den EntryScorerService und haben mit dem restlichen System nichts zu tun.
Durch diesen Aufbau ergeben sich für EntwicklerInnen praktische Möglichkeiten. Es können jederzeit neue Scorer-Unterservices hinzugefügt werden. Angesprochen werden diese direkt über den EntryScorerService. Nur dieser hat Kenntnisse von den Unterservices. Dies kann exakt mit Abbildung 1 aus dem Abschnitt „Service Orchestrierung“ (vgl. 2.4.1) verglichen werden. Der EntryScorerService reagiert als ControllerService über die anderen. Sein Scope umfasst zurzeit die zwei nachfolgend vorgestellten neuen Services „WordScorerService“, „AttributeScorerService“ und sich selbst.
Dieser bereits angesprochene neue Gem-Service wird in das „Unter-SOA-Konstrukt“ des EntryScorerServices integriert. Er bewertet dabei die Wörter innerhalb eines Eintrags und beeinflusst dadurch den Gesamtscore. Dabei handelt es sich um einen recht kleinen Service, welcher jederzeit praktisch und unabhängig von anderen Modulen geändert werden kann (Stichwort: Performance). Er hat nur Kenntnisse über sich selbst.
Dieser neue Gem-Service ist der zweite Unterservice des EntryScorerServices. Er behandelt die Metadaten zwischen einer BenutzerIn und einem Eintrag. Je nachdem wie diese in Beziehung stehen und wie die BenutzerIn mit einem bestimmten Eintrag interagiert hat (z.B. geliked, geteilt, up-gevoted, down-gevoted, etc.), wird der Gesamtscore beeinflusst. Dieser Service versteht nur seine interne Logik und hat außer einer Verbindung zur Datenbank keinerlei Kommunikation nach außen. Er wird innerhalb des EntryScorerServices registriert und nur von diesem aufgerufen.
Diese vier Services bilden zusammen das neue Scoring-System von JAMES. Es könnte zusätzlich noch ein fünfter Service eingeführt werden, der alle Scoring-spezifischen Services umfasst und mit dem StreamFetcherService oder DetailsFetcherService verglichen werden könnte. Zum jetzigen Zeitpunkt (15.08.2013) ist dies nicht geplant. Grund dafür ist, dass bis jetzt kein anderer Service alle Scoring-Services zugleich aufrufen muss. Der RankService nimmt zwar teilweise die Rolle eines „Wrapper“-Services für das Scoring ein, benötigt aber auch nicht alle Services zur selben Zeit.
Damit ist die neue Architektur betreffend Scoring-System von JAMES erläutert worden und es kann zum nächsten Bereich übergegangen werden. Dieser beschäftigt sich mit dem Thema Authentifizierung. Im Speziellen geht es um das Authentifizieren über externe Provider wie Facebook und Twitter.
- AuthService
- FacebookAuthService
- TwitterAuthService
Der neue Service namens AuthService ist nach dem Vorbild des EntryScorerServices gestaltet. Bei diesem Service handelt es sich allerdings um einen lokalen Service, welcher in die Ruby on Rails-Applikation integriert ist. Grund dafür ist, dass die Authentifizierung per externem Aufruf zu Problemen betreffend Sicherheit und Performance führen kann. Dieser Service dient als ControllerService für die nachfolgend erklärten Unterservices. Er stellt eine Stelle für die Registrierung zur Verfügung.
Dies hat den Vorteil, dass beliebig viele externe Provider hinzugefügt werden können. Innerhalb der alten Architektur konnten nur unterstützte Quelltypen, welche auch über einen Oauth-Login verfügen, zur Authentifizierung verwendet werden. Dies kann mithilfe dieses neuen AuthService umgangen werden.
Der FacebookAuthService ist ein lokaler Unterservice des AuthServices und implementiert die Facebook-spezifischen Methoden betreffend einer Authentifizierung über Oauth. Durch diese einzelne Komponente kann leichter auf Änderungen seitens Facebook reagiert werden.
Dieser Service ist ebenfalls ein lokaler Unterservice des AuthServices. Seine Aufgaben sind, Twitter-spezifische Eigenschaften betreffend Authentifizierung zur Verfügung zu stellen. Mithilfe dieses Service soll es BenutzerInnen ermöglicht werden, eine Authentifizierung über die Twitter-Plattform durchzuführen.
Das Thema Authentifizierung innerhalb der neuen JAMES-Architektur wird somit über diese drei neuen Services realisiert. Diese Services sind die ersten lokalen Komponenten, welche innerhalb der Ruby on Rails-Applikation als Module herangezogen werden können. Dadurch ist zum einen eine einfachere Code-Strukturierung möglich, zum anderen können lokale Vorteile wie Session-Management, Sicherheit und Performance in Bezug auf die Authentifizierung verwendet werden.
Die nächsten Punkte beinhalten diverse Services und Komponenten, welche die JAMES-SOA vervollständigen.
- Ruby on Rails-Applikation
- StaticContentService
- LoggingService
Die Ruby on Rails-Applikation bleibt weiter bestehen und fungiert als Routing-Schnittstelle zwischen Frontend- und Backendprozessen. Bereits angesprochene Funktionalitäten (Message-Pipeline, Scoring) befinden sich allerdings nicht mehr in der Applikation selber, sondern sind in Services entkoppelt worden. Die Anwendung ruft diverse Services auf, um Interaktionen von BenutzerInnen und Hintergrundprozesse zu managen.
Die Ruby on Rails-Applikation umfasst nur noch die Ember Frontend-Applikation sowie die BenutzerInnen-Verwaltung. Das Entry-Management wurde in die jeweiligen Services entkoppelt. Die Models, um mit der Datenbank kommunizieren zu können, sind teilweise dupliziert und verlagert worden.
Hier ist anzumerken, dass trotz der Partitionierung in verschiedene Services eine Datenbank zum Einsatz kommt und auf eine Aufteilung der Daten in einzelne kleinere Portionen verzichtet wird. Der Grund dafür ist, dass das Datenbank-Management ansonsten zu kompliziert werden würde. Eine Aufteilung in mehrere Datenbanken und weitere Optimierungen könnte eventuell in Zukunft relevant werden, zum jetzigen Zeitpunkt (15.08.2013) allerdings noch nicht.
Die Ember Frontend-Applikation verbleibt in der Ruby on Rails-Applikation, da diese mit der Asset-Pipeline ein gutes Werkzeug zur Verwaltung von Assets, Javascript- und CSS-Dateien bietet. Hauptgrund ist allerdings, dass diese Komponente noch nicht stabil genug ist. Die jetzige Implementierung beruht auf einer mittlerweile veralteten Version des Ember-Frameworks und sollte auf den neuesten Stand gebracht werden. Zusätzlich ist das Design noch nicht vollständig ausgereift. Außerdem ergeben sich durch die Beta-Phase einige KundInnenwünsche, die umgesetzt werden sollten. Aus all diesen Gründen belässt der Autor diese Komponente innerhalb der Applikation. Hier wird auf den Ansatz „Partitioning on Iteration Speed“ (vgl. 8.2) gesetzt.
Die Logik für die Beta-Authentifizierung wird ebenfalls in der Ruby on Rails-Applikation belassen. Sie wird in ein paar Monaten aus dem Projekt ausscheiden und ist somit zum jetzigen Zeitpunkt nicht als relevant anzusehen.
Der StaticContentService entkoppelt die Logik von statischen Seiten innerhalb der Ruby on Rails-Applikation. Diese sind in einen eigenen Service extrahiert. Durch diesen soll der Deploy von statischen Änderungen erleichtert werden. Hier kann eine Querverbindung zu Loose Coupling (vgl. 2.5) aufgebaut werden. Durch eine eigene Komponente für statische Inhalte können Änderungen in diesem Bereich leichter online gestellt werden. Es müssen keine Hintergrundprozesse oder Worker, die nichts mit den Änderungen zu tun haben, neu gestartet werden.
Dieser Service soll die Logik des Loggings aller Services innerhalb der neuen JAMES-Architektur übernehmen. Wenn möglich wird hierzu der Servicesmaster um eine Logging-Funktion erweitert. Dies wird allerdings nicht im Rahmen dieser Masterarbeit durchgeführt, aber der Vollständigkeit halber trotzdem angesprochen. Ein solcher LoggingService hätte den Vorteil, dass die Log-Dateien zentral abrufbar und einsehbar sind.
Womöglich könnte diese Aufgabe mit einem externen Tool wie „Loggly“ kombiniert werden. Interessierte LeserInnen finden unter folgender URL weitere Informationen zu „Loggly“: http://loggly.com/
Mit diesen letzten Partitionierungen und Strukturierungen ist die Umwandlung der monolithischen Architektur von JAMES in eine serviceorientierte beendet. Die neue Struktur von JAMES beinhaltet eine Ruby on Rails-Applikation mit dreiundzwanzig weiteren Services.
Abbildung 12: Beispielapplikation JAMES: serviceorientierte Architektur
Abbildung 12 fasst die Komponenten der neuen Architektur nochmals zusammen. Wird diese mit der alten Architektur von JAMES (vgl. Abbildung 8) verglichen, ist der serviceorientierte Ansatz klar erkennbar. Die monolithische Ruby on Rails-Applikation beinhaltet nur noch den lokalen AuthService mit den beiden Unterservices (FacebookAuthService und TwitterAuthService) und die Ember Frontend-Applikation, die MVC-Komponenten wurden verkleinert. Die restlichen Module (vor allem Message-Pipeline und Scoring- System) wurden in die verschiedenen neuen Services ausgelagert. Die Datenbank ist, falls notwendig, auch für die neuen Komponenten zugänglich. Die Ruby on Rails-Applikation fungiert als Routing-Schnittstelle in der neuen SOA.
Die zwei gelb hinterlegten Blöcke beinhalten jeweils die StreamFetcherServices und die DetailsFetcherServices für die unterschiedlichen Quelltypen. Jeder dieser Services ist einzeln aufrufbar. Ein gestaffeltes Aufrufen über die „Wrapper“-Services ist ebenfalls möglich. Der EntryScorerService beinhaltet den WordScorerService und den AttributeScorerService. Die restlichen Komponenten sind ebenfalls in der Gesamtübersicht dargestellt. Innerhalb der neuen SOA sind alle Services (mit Ausnahme FacebookAuthService, TwitterAuthService, WordScorerService und AttributeScorerService) im Servicesmaster eingetragen. Jeder einzelne Service wiederum besitzt die bereits erläuterte „services.yml“-Datei mit den passenden Daten. Die Servicesregistry ist in jeden Service über das Bundle integriert.
Damit ist die Forschungsfrage „Wie kann die Struktur einer monolithischen Ruby on Rails-Applikation in eine SOA umgewandelt werden?“ anhand des Beispiels JAMES beantwortet worden und dieses Kapitel abgeschlossen.
Um die Resultate nochmals zu diskutieren und die Arbeit zusammenfassen zu können, erfolgt hier der Übergang zum letzten Kapitel. Dieses beinhaltet zusätzlich noch einen Ausblick in die Zukunft dieses Forschungsgebiets.
9. Diskussion & Ausblick
Im vorigen Kapitel wurde auf die Ergebnisse der vorliegenden Masterarbeit eingegangen. Diese Ergebnisse wurden durch Erkenntnisse aus den literarischen Referenzwerken entwickelt. Dadurch konnte die Forschungsfrage der vorliegenden Masterarbeit beantwortet werden.
Der entwickelte Prototyp setzt unter anderem auf Ansätze des Spring Frameworks (vgl. 5.5), OSGi (vgl. 5.6), Ruby on Rails (vgl. 5.1), Web Services mit REST (vgl. 4.3) oder Rack (vgl. 5.3).
Anhand des „Einführenden Beispiels NewsService“ (vgl. 7.2) wurde auf die internen Abläufe des Prototyps eingegangen. Dabei konnten Querverbindungen zu Loose Coupling (vgl. 2.5) gezogen werden und es sind Theorien zur Service Choreographie (vgl. 2.4.2) umgesetzt worden. Der Prototyp funktioniert für die Beispielapplikation JAMES, kann aber auch für andere Ruby-Anwendungen zum Einsatz kommen (vgl. Einführendes Beispiel NewsService 7.2).
Der Autor ist der Meinung, dass solche Applikationen wie JAMES die Zukunft des Internets darstellen werden. Schnelle, stabile Entwicklung mit gut getesteten und evaluierten Werkzeugen wird immer wichtiger und essentieller. Der Prototyp soll den Anfang eines solchen Tools darstellen.
Die Durchführung am Beispiel JAMES wurde aus Erkenntnissen der unterschiedlichen Varianten der Partitionierung (vgl. 8.2, 8.3, 8.4, 8.5) sowie grundsätzlichen Vorteilen von Services (vgl. 2) vollzogen.
Es wurde innerhalb dieser Masterarbeit ein Bogen zwischen Literatur und praktischer Umsetzung gespannt, um eine fundierte Basis zur Beantwortung der Forschungsfrage zu schaffen. Es kann zusammenfassend gesagt werden, dass die Beantwortung der Forschungsfrage auf die Querverbindungen und Bezüge zwischen Literatur und praktischer Umsetzung zurückzuführen ist.
Das im Rahmen dieser Arbeit entwickelte Ruby-SOA-Tool befindet sich noch am Anfang seiner Möglichkeiten. In Zukunft könnte es noch erweitert und deutlich verbessert werden. Hierfür wäre interessant, wie sich das Forschungsgebiet rund um das Thema SOA in Bezug auf die Ruby-Programmierung weiterentwickelt. Mögliche Fragestellungen für die Zukunft könnten sein, wie das Framework Ruby on Rails (vgl. 5.1) in verteilten Systemen in der Cloud zum Einsatz kommen könnte.
Die Cloud wird immer wichtiger und bietet praktische Möglichkeiten, für EntwicklerInnen automatisiert Applikationen zu skalieren. Dies betrifft nicht nur die großen Unternehmen, welche sich teure Cloud-Lösungen leisten können. Es spielt auch für die Klein- und Mittelbetriebe immer eine größere Rolle. Es gibt genügend AnbieterInnen (z.B. Heroku - http://www.heroku.com), die bis zu gewissen Grenzen Pakete gratis zur Verfügung stellen. Somit wird es auch EntwicklerInnen erleichtert, verteilte Systeme zu simulieren und zu entwickeln. Der Autor hat dies im Rahmen dieser Masterarbeit anhand der NewsService-Applikation und Heroku ausprobiert. Die Entwicklung in diesem Gebiet in Bezug auf SOA und Ruby ist interessant, um in einer anderen Arbeit behandelt zu werden.
Die generelle Entwicklung von Enterprise-Lösungen mit Ruby ist in Zukunft interessant zu beobachten. Welche Ansätze könnten hier gewählt werden? Der entwickelte Prototyp bietet, nach der Meinung des Autors, auch die Möglichkeit, ein Grundgerüst für eine große Software aufzubauen. Wie dies in unternehmensspezifischen Umgebungen aussehen könnte, wäre eine zusätzliche interessante Fragestellung für zukünftige StudentInnen.
Das Thema Smartphones und Tablets (mobile Lösungen) in Bezug auf SOA-Anwendungen mit Ruby wäre eventuell eine weitere Möglichkeit, welche im Rahmen einer wissenschaftlichen Arbeit behandelt werden könnte. Wäre es beispielsweise möglich unterschiedliche Services mit Optimierungen für mobile Lösungen innerhalb einer SOA zu entwickeln? Oder besteht die Möglichkeit einen Service für verschiedene Endgeräte zu konfigurieren? Diese Punkte wären relevant, um wissenschaftlich bearbeitet zu werden.
Dieses Forschungsgebiet bietet somit noch einige Themengebiete für wissenschaftliche Arbeiten. Nach der Meinung des Autors werden diese Teilbereiche auch noch einige Jahre aktuell sein und die Softwareentwicklung maßgeblich beeinflussen.
Abkürzungsverzeichnis
- AOP
- API
- CORBA
- ESB
- GUI
- HTML
- HTTP
- HTTPS
- IDL
- JAR
- Java EE
- JDK
- JMS
- JSON
- MEP
- MVC
- OMG
- ORB
- ORM
- OSGi
- REST
- RPC
- SMTP
- SOA
- SOAP
- SOD
- URI
- URL
- WSDL
- XML
- XML-RPC
- Aspect-oriented programming
- Application programming interface
- Common Object Request Broker Architecture
- Enterprise Service Bus
- Graphical user interface
- Hypertext Markup Language
- Hypertext Transfer Protocol
- Hypertext Transfer Protocol Secure
- Interface Definition Language
- Java Archive
- Java Platform Enterprise Edition
- Java Development Kit
- Java Message Service
- JavaScript Object Notation
- Message Exchange Pattern
- Model-View-Controller
- Object Management Group
- Object Request Broker
- Object-relational mapping
- Open Services Gateway initiative
- Representational State Transfer
- Remote Procedure Call
- Simple Mail Transfer Protocol
- serviceorientierte Architektur
- Simple Object Access Protocol
- serviceorientiertes Design
- Uniform Resource Identifier
- Uniform Resource Locator
- Web Services Description Language
- Extensible Markup Language
- Extensible Markup Language Remote Procedure Call
Abbildungsverzeichnis
- Service Orchestrierung: Beispiel für Orchestrierung-Prozess
- Common Object Request Broker Architecture: Referenzmodell
- Enterprise Service Bus: Beispiel Lagerverwaltung
Que id="abbildung1"lle: http://jaxenter.de/artikel/Enterprise-Service-Bus - Ruby on Rails: MVC-Pattern einer Ruby on Rails-Applikation
Q id="abbildung1"uelle: http://rubyonrailslink.blogspot.co.at/2010/09/mvc-architecture-mvc.html - Beispielapplikation JAMES: Auflistung aller Models
- Beispielapplikation JAMES: Auflistung aller Controllers
- Beispielapplikation JAMES: Pipeline-Konzept
- Beispielapplikation JAMES: monolithische Architektur
- Prototyp: Architektur inklusive Beispiel Adapter-Middleware
- Prototyp: Beispiel für Einsatz der Communication-Middleware
- Einführungsbeispiel NewsService: Architekturübersicht
- Beispielapplikation JAMES: serviceorientierte Architektur
Listings
- SinatraTool:EinfacheSinatra-Applikation
- RackTool:call-Methode
- Rack Tool: Beispiel für Middleware (OldUrlRedirector)
- Rack Tool: Integration einer Middleware (OldUrlRedirector) in eine Ruby onRails-Applikation
- Spring Framework: Beispiel eines einfachen Java-Objekts
- Einführungsbeispiel NewsService: HighlightService CSS-Klasse
- Einführungsbeispiel NewsService: Auszug aus dem NewsService
Tabellenverzeichnis
- Mögliche Formen von Loose Coupling in SOA