Bachelorarbeit Vergleich von Spring Boot und Buffalo für die Entwicklung von Microservices

Die Seite wird erstellt Hauke Kellner
 
WEITER LESEN
Bachelorarbeit

Vergleich von Spring Boot und Buffalo für die
        Entwicklung von Microservices

                  vorgelegt von
         Leander Ludwig Günther Dreier

        Erstprüfer   Prof. Dr. rer. nat. Franz Regensburger
       Zweitprüfer   Prof. Dr. Sebastian Apel
         Betreuer    Christian Sarow
    Ausgabedatum     15. Juni 2021
     Abgabedatum     05. August 2021

                 Studiengang Informatik
                   Fakultät Informatik
             Technische Hochschule Ingolstadt
ii
Danksagung
Ich möchte mich bei meinem Prüfer Franz Regensburger und bei meinem Betreuer Christian
Sarow für ihre Geduld und Tipps bedanken, sowie speziell Christian für das Durchlesen mehrerer
Entwürfe.

   Weiter möchte ich mich bei meinen Eltern und Freunden für die generelle Unterstützung
bedanken, die mir die nötige Kraft gegeben hat, diese Arbeit fertigzustellen. Insbesondere möchte
ich mich bei meinen Freunden Regina Schneider und Dominik Okwieka für das Durchlesen eines
Entwurfs meiner Arbeit aus der Perspektive eines Außenstehenden bedanken.

  Zuletzt möchte ich mich noch bei meinem Freund und ehemaligen Kollegen Philip Schneider für
die tolle Zusammenarbeit am EMU Projekt bedanken.

                                                                                               iii
Abstract
Die Relevanz der Microservicearchitektur für die Softwareentwicklung hat in den vergangenen
Jahrzehnten – nicht zuletzt wegen der steigenden Verbreitung von Cloud-Computing – einen
stetigen Zuwachs erfahren. Bei der Entwicklung eines Microservices – gerade auch im Bereich der
Webanwendungen – werden häufig Frameworks eingesetzt, um repetitive Arbeit zu vermeiden. Ein
Beispiel hierfür ist das auf dem Spring Framework basierende Spring Boot Projekt, welches von
VMware für die Programmiersprache Java entworfen wurde und auf dem Markt häufig zum Einsatz
kommt. Daneben existieren noch weitere Lösungen in verschiedenen Sprachen, wie beispielsweise
das relativ neue und weniger bekannte Framework Buffalo des Entwicklers Mark Bates in der
Programmiersprache Go.
   In dieser Arbeit werden Spring Boot und Buffalo bezüglich ihrer Eignung für die Entwicklung
von Microservices miteinander verglichen. Als Vergleichsgrundlage wird dafür jeweils ein eigens
geschriebenes Programm verwendet, welches typische Workloads in drei unterschiedlichen Diensten
implementiert. Als Inspiration der Use-Cases und Quelle für Beispieldaten dient eine in der Sulzer
GmbH von mir mitentwickelte interne Anwendung zur digitalen Verwaltung von Nutzerdaten.
Die Evaluation erfolgt auf Basis des Analytischen Hierarchieprozesses nach Thomas L. Saaty
und wird für drei verschiedene Anwendungsfälle getrennt vorgenommen. Betrachtet werden die
durchschnittliche Laufzeit der Dienste und deren Standardabweichung, das Supportangebot durch
den Anbieter des Frameworks, die Größe der zugehörigen Onlinecommunity, und Erfahrungen bei
der Entwicklung mit dem Framework. Benchmarks zur Laufzeit werden sowohl auf Windows- wie
auch auf Unix-Systemen durchgeführt.
   Während Spring Boot auf allen Geräten ähnliche Ergebnisse erzielt, laufen die Benchmarks für
Buffalo auf Unix-Systemen durchschnittlich konsistenter ab. Dennoch geht Spring Boot für alle
drei Anwendungsfälle als Gewinner hervor, wobei es 60, 6% bis 76, 0% der Punkte beanspruchen
kann.

                                                                                                v
Inhaltsverzeichnis

Danksagung                                                                                                                                                     iii

Abstract                                                                                                                                                        v

1 Einleitung                                                                                                                                                    1
  1.1 Relevanz und Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                                 1
  1.2 Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                               2
  1.3 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                                 2

2 Bestehendes System (EMU)                                                                                                                                      5
  2.1 Motivation von EMU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                                  5
  2.2 Eingesetzte Technologien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                                5
  2.3 Nutzerstand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                               6

3 Testaufbau                                                                                                                                                    7
  3.1 Auswahl der Services . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    7
       3.1.1 Datapoint-Service . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    8
       3.1.2 Stats-Service . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    9
       3.1.3 Json-Service . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   10
       3.1.4 Bewertung der Services        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   12
  3.2 Auswahl der Frameworks . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   14
       3.2.1 Spring Boot . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
       3.2.2 Buffalo . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   15
  3.3 Schnittstellen . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   16

4 Evaluation                                                                                                                                                   19
  4.1 Analytischer Hierarchieprozess nach Saaty . . . . . . . . . . . . . . . . . .                                                        .   .   .   .   .   19
  4.2 Definition der Evaluationskriterien . . . . . . . . . . . . . . . . . . . . . .                                                      .   .   .   .   .   20
       4.2.1 Harte Kriterien . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                     .   .   .   .   .   20
       4.2.2 Weiche Kriterien . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                      .   .   .   .   .   21
  4.3 Vergleich von Spring Boot und Buffalo anhand der Kriterien . . . . . . . .                                                           .   .   .   .   .   22
       4.3.1 Laufzeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                                                    .   .   .   .   .   22
       4.3.2 Professioneller Support . . . . . . . . . . . . . . . . . . . . . . . . .                                                     .   .   .   .   .   27
       4.3.3 Größe der Stack Overflow-Community . . . . . . . . . . . . . . . .                                                            .   .   .   .   .   30
       4.3.4 Entwicklung mit Framework . . . . . . . . . . . . . . . . . . . . . .                                                         .   .   .   .   .   31
  4.4 Definition der Anwendungsfälle und Gewichtung der Evaluationskriterien .                                                             .   .   .   .   .   35

5 Fazit                                                                                                                                                        39

6 Ausblick                                                                                                                                                     41

A Anhang – Diagramme und statistische Auswertungen zur Laufzeitmessung                                                                                         ix

                                                                                                                                                               vii
Inhaltsverzeichnis

B Anhang – Programmcode zur Datenbankkonfiguration   xiii

C Anhang – Sonstige Abbildungen und Tabellen         xix

Abkürzungsverzeichnis                                xxiii

Abbildungsverzeichnis                                xxv

Quellcodeverzeichnis                                 xxvii

Tabellenverzeichnis                                  xxix

Webseitenverzeichnis                                 xxxi

Literaturverzeichnis                                 xxxv

Eidesstattliche Erklärung                            xxxvii

viii
1 Einleitung

                                                          Write programs that do one thing and do
                                                          it well. Write programs to work
                                                          together [58].
                                                                                   Dough McIlroy
                                                                         über die Unix-Philosophie

   Im Verlauf der letzten Jahrzehnte ist die Bedeutung von verteilter Entwicklung und Software im
Gegensatz zur Verwendung einer monolithischen Architektur stetig gestiegen. Bei dem klassischen,
monolithischen Softwareaufbau werden verschiedenen Dienste zu einem Paket zusammengefasst,
welches dann insgesamt alle erwünschten Aufgaben des Produkts leistet. Dieses Paket kann auf
Grund von internen Abhängigkeiten meist nur im Ganzen deployt werden. Da auf diese Weise ent-
wickelte Systeme häufig nicht den Qualitätsanforderungen von zum Beispiel Echtzeitanwendungen
wie Videotelefonie genügen, wurde bereits im Jahr 1991 von der Object Management Group (OMG)
die Common Object Request Broker Architecture (CORBA) veröffentlicht. CORBA ist ein Stan-
dard für sogenannte Middleware, welche eine Abstraktion von der Wahl der Programmiersprache
und des Betriebssystems liefert und durch eine standardisierte Schnittstelle Kommunikation von
Programmen über ein Netzwerk und einfachere Wiederverwendung von Software erlaubt. [14][60,
S. 56]
   Eine moderne Weiterentwicklung dieses Ansatzes ist die Microservicearchitektur. Dieser Um-
schwung wird in zahlreichen Studien belegt und kann unter anderem als eine Folge des immer
wichtiger werdenden Cloud-Computings gesehen werden [3, 48]. Das Prinzip der Microservices sieht
vor, jeden Dienst als eigenständige Anwendung zu betrachten, welche genau eine bestimmte Aufga-
be bearbeitet. Mehrere solcher Microservices können dann kommunizieren und zusammenarbeiten,
um ein größeres Ziel zu erreichen. Zwischen den einzelnen, kleinen Anwendungen bestehen üblicher-
weise wenig bis keine Abhängigkeiten, wodurch insbesondere Weiterentwicklung und Deployment
individuell stattfinden können. Auch steigt durch diesen Entwicklungsansatz die Skalierbarkeit
einer Anwendung, da Programmteile, welche eine höhere Last erfahren, zum Beispiel auf mehrere
Server geklont werden können, ohne dass Änderungen am Rest der Anwendung nötig werden.
   Zur Kommunikation zwischen Microservices, speziell bei einem Client-Server-Aufbau, werden
häufig Representational State Transfer (REST)-Schnittstellen eingesetzt. Durch die Zustandslo-
sigkeit dieses Paradigmas können Ressourcen gespart werden, während die Einheitlichkeit von
Schnittstellen deren Nutzung sowie den Austausch einzelner Bausteine vereinfacht. So ergab sich
beispielsweise bei einer Studie aus dem Jahr 2019, dass sich 6,41% der technischen Beiträge im
Bereich Microservices auf StackOverflow mit dem Thema REST beschäftigten [4, S. 257].

1.1 Relevanz und Motivation
Zur Umsetzung von RESTful Services werden oftmals Frameworks benutzt. Die Auswahl ist hierbei
mehr als reichhaltig, mit Optionen, welche beispielsweise auf Java, JavaScript, Python oder Go
basieren (siehe zum Beispiel Artikel von Kurmi [36], Brannan [10] oder Purkayastha [51]). Man

                                                                                                1
1 Einleitung

findet dabei sowohl bereits weiter verbreitete Angebote wie Spring Boot oder Django als auch
neuere, innovative Ansätze wie Go Buffalo oder FastAPI. All diese Möglichkeiten zu vergleichen,
um das für den Anwendungsfall am besten passende Angebot zu finden, kann mitunter sehr
aufwendig sein, gerade dann, wenn man mit der Zeit gehen und neueren Frameworks eine Chance
geben möchte.
   Inspiration für die vorliegende Arbeit war es, dass eine neu entwickelte Elektronische Mitar-
beiterrechteverwaltung (EMU) hinsichtlich verschiedener Parameter untersucht werden sollte. So
können aus der Datenbank von EMU unter anderem die Anzahl der aktiven Mitarbeiter, die
Anzahl der verwalteten Projekte und die Anzahl vergebener Berechtigungen angefragt werden.
Diese und weitere Kennzahlen sollten nun regelmäßig gesammelt und die gesammelten Daten
aggregiert werden. Zu diesem Zweck schien es sinnvoll, verschiedene Microservices zu schreiben,
welche mithilfe eines Calls über das Hypertext Transfer Protocol (HTTP) angesprochen werden
können.
   EMU wurde bereits mit Spring Boot geschrieben, zudem wird in der Firma auch viel mit Java
gearbeitet, wodurch es sich anbieten würde, auch die neuen Dienste in diesem Framework zu
schreiben. Mit einem Auge auf die Innovationen der letzten Jahre stellte sich nun jedoch die Frage,
ob nicht eine besser geeignete Sprache, beziehungsweise ein besser geeignetes Framework unter den
moderneren Lösungen zu finden ist. Diese Frage motivierte die Gegenüberstellung und Evaluation
zweier Frameworks im Rahmen dieser Arbeit.

1.2 Zielsetzung
Das Ziel dieser Arbeit ist es, Spring Boot – ein etabliertes Framework, geschrieben in der weit
verbreiteten Sprache Java, veröffentlicht von einem bekannten Unternehmen – mit Buffalo – einem
Framework mit kleinerer Community und kleinem, unabhängigem Team, entwickelt für eine
Sprache, die explizit als Alternative zu Sprachen wie Java geschaffen wurde [42, S. 101] – zu
vergleichen. Als Codebeispiele für diese Gegenüberstellung sollen drei Dienste dienen, die so
auch als Microservices eingesetzt werden könnten. Evaluationskriterien, unterteilt in harte und
weiche Kriterien, sollen mit dem analytischen Hierarchieprozess nach Saaty ausgewertet werden.
Diese Merkmale sollen messbare und statistisch auswertbare Metriken wie die Programmlaufzeit,
aber auch Erlebnisse bei der Arbeit mit der Software beinhalten. Um eine bessere Übersicht zu
liefern, soll auf drei unterschiedliche Anwendungsfälle eingegangen werden, welche unter anderem
verschiedene Betriebssysteme abdecken. Final soll ein Fazit gezogen werden, das erläutert, in
welchen Fällen welches Framework vorzuziehen ist und auf was bei der Auswahl geachtet werden
sollte.

1.3 Aufbau der Arbeit
Im Folgenden wird zunächst in Kapitel 2 das Projekt vorgestellt, welches sowohl das Thema dieser
Arbeit inspiriert als auch Beispieldaten dafür geliefert hat. Es folgt in Kapitel 3 eine Beschreibung
der ausgewählten Frameworks, der programmierten Services und der verwendeten Schnittstellen
sowie eine Begründung, warum ich mich gerade für diese entschieden habe. Zusätzlich wird in
Unterpunkt 3.1.4 eine Evaluation der implementierten Dienste vorgenommen, um aufzuzeigen,
wie sehr diese dem klassischen Modell eines Microservices entsprechen. Kapitel 4 bildet mit
der Bewertung der Frameworks den Hauptteil der Arbeit. Auf die Erklärung des verwendeten
Vergleichsprozesses folgt hier die Definition harter wie weicher Evaluationskriterien, anhand
derer in Abschnitt 4.3 die Gegenüberstellung der beiden betrachteten Optionen vorgenommen

2
1.3 Aufbau der Arbeit

wird. Abschließend findet sich die Definition dreier Anwendungsfälle, welche zur Gewichtung der
Kriterien und zu einer differenzierten Einschätzung der Frameworksleistung beitragen, sowie die
finale Bepunktung nach dem eingangs beschriebenen Prozess. Den Schluss der Arbeit bilden ein
Fazit, das die gewonnenen Ergebnisse zusammenfasst und deren Bedeutung aufzeigt, und ein
Ausblick auf mögliche Weiterführungen des angeschnittenen Themenkomplexes in den Kapiteln 5
und 6.

                                                                                             3
1 Einleitung

4
2 Bestehendes System (EMU)

2.1 Motivation von EMU

Bei der Arbeit mit Rechner- und Softwaresystemen von Kundenunternehmen werden für letztere
verschiedene Zugänge und Berechtigungen benötigt. Diese Berechtigungen müssen beantragt und
meist auch regelmäßig erneuert werden. Da vor dem Ablauf einer Berechtigung häufig keine
Erinnerung darüber von Kundenseite erfolgt und der Beantragungsprozess unter Umständen
mehrere Wochen dauern kann, ist eine frühzeitige Bearbeitung essentiell. EMU wurde ins Leben
gerufen, um diesen Prozess zu vereinheitlichen und zu zentralisieren. Wurde früher noch für
jedes Team ein individuell angelegter Datensatz ohne festes Schema gepflegt, so werden jetzt
in EMU alle relevanten Daten in einer zentralisierten Datenbank zusammengefasst. EMU steht
dabei für Elektronische Mitarbeiterrechteverwaltung beziehungsweise Electronical Management
of Userdata.
   Auch der Beantragungsprozess von Rechten wurde vereinfacht: Benötigt ein Mitarbeiter einen
neuen Zugang, kann dieser von der zuständigen Projektleiterin direkt in der Anwendung angefragt
werden und ist von der Assistenz der Niederlassungsleitung leicht einzusehen. Auch der Zustand
einer Berechtigung, also ob sie bereits bei der Kundenabteilung beantragt wurde, ob sie genehmigt
wurde, ob sie funktioniert oder nicht, wird gespeichert und kann mit wenigen Mausklicks eingetragen
und geprüft werden.
   Entwickelt wurde EMU bis Ende 2020 hauptsächlich von einem Auszubildenden und mir, stets in
enger Zusammenarbeit mit den Teamleitern, den führenden Personen der Geschäftsfelder und der
internen IT. Seitdem wird es von einem inzwischen größeren Team weiterentwickelt und gepflegt.

2.2 Eingesetzte Technologien

Bei der Frage nach der zu verwendenden Technologie fiel die Wahl der Sprache schnell auf Java,
da diese sowohl von allen initialen Entwicklelnden am Projekt am besten beherrscht wurde als
auch im Unternehmen allgemein weit verbreitet ist. Mit dem Ziel der Arbeitsersparnis und um
Fehler bei der Auswahl von Bibliotheken zu vermeiden, wurde nach einem Framework Ausschau
gehalten. Architektonisch wurde festgelegt, dass eine Webanwendung entwickelt werden soll, um die
Auslieferung und Wartung der Software zu vereinfachen und damit einen zügigen Updatezyklus zu
gewährleisten. Mit dieser Bedingung im Hinterkopf habe ich mich schließlich, aufgrund der großen
Community und weil es im Unternehmen bereits in anderen Projekten eingesetzt wurde, für Spring
Boot als Framework für das Projekt entschieden. Für die Datenhaltung schien, wegen der erwarteten
relationalen Struktur zwischen Objekten im Projekt, ein objektrelationales Datenbanksystem die
beste Lösung. Es war das Anliegen der Entwickler, ein quelloffenes Produkt auszuwählen, und mit
diesem Rahmen viel die Wahl auf PostgreSQL.

                                                                                                 5
2 Bestehendes System (EMU)

2.3 Nutzerstand
Zu Beginn der Entwicklung von EMU wurde es versuchsweise nur für ein Projekt mit etwa 30
Kolleginnen und Kollegen benutzt und getestet. Dort war es so erfolgreich, dass heute, nach
circa eineinhalb Jahren Entwicklungszeit, über 15 Projekte mit insgesamt über 450 Nutzerinnen
und Nutzern und über 600 Berechtigungen verwaltet werden. Die Anwendung wird in mehreren
Niederlassungen eingesetzt und ist inzwischen fester Bestandteil der Ein- und Austrittsprozesse
von Angestellten.

6
3 Testaufbau
Um eine Grundlage für die Bewertung und Gegenüberstellung der zu testenden Frameworks zu
schaffen, habe ich jeweils eine Anwendung in Spring Boot und Buffalo geschrieben. Die beiden
Programme implementieren jeweils drei Funktionalitäten, welche über eine REST-Schnittstelle
mit HTTP angesprochen werden können. Es wurden also nicht echte, eigenständige Microservices
implementiert. Stattdessen wurde eine Architektur gewählt, welche einige Eigenschaften davon,
wie das Aufrufen über HTTP oder eine gewisse Trennung von Diensten – hier zum Beispiel
durch die Aufteilung in eigene Controller – übernimmt, während der Komfort einer einzelnen
Binärdatei pro Sprache gewahrt bleibt. Um die drei unten beschriebenen Dienste, welche im
Laufe dieser Arbeit untersucht werden, effizient testen zu können, wurde außerdem ein Service
zum Benchmarking entwickelt, der die relevanten Funktionen mehrfach hintereinander ausführen
und dabei deren Laufzeit messen kann. Ich möchte anmerken, dass ich zuerst die Spring Boot-
Anwendung geschrieben und dann den Buffalo Gegenpart daran orientiert habe, da Java mir
vertrauter als Go war.
   Der strukturelle Aufbau der Anwendungen im Dateisystem ist in Abbildung 3.1 zu sehen. Für
die Spring Boot-Anwendung beinhaltet das emu-Paket die Teile der EMU-Anwendung, welche
auch für die hier gewünschten Funktionalitäten relevant sind und daher übertragen werden
mussten. Im stats-Paket befinden sich Models und Repositorys der eigentlichen Anwendung
in den jeweiligen Unterordnern. BenchmarkController, DatapointController, StatController
und JsonStatController – welche die Implementationen der verschiedenen Dienste enthalten
– sind schließlich im Ordner controller zu finden. Die Struktur des Buffalo-Projekts ist im
Gegensatz dazu mit nur zwei Ebenen deutlich flacher. Die Dienste, welche in Java separat in
einem controller-Paket untergekommen sind, finden sich hier, zusammen mit zum Beispiel dem
Router, in actions. Alle Models, auch die zum Verarbeiten der EMU-Daten, werden in models
zusammengefasst. Die Unterordner config, fixtures, grifts, locales und migrations werden
von Buffalo generiert, aber hier nicht verwendet.

3.1 Auswahl der Services
Bei der Auswahl der zu implementierenden Dienste standen zwei Faktoren im Vordergrund: Zum
einen sollten sie sich an dem gegebenen Use-Case, sprich der Auswertung von Nutzungsdaten aus
EMU, orientieren. Es sollte betrachtet werden, wie sich bestimmte Kennzahlen aus der Datenbank
von EMU im zeitlichen Verlauf verändern. Dies bedeutet, dass die von mir geschriebene Anwendung
mit kleinen Änderungen auch im Produktivbetrieb eingesetzt werden könnte. Solche geringen
Anpassungen wären zum Beispiel notwendig, um den Benchmark-Controller zu entfernen oder um
die korrekte (Live-) Datenbank anzubinden. Ein Ziel dieser Entscheidung war es, für das Testen der
Anwendung und vor allem beim Evaluieren deren Laufzeit mit realistischen Beispieldaten arbeiten
zu können. Auf diese Weise wurde versucht, ein aussagekräftigeres Ergebnis als mit synthetischen
Testdaten zu erreichen. Ein weiteres Ziel war es, bei der späteren Evaluation der Services konkrete
Sollwerte zu haben. Bei dieser Orientierung an einem realen Anwendungsfall wurde jedoch darauf
geachtet, dass sie nicht auf Kosten der Signifikanz der Messergebnisse beziehungsweise zum Nachteil
des folgenden Punktes geschieht.

                                                                                                 7
3 Testaufbau

             thesis_java
                   gradle
                   src
                           main
                                  java
                                                                         thesis_go
                                         de.thi.dreier.thesis
                                                                               actions
                                               controller
                                                                               config
                                               emu
                                                                               fixtures
                                                       repository
                                                                               grifts
                                                       enums
                                                                               locales
                                                       model
                                                                               migrations
                                                       service
                                                                               models
                                               stats
                                                       model
                                                                    (b) Buffalo-Anwendung
                                                       repository
                                  resources
                           test

                    (a) Spring Boot-Anwendung

                         Abbildung 3.1: Paketstrukturen der Anwendungen

  Zum anderen bestand die Absicht, eine gewisse Diversität unter den zu erstellenden Diensten zu
erreichen. Es wurde daher darauf geachtet, dass sich jeder Service auf einen separaten, häufig ge-
nutzten Bestandteil von Microservices spezialisiert. Häufige Anwendungsfälle für Microservices sind
laut verschiedenen Onlineressourcen neben der Migration von Monolithen Big-Data-Anwendungen
und Datenverarbeitung in Echtzeit [20, 35, 45]. Ein besonderes Augenmerk wurde daher auf die
Verwendung von Datenbanken und die Abfrage sowie Verarbeitung – in diesem Anwendungsfall –
großer Datenmengen gelegt.

3.1.1 Datapoint-Service
Der erste von mir im Rahmen dieser Arbeit geschriebene Service, genannt Datapoint-Service, fragt
verschiedene Daten aus der EMU-Datenbank – einer objektrelationalen PostgreSQL-Datenhaltung
– an, bereitet diese in einem Datapoint-Objekt auf und speichert dieses in einer lokalen, doku-
mentenbasierten Mongo-Datenbank. Ein Datapoint spiegelt damit einen Snapshot von EMU zu
einem bestimmten Zeitpunkt wieder. Die angefragten Werte lassen sich hierbei in zwei Kategorien

8
3.1 Auswahl der Services

unterteilen: Solche, die projektspezifisch zu werten und jene, die projektübergreifend gültig sind.
Beispiele für projektspezifische Kennzahlen sind die Anzahl vergebener Berechtigungen pro Projekt,
die Anzahl der verfügbaren Berechtigungen im Projekt oder die Anzahl der Mitarbeiter in einem
Projekt. Im Datapoint-Objekt werden diese als Map der Form

        {Projektname als string} → {Wert für dieses Projekt als long / int64}

abgelegt. Projektübergreifend werden hingegen beispielsweise die Anzahl der erfolgreich und
erfolglos durch die Anwendung verschickter E-Mails und die Anzahl aufgetretener Frontend-Fehler
abgefragt, welche als einfache long beziehungsweise int64 Werte gespeichert werden können.
Zusätzlich zu diesen Daten wird als Primärschlüssel noch ein Zeitstempel hinterlegt, der den
Zeitpunkt der Anfrage angibt. Es kann somit nicht mehrere Datapoints mit dem selben Zeitstempel
geben. Da bei diesem Modell nur von einem Knoten der Anwendung ausgegangen wird, über
welchen Statistiken erstellt werden sollen, ist diese Einschränkung kein Hindernis und im Gegenteil
eine Erleichterung durch die Vermeidung redundanter Datapoints.
   Die Methode, welche mit POST :/datapoint/generate aufgerufen wird, geht
wie folgt vor: Nachdem der Zeitstempel erstellt wurde und bei Bedarf alte Datenpunkte gelöscht
wurden, werden zunächst aus der EMU-Datenbank alle angelegten Projekte angefragt und ein
neuer, leerer Datapoint angelegt. Anschließend werden die Variablen mit den passenden Werten
gefüllt, welche für die projektunabhängigen Daten durch einfache Anfragen an die Datenbank
erhalten werden können. Zum Füllen der Maps hingegen wird über die vorher bestimmten verfüg-
baren Projekte iteriert, um für jedes die individuellen Werte aus der Datenbank abzufragen. Der
vollständig befüllte Datapoint wird zuletzt in der MongoDB gespeichert. Bei meinem konkreten
Testaufbau wird der Zeitstempel, welcher zur Identifikation der erzeugten Objekte notwendig ist,
nicht aus dem aktuellen Datum gewonnen, sondern beim Methodenaufruf als Requestparameter
übergeben. Diese Lösung ist dem Umstand geschuldet, dass meine Anwendung nicht auf die
Live-Datenbank von EMU zugreift, sondern jeweils lokale, aus psql-Exporten geladene Kopien
verwendet. Auf diese Weise können viele verschiedene Datenpunkte aus verschiedenen historischen
Ständen der Datenbank einfach und schnell erzeugt werden. Der übergebene Zeitstempel ist
dabei der Name der aktuell eingelesenen SQL-Datei im Format yyyy-MM-dd-HH.sql. Ein netter
Nebeneffekt dieser Vorgehensweise ist es, dass Unterschiede im Parsen von Zeitstempeln zwischen
Java und Go mitbetrachtet werden können.

3.1.2 Stats-Service
Aufbauend auf dem Output des Datapoint-Services arbeiten die Funktionen des Stats-Services.
Die Aufgabe dieses Dienstes ist es, aus den Datenpunkten, welche jeweils Informationen zu
einem bestimmten Zeitpunkt speichern, Stat-Objekte zu erzeugen. In diesen Stats wird zu
einem bestimmten Kennwert, wie zum Beispiel der Anzahl erfolgreich versandter E-Mails, dessen
zeitlicher Verlauf in Form einer Map von Zeitpunkt der Messung zu dem Messergebnis abgelegt. Die
Gruppierung ändert sich damit von einer zeitlichen zu einer Gruppierung nach Kennwert. Konkret
passiert dies folgendermaßen: Nach dem Aufruf von POST :/stats/generate
wird zuerst anhand des neuesten Datapoints bestimmt, welche Projektnamen verfügbar sind. Für
jeden davon werden anschließend die projektspezifischen Kennzahlen verarbeitet. Dazu wird pro
Projekt ein StatsForProjekt-Objekt angelegt, welches den Namen dieses Projekts sowie eine
Liste von Stat-Objekten hält. Jedes Stat-Objekt besteht aus einer Bezeichnung, wie beispielsweise
employeesPerProject, und einer Map, in der Zuordnungen der Form

        {Zeitstempel t } → {Wert von zum Beispiel employeesPerProject zu t }

                                                                                                 9
3 Testaufbau

gespeichert werden. Dieser Stats-Service iteriert pro Projekt über alle verfügbaren Datapoints, um
in jedem Interationsschritt jeweils einen neuen Eintrag in die Maps der passenden Stat-Objekte zu
schreiben. Nach vollständigem Durchlauf der Schleife wird der StatsForProject-Datensatz in der
Datenbank gespeichert. Wurde dieser Vorgang für alle Projekte wiederholt, werden schließlich die
projektunabhängigen Kennzahlen mit dem gleichen Vorgehen ermittelt und in einer GeneralStats-
Collection gespeichert. Eine Übersicht, wie die verarbeiteten Daten ineinander umgewandelt und
in welchem Format sie abgelegt werden, ist in Abbildung 3.2 zu sehen. Die dort dargestellten
Datentypen sind die von Java; in Go würden die dazu analogen Bezeichnungen verwendet werden.
   Dieses Verfahren, immer wieder neu über alle Datapoints zu iterieren, ist so sicher nicht besonders
effizient. Auch könnte man diskutieren, ob diese Umsortierung der Datenstrukturen nötig ist. Hohe
Effizienz war jedoch nicht das Ziel beim Schreiben dieser als Beispiel dienenden Anwendungsteile; im
Gegenteil wurde eher Wert darauf gelegt, mit den vorhandenen Daten und in einem überschaubaren
Rahmen pro Microservice ein bestimmtes Muster möglichst intensiv anzuwenden. Während das
Muster im ersten Service darin bestand, aus einer PostgreSQL-Datenbank viele verteilte Werte
zusammenzufassen, ist es in diesem Fall, viele Dokumente aus einer Mongo-Datenbank anzufragen
und zu verarbeiten.
  Sowohl Datenquelle als auch -senke sind für diesen Microservice MongoDB Collections. Da-
durch, dass es sich bei MongoDB um ein dokumentenbasiertes Datenbanksystem handelt, ist die
Speicherung von Listen und Maps problemlos darin möglich. Würde stattdessen eine relationale
Datenbank verwendet werden, wären deutlich mehr Tabellen und komplexere Abhängigkeiten
notwendig, was sowohl die Anwendung als auch die Datenhaltung aufblähen würde. Ein solcher
Zuwachs an Komplexität wurde für diesen Anwendungsfall als nicht zielführend erachtet, weshalb
die Entscheidung getroffen wurde, für die Speicherung von Datapoints und Stats MongoDB zu
verwenden.

3.1.3 Json-Service

Als drittes und letztes hier betrachtetes Beispiel dient der Json-Service. Wie der Name bereits
vermuten lässt, spielt hierfür die JavaScript Object Notation (JSON) eine Rolle. Im Kern verläuft
seine Datenverarbeitung so, wie beim Stats-Service auch, wodurch viele Funktionen wiederverwen-
det werden können. Der entscheidende Unterschied zum vorher behandelten Dienst liegt darin,
dass der Json-Service sowohl als Datenquelle als auch für den Output einfache Textdateien be-
nutzt. Anstelle einer Datenbank liegt deren Inhalt als JSON-formatierte Strings vor. Konkret
wird aus einer datapoints.json-Datei gelesen, welche zuvor durch Exportieren der datapoints-
Collection mithilfe der IntelliJ IDEA IDE [33] erzeugt wurde. Geschrieben wird in die zwei Dateien
generalStats.json und statsForProject.json.
   Die Idee für diese Implementierung ist aufgekommen, nachdem der Stats-Service bei ersten
Testläufen unter gewissen Umständen eine sehr hohe Varianz in der Laufzeit zeigte (mehr dazu
in Abschnitt 4.2). Dadurch, dass diese Variante nur mit Textdateien auskommt, sollte überprüft
werden, ob diese Auffälligkeiten auf die Verwendung der Datenbank zurückzuführen sind. JSON
als Dateiformat wurde hier wegen seiner weiten Verbreitung und seiner guten Lesbarkeit für
Maschinen und Menschen verwendet. Das auf den Datentypen von JavaScript basierende Format
wird häufig für den Austausch von Daten zwischen Maschinen eingesetzt, kann uns hier aber dank
der Möglichkeit, Objekte, Listen, Maps, und vor allem Kombinationen dieser wiederzugeben, auch
für die Speicherung der Datensätze behilflich sein. [49, S. 263][9, S. 123]

10
3.1 Auswahl der Services

                                          EMU
                                       Datenbank

                                           Datapoint-Service

                                          Datapoint
                        -timestamp: Date
                        -privilegesPerProject: Map
                        -employeesPerProject: Map
                        -visibleJSErrors: long
                        -successfulMails: long
                        -unsuccessfulMails: long
                        ...

                                           Stats-Service
                                           Json-Service

                        GeneralStats         StatsForProject
                                          -projectName: String
                               0..1
                                                     0..1
                               stats
                                                     stats

                               0..n                  0..n

                                         Stat
                            -name: String
                            -values: Map

Abbildung 3.2: Verlauf der verwendeten Datenstrukturen am Beispiel der Java-Implementierung

                                                                                        11
3 Testaufbau

3.1.4 Bewertung der Services
Als Abschluss dieser Sektion werden die oben beschriebenen Dienste noch bezüglich ihrer Eignung
als Beispiele für diese Arbeit bewertet. Zu diesem Zweck wurden sechs typische Eigenschaften eines
Microservices aus Texten von Leitner, Richter, Indrasiri u.a. ermittelt [37, S. 166][54, S. 131 f.][29].

1. Horizontale Skalierbarkeit             Skalierung der Leistung bei Vervielfältigung des Dienstes
Eine einfache Form der Parallelisierung mit dem Zweck, eine horizontale Skalierung zu erreichen,
ist die Verwendung jeweils eigener Instanzen des Datapoint- und des Stat-Services pro Anwendung,
über die Statistiken ermittelt werden sollen. Aufgrund der Spezialisierung der Dienste auf die
Datenstruktur von EMU ist dies jedoch nicht ohne größere Anpassungen am Code möglich. Eine
weitere Möglichkeit, eine Skalierung umzusetzen, ist es, die Teilaufgaben der einzelnen Dienste bei
der Arbeit mit EMU auf mehrere Instanzen aufzuteilen. Für den Datapoint-Service auf der einen
Seite ist dies gut möglich: Hier könnte die Ermittlung der einzelnen Werte für einen Datenpunkt –
wie zum Beispiel die Anzahl der Mitarbeiter pro Projekt employeesPerProject oder die Anzahl
der erfolgreich versendeten E-Mails successfulMails – auf verschiedene Knoten verteilt werden.
Um hier einen merklichen Zeitgewinn beobachten zu können, könnte es dann noch notwendig sein,
den zu bearbeitenden Zustand der EMU-Datenbank zu klonen, um bei den Datenbankzugriffen
nicht in ein Bottleneck zu geraten. Für den Stat-Service auf der anderen Seite bietet sich dieses
Verfahren weniger an beziehungsweise wäre aufwendiger. Durch die Umordnung der Daten wird
für jede generierte Statistik der Input aus allen Datenpunkten benötigt. Dieser Input wird aktuell
im Service aus der Datenbank abgefragt. Um redundante Abfragen zu vermeiden, müssten diese
also aus dem Dienst ausgelagert und zum Beispiel bei Aufruf bereits mitgeliefert werden. Somit
ergäbe sich dann eine Skalierung von nur einem Teil des Services. Ähnlich verhält es sich mit
dem Json-Service: Die in einer Datei vorliegenden Daten könnten in mehrere Dateien – eine pro
Statistik – aufgeteilt werden, um zu vermeiden, dass bei einer Parallelisierung jede Instanz alle
Daten lesen muss. Ansonsten ergibt sich bei einer Teilung nach einzelnen Statistiken bei den beiden
zuletzt genannten Diensten noch das Problem, dass die Datenstruktur des Outputs aktuell etwas
verschachtelt ist und wahrscheinlich für eine effektive Skalierung umstrukturiert werden müsste.

2. Hardwareunabhängigkeit                               Unabhängigkeit von verwendeter Hardware
Alle für diese Arbeit geschriebene Software wurde auf mehreren handelsüblichen Rechnern entwickelt
und getestet, wodurch dieses Kriterium für alle drei Microservices als erfüllt betrachtet werden
kann.

3. Technologieunabhängigkeit            Unabhängigkeit von umgebender Software, wie Datenbanken
Da sowohl der Database- wie auch der Stat-Service direkt aus Datenbanken lesen und in diese
schreiben, müssen diese verfügbar und korrekt konfiguriert sein, um die Dienste erfolgreich ausführen
zu können. Für den Stat-Service besteht dabei nur eine Abhängigkeit zu einer Mongo-Datenbank,
bei dem Database-Service kommt noch eine Abhängigkeit zu einer PostgreSQL-Datenbank hinzu,
welche unter Umständen auch auf einem anderen Server liegen könnte. Der Json-Service benötigt
im Vergleich dazu keine Verbindung zu einer Datenbank, stattdessen ist jedoch das Vorhandensein
einer korrekt formatierten Datei als Input notwendig. Das Kriterium der Technologieunabhängigkeit
ist somit nicht erfüllt, wobei der Json-Service durch minimale Änderungen am einfachsten angepasst
werden könnte, um dies zu beheben.

4. Austauschbarkeit  Vorhandensein einer höchstens losen Kopplung zwischen Komponenten
Um mit meiner Anwendung sinnvolle Statistiken generieren zu können, war es für den Datapoint-

12
3.1 Auswahl der Services

Service notwendig, eine enge Bindung zu der Datenstruktur der EMU-Anwendung herzustellen. Im
Codebeispiel 3.1 ist zu sehen, wie die Mitarbeiter pro Projekt bestimmt und gespeichert werden.
Damit dieser Code funktionieren kann, müssen die Tabellen- und die wichtigsten Spaltennamen der
EMU-Datenbank wie erwartet vorliegen. Dieses Problem setzt sich im Stat-Service leider fort: Hier
werden die durch den Datapoint-Service gespeicherten Daten mit ihrem genauen Bezeichner abge-
fragt. Das Codebeispiel 3.2 zeigt die Weiterverarbeitung des vorher bereits behandelten Datensatzes
employeesPerProject. Wahrscheinlich existiert eine Lösung, die durch höhere Codekomplexität
eine losere Kopplung erreicht, diese Überlegung wurde im Rahmen dieser Arbeit jedoch nicht
näher verfolgt. Für den Json-Service ergibt sich, aufgrund der nahezu gleichen Datenverarbeitung,
ein ähnliches Bild wie für den Stat-Service, wobei man durch das Lesen aus und Schreiben in
Dateien zumindest von einer etwas loseren Bindung sprechen könnte.

// Get all available projects.
List availableProjects = projectRepo.findAll();

// Populate datapoint.
Map employeesPerProject = availableProjects.stream()
        .collect(Collectors.toMap(
                Project::getName,
                employeeRepo::countEmployeesByProject
        ));
datapoint.setEmployeesPerProject(employeesPerProject);

         Codebeispiel 3.1: Setzen einer Variable eines Datenpunkts im Datapoint-Service

// Create the stats with hardcoded names.
Stat employeesPerProject = new Stat("employeesPerProject");

// Fill the stats with data.
datapoints.forEach(datapoint -> {
    if (datapoint.getEmployeesPerProject() == null) {
        return;
    }

      employeesPerProject.getValues().put(
          datapoint.getTimestamp(),
          datapoint.getEmployeesPerProject().get(projectName)
      );

      // ... Verarbeitung weiterer Statistiken zur Übersichtlichkeit ausgelassen ...
});

             Codebeispiel 3.2: Weiterverarbeitung eines Datensatzes im Stat-Service

                                                                                                13
3 Testaufbau

5. Standardschnittstellen Verwendung standardisierter Kommunikation via HTTP und JSON
Die von mir geschriebene Anwendung wurde so konzipiert, dass alle Methoden beziehungswei-
se Funktionen, welche von außen ansprechbar sein sollen, über eine HTTP-REST-Schnittstelle
zur Verfügung gestellt werden. Im Beispiel benutzen die meisten Aufrufe den HTTP POST-
Befehl, welcher das Anlegen einer neuen Ressource signalisiert. Die POST-Methode wurde ge-
wählt, da in der Regel durch jeden Befehl an die Anwendung eine bestimmte Form von Daten
generiert und gespeichert wird: Durch POST :/stats/generate werden bei-
spielsweise die Statistikdaten aus den vorliegenden Datenpunkten ermittelt und mithilfe von
POST :/benchmark/datapoint wird die Methode zum Erzeugen eines Daten-
punkts mehrmals ausgeführt und dabei die benötigte Laufzeit gesichert. Gegebenenfalls erforderliche
oder optionale Parameter, wie die Anzahl durchzuführender Durchläufe bei einem Benchmark,
können über den HTTP-Body gesetzt werden und sind im JSON-Format codiert.

6. Klar definierter Anwendungsbereich           Beschränkung auf genau eine konkrete Aufgabe
Zwar erfüllt die Anwendung als Ganzes – durch die Bauweise als lose gekoppelter Monolith –
mehrere Aufgaben, nämlich die der einzelnen Dienste, letztere sind jedoch für genau eine Aufgabe
innerhalb des Verbundes zuständig: Der Datapoint-Service erstellt einen Datenpunkt aus den
Daten der EMU-Datenbank, der Stat-Service generiert Statistiken aus den Informationen mehrerer
Datenpunkte in der Datenbank und der Json-Service speichert Statistiken zu JSON-codierten
Datenpunkten aus einer Datei.

3.2 Auswahl der Frameworks
Bei der Entwicklung von Anwendungen, gerade bei Webanwendungen, werden bestimmte Pro-
grammbestandteile häufig wiederverwendet. Auch bei Microservices werden Bausteine wie ein
Webserver zum Empfangen von Aufrufen, ein Routing-System zur Zuordnung von Call zu be-
arbeitender Funktion, ein Security-System zur Berechtigungsverwaltung und häufig auch eine
Datenbankanbindung benötigt. Um zu vermeiden, diesen Rahmen einer Anwendung jedes Mal
manuell neu anlegen zu müssen, werden hierfür häufig Frameworks benutzt. Dadurch kann ein
schlankerer und übersichtlicherer Code erreicht werden, welcher in der Regel weniger Fehler aufweist
und optimierter ist, womit man auch dem „Micro“ in Microservice besser gerecht wird.
   Doch nicht alle Frameworks sind gleichzustellen, stattdessen lassen sie sich gut in verschiedene
Gruppen, passend für den jeweiligen Anwendungsfall, untergliedern: Wenn bereits ein Backend
existiert und darauf aufbauend lediglich eine REST-Schnittstelle über ein Application Programming
Interface (API) benötigt wird, bietet es sich an, ein leichtgewichtiges Microframework wie Flask [19]
zu benutzen. Ist auf der anderen Seite ein Client erforderlich, welcher selbst API-Requests absetzt,
kann ein Blick auf Client-Libraries wie Requests [52] geworfen werden. Für die Entwicklung einer
vollen Webanwendung von Grund auf empfiehlt sich schließlich die Verwendung eines sogenannten
Full-Stack-Frameworks (wie zum Beispiel Django [41]), welches die volle Bandbreite an Schichten
und Funktionen anbietet. Um bei meiner Gegenüberstellung Unterschiede zwischen den verwendeten
Frameworks sinnvoll vergleichen zu können, habe ich mich bei der Auswahl ebenjener auf einen
Typ beschränkt: Da Spring Boot für mich als erstes Vergleichsobjekt schon zu Beginn schnell
feststand, sollte das Vergleichs-Framework ebenso ein Full-Stack-Framework sein.
   Wie bereits in Punkt 3.1 zu sehen, decken meine Beispielanwendungen nicht den vollen Um-
fang eines Full-Stack-Frameworks ab. Speziell wurde kein Frontend entwickelt; die Funktionen
werden mit HTTP-Calls – angelehnt an das REST-Muster – angesprochen und liefern lediglich
kurze Statusmeldungen zurück, nicht jedoch ganze Webseiten. Der Frontendbereich wurde somit

14
3.2 Auswahl der Frameworks

ausgeklammert, da dort andere Vergleichsparameter in Frage kämen als bei der Backendentwick-
lung und im Microservicebereich. Aufgrund der Spezialisierung einzelner Services auf bestimmte
Aufgaben, werden Front- und Backend auch generell häufig getrennt behandelt. Da das Backend
einer Anwendung ein essentieller Teil ist, sollte meine Fokussierung darauf bei der Evaluation
eines Full-Stack-Frameworks die Relevanz der Ergebnisse wenig einschränken; trotzdem ist diese
Entscheidung im Hinterkopf zu behalten.

3.2.1 Spring Boot
Spring Boot ist ein Projekt des Unternehmens VMware, Inc., welches die Erstellung und vor allem
die Konfiguration von Anwendungen in dem quelloffenen Spring-Framework vereinfacht. Laut
offizieller Website können damit Standalone-Anwendungen erstellt werden, die „einfach laufen“ [65].
Während bei einer klassischen Spring Anwendung noch viel Konfiguration in der Extensible
Markup Language (XML) notwendig ist, übernimmt Spring Boot diese Arbeit und bringt auch
direkt einen Webserver (Tomcat, Jetty oder Undertow) sowie Dependency-Pakete für verschiedene
Anwendungsfälle wie „Spring Web“, „Spring Boot Dev Tools“ oder „Spring Data JPA“ mit [66].
Durch diese Vereinfachungen bei der Initialisierung einer neuer Applikation wird Spring Boot
gerne für die Entwicklung von Microservices eingesetzt. Spring-Anwendungen, und damit auch
solche mit Spring Boot, werden in der Programmiersprache Java geschrieben, welche sich seit über
zehn Jahren einen Platz als eine der wichtigsten und meistgenutzten Programmiersprachen sichern
kann [8, 16, 46]. Mit ein Grund für diesen Erfolg wird sicher die Plattformunabhängigkeit der
Sprache sein, welche durch die Verwendung der Java Virtual Machine (JVM) erzielt wird [27]. Auch
EMU, die Datenbasis für diese Arbeit, wurde in Java und mit Spring Boot geschrieben, weshalb
es sich anbot, hier als erstes Framework zur Gegenüberstellung ebenso Spring beziehungsweise das
darauf aufbauende Spring Boot-Projekt, zu verwenden.

3.2.2 Buffalo
                                                           There’s a joke that Go was conceived
                                                           while waiting for a C++ program to
                                                           compile, which is kind of half true [42].
                                                                                    Andrew Gerrand
                                                                       Software Ingenieur bei Google

  Die Programmiersprache Go, ursprünglich ein Nebenprojekt dreier Google-Entwickler, wurde im
November 2009 mit Spezifikation und Open-Source-Referenzimplementierung veröffentlicht. Auch
heute noch von Google unterstützt, hat sich seitdem die Zahl der Mitwirkenden auf das mehr als
hundertfache erhöht. Auch außerhalb des Unternehmens hat sich eine aktive Community gebildet.
Anstoß für die Entwicklung von Go war eine steigende Unzufriedenheit einiger Google-Mitarbeiter
mit den Tools und Sprachen, mit denen sie arbeiten mussten, insbesondere, da diese für den dort
erforderlichen Maßstab nie designt wurden. Namentlich werden Java und C++ genannt, beide
über ein Jahrzehnt älter als Go, beide weit verbreitete Sprachen, aber beide für die Go Gründer
unzufriedenstellend. Im Gegensatz zu Java ist Go keine reine objektorientierte Sprache, erlaubt
jedoch objektorientierte Programmierung. Go legt besonderen Wert auf einfache Skalierbarkeit
und einfache (auch kooperative) Entwicklung. Ein Beispiel hierfür ist Gofmt (kurz für Go Format),
ein Werkzeug, welches Go Quellcode in kanonischer Formatierung drucken kann, wodurch dieser
nahtlos in anderen, bestehenden Code eingefügt werden kann. Auf diese Weise sollen Differenzen
zwischen Programmierern bezüglich unterschiedlicher Präferenzen im Formatierungsstil umgangen

                                                                                                       15
3 Testaufbau

werden [42, S. 101]. Für diese Arbeit habe ich mir ein Framework in Go zum Vergleich mit einem
Java Framework ausgesucht, um zu überprüfen, inwieweit die Bemühungen gelungen sind, eine für
die Entwicklung von Webanwendungen bessere Alternative zu Java zu schaffen. Auch wenn ich in
diesem Rahmen die Skalierbarkeit meiner Software nicht vollumfänglich testen kann, so bieten die
Microservices zumindest einen Einblick für dieses Qualitätsmerkmal.
   Auch die Erstellung von Webanwendungen wird bei Go out of the box unterstützt und erleichtert.
Sowohl Anbindungen an verschiedene Datenbanken als auch ein performanter Webserver werden
in der Standardbibliothek bereitgestellt. Es wäre somit auch gut möglich, Microservices ohne das
Hinzuziehen eines Frameworks in Go zu schreiben, was nicht heißt, dass eine Unterstützung nicht
trotzdem sinnvoll sein kann. [23]
   Dieser Unterschied zu anderen Sprachen, sprich, dass bereits viele der benötigten Tools direkt in
der Standardbibliothek mitgebracht werden, verändert aber, welche Aufgaben auf das Framework
zurückfallen. Bei der Evaluation in Sektion 4.3 soll näher auf diese Beobachtung eingegangen werden.
Für diese Arbeit habe ich mich dazu entschieden, das Go-Framework Buffalo einzusetzen. Buffalo
ist von Full-Stack-Web-Developer Mark Bates [5] speziell für Webentwicklung mit Priorisierung
der Entwicklungsgeschwindigkeit und -produktivität entworfen worden. Gerade der Einstieg für
neue Go-Entwickler soll durch umfangreiche Codegenerierung und der Bereitstellung von Tools
wie automatischem Deployment von Docker-Containern oder Hot Code Reload (automatischem
Rebuilden der Anwendung bei Änderungen) erleichtert werden. Es können mit Buffalo einfach volle
Webanwendungen erstellt werden, bei der Initialisierung einer Anwendung kann die Generierung
des Frontend-Codes mithilfe des --api-Flags jedoch auch übersprungen werden, wodurch es auch
für die hier gewünschte Entwicklung von Microservices, welche lediglich aus Backend bestehen,
gut geeignet ist.

3.3 Schnittstellen
Datenbankverbindungen sind häufig ein wichtiger Bestandteil einer Anwendung, damit ein organi-
siertes Ablegen und Abrufen von Daten sichergestellt werden kann. Aufgrund der Vielfältigkeit
von Microservices ist es durchaus realistisch, dass für eine Anwendung, bestehend aus vielfältigen
Microservices, auch Verbindungen zu mehreren, unterschiedlichen Datenbanken notwendig sind.
Auch im Laufe der Entwicklung von EMU kam diese Anforderung auf. Dort existiert zum Beispiel
eine Verbindung zur projekteigenen Datenbank – derjenigen, welche auch die Daten für die in dieser
Arbeit besprochenen Microservices liefert – und eine zur firmenübergreifenden LDAP-Datenbank.
War die Entwicklung mit nur einer Datenbank zu Beginn kein Problem in Spring Boot, stellte
sich das Hinzufügen neuer Datenquellen im weiteren Verlauf als nicht trivial heraus. Aufgrund
dieser Erfahrung beschloss ich, meine Testkandidaten auch auf die Schwierigkeit der Verwaltung
von multiplen Datenbanken in einer Anwendung zu testen. Die erste sollte dabei PostgreSQL sein,
da dies das von EMU verwendete Datenbanksystem ist. Für EMU wurde PostgreSQL damals aus
dem Grund ausgewählt, dass es sich dabei um ein weit verbreitetes relationales System handelt,
welches zudem quelloffen ist.
   Für die zweite Datenbank wollte ich einen Kontrast zu der bereits vorhandenen, relationalen,
herstellen. Dokumentenbasierte Datenbanken werden gerade im Microservice- und Cloud-Bereich
immer wichtiger. In einer cloudbasierten Webanwendung, welche das Event-Sourcing-Pattern
einsetzt, ist es beispielsweise vorstellbar, eine relationale Datenbank zum Event-Sourcing zu ver-
wenden, während zum Verwalten des View-Models eine dokumentenbasierte NoSQL-Datenhaltung
eingesetzt wird [55][15].
   Nachdem im Laufe des Benchmarkings meiner Anwendung Phänomene auftraten, welche ein

16
3.3 Schnittstellen

mögliches Bottleneck bei der Kommunikation mit den Datenbanken vermuten ließen, wurde der
Anwendung eine dritte Schnittstelle zur Datenhaltung hinzugefügt: dem Schreiben und Lesen
von Werten aus und in Textdateien, die mit JSON formatiert sind. Zur einfachen Erzeugung
geeigneter Inputdateien wurden Teile der bereits bestehenden Datenbanken mithilfe der IntelliJ
IDEA IDE [33] exportiert.

                                                                                           17
3 Testaufbau

18
4 Evaluation
Die Evaluation der behandelten Frameworks auf Basis des von mir geschriebenen Codes soll
mithilfe des Analytischen Hierarchieprozesses von Thomas L. Saaty [57] durchgeführt werden.
Hierfür werden zuerst Evaluationskriterien in harte und weiche Kriterien unterteilt. Harte Kriterien
(KHx, mit x einer fortlaufenden Nummer) werden bei der abschließenden Bewertung berücksichtigt,
während weiche Kriterien (KWy, mit y einer fortlaufenden Nummer) zwar bei der Gegenüber-
stellung betrachtet werden, jedoch in die Bewertung nicht mit einfließen. Nach der Aufstellung
und Gliederung der Kriterien werden die Frameworks im darauffolgenden Teil anhand von ihnen
miteinander verglichen. Im letzten Unterkapitel werden drei verschiedene Anwendungsfälle einge-
führt, welche Grundlage für die Gewichtung der Kriterien und Bewertung der Frameworks sind.
Die angesprochene Gewichtung und die abschließende Evaluation mit einer Auswertung der zuvor
gesammelten Daten, aufgeteilt auf die Anwendungsfälle, erfolgt ebenso in Punkt 4.4.

4.1 Analytischer Hierarchieprozess nach Saaty
Der Analytische Hierarchieprozess (AHP) ist ein Instrument zum Vergleich verschiedener Optionen
in einem Sachverhalt, um die Entscheidungsfindung zwischen ihnen zu vereinfachen. Der von
Thomas L. Saaty 1971 bis 1975 entwickelte und in seinem Buch The Analytic Hierarchy Process [57]
beschriebene Vorgang stützt sich dabei auf paarweise Gegenüberstellungen, bei denen jeweils
ein Gewinner und eine Gewichtung des Vergleichs bestimmt werden muss. Letztere erfolgt dabei
durch das Vergeben einer Punktzahl von eins bis neun – eine eins steht für gleiche Wichtigkeit der
verglichenen Partner, fünf etwa für eine deutlich höhere Bedeutung des Siegers, und neun für eine
extrem größere Wichtigkeit des Gewinners in dem behandelten Vergleich; die dazwischenliegenden
Bewertungen ergeben sich entsprechend. Eine weitere Eigenschaft des AHP ist, dass Inkonsistenzen
im Bewertungsprozess rechnerisch aufgedeckt und somit korrigiert werden können. [56, 61]
  Verglichen werden zum einen die Kriterien bezüglich ihrer Relevanz in der gegebenen Situation.
Um ein möglichst aussagekräftiges Ergebnis liefern zu können, werden in Abschnitt 4.4 drei verschie-
dene Anwendungsfälle getrennt voneinander behandelt. Die Gewichtung der Evaluationskriterien
der Frameworks nach dem AHP erfolgt deshalb in diesem Teil für jeden Fall individuell. Das
Ergebnis ist eine Zerlegung der Eins durch die Gewichtungen der Kriterien, wobei eine höhere
Prozentzahl für eine höhere Priorität für den entsprechenden Fall steht. Es gilt also
                            X
                               gk = 1      und     ∀k ∈ K : 0 < gk < 1
                           k∈K

mit K der Menge der gewichteten Evaluationskriterien, gk der Gewichtung von Kriterium k.
  Zum anderen werden Spring Boot und Buffalo auf Basis der Kriterien gegenübergestellt. Hier
spielt nicht mehr die Priorität der Kriterien, sondern lediglich die Performance der Frameworks
eine Rolle. Der Vergleich erfolgt wie oben mit einer Skala von eins bis neun. Für KH3 und KH4
kann dieser Vergleich fallübergreifend vorgenommen werden, für KH1 und KH2 muss – aufgrund
der unterschiedlichen Messergebnisse bei Verwendung unterschiedlicher Betriebssysteme – pro
Anwendungsfall eine separate Beurteilung abgegeben werden. Am Ende können pro Anwendungsfall

                                                                                                 19
4 Evaluation

die Prozentwerte der Prioritäten der Kriterien und die der Gewichtungen der Alternativen mitein-
ander verrechnet werden, wodurch sich ein Gesamtwert in Prozent jeweils für Spring Boot und
Buffalo für diesen Anwendungsfall ergibt. Für diese Arbeit wurden die für den Prozess notwendigen
Berechnungen mithilfe eines online verfügbaren Tools der Business Performance Management
Singapore (BPMSG) [24] angefertigt.

4.2 Definition der Evaluationskriterien
Wie oben bereits beschrieben, werden hier die harten und weichen Kriterien definiert, anhand
derer später die Evaluation erfolgt. Der Vollständigkeit halber soll an dieser Stelle zusätzlich noch
das Kriterium der Skalierbarkeit betrachtet werden, welches im Weiteren nicht mehr behandelt
wird. Gerade im Bereich des Cloudcomputing ist Skalierbarkeit von Mikroservices eine wichtige
Voraussetzung für den Erfolg einer Anwendung, da sowohl ein Service häufig redundant auf
verschiedenen Servern läuft, auf einem Server aber auch viele verschiedene Services betrieben
werden können. Nachdem die Programmiersprache Go auch mit besonderem Augenmerk auf
Webanwendungen geschrieben wurde, ist damit zu rechnen, dass, speziell aufgrund des Threading-
Ansatzes, Go-Anwendungen in diesem Bereich gut abschneiden. Java wurde ursprünglich nicht
mit Fokus auf Cloud-Deployment entwickelt, Spring Boot bringt jedoch einige passende Dienste
zur automatischen Skalierung mit. Beispiele hierfür sind ein Endpoint, welcher Metriken über die
Anwendung zur Verfügung stellt, ein Endpoint, über den ein geordnetes Herunterfahren möglich ist,
dynamische Portzuweisung, sowie eine einfache Anbindung an Netflix Eureka (einem REST-AWS-
Service für Lastverteilung und Ausfallsicherung [17]) [43]. Konkrete Tests in diese Richtung, oder
auch Untersuchungen speziell zum Einfluss von Buffalo auf diesem Gebiet, konnten im Rahmen
dieser Arbeit jedoch nicht untersucht werden, weshalb dieses Kriterium nicht in die Evaluation
einfließt.

4.2.1 Harte Kriterien
KH1 & KH2: Durchschnittliche Laufzeit und Standardabweichung der Laufzeit
Als erste harte Kriterien, soll die Laufzeit der erstellten Anwendungen untersucht werden. Unter-
schieden wird dabei zwischen der durchschnittlichen Laufzeit in KH1 und der Standardabweichung
davon in KH2. Auf diese Weise kann sowohl reine Geschwindigkeit als auch Konsistenz betrachtet
werden. Ein Vorteil dieser Benchmarks ist auf der einen Seite, dass sie objektive Ergebnisse in
Form von eindeutigen, statistisch auswertbaren Zahlwerten liefern. Die konkrete Bewertung dieser
Werte mag zwar einen gewissen subjektiven Charakter haben, das generelle Resultat der Messung
(beispielsweise „Service A ist in Framework X durchschnittlich um t Millisekunden schneller als in
Framework Y“) bleibt jedoch bestehen. Auf der anderen Seite ist ein Nachteil dieses Verfahrens,
dass es von der vorliegenden Implementierung abhängt. So können verschiedene Funktionalitäten in
einer bestimmten Sprache unterschiedlich performant sein. Zudem kann die gleiche Funktionalität
durch Verwendung verschiedener Entwicklungsansätze unterschiedlich effizient gestaltet werden.
Um durch diesen Faktor möglichst wenig Ungenauigkeiten in meinen Messwerten zu erzeugen,
habe ich zwei Maßnahmen ergriffen: Erstens habe ich mich, wie bereits in Kapitel 3 erläutert, bei
der Entwicklung pro Service auf jeweils unterschiedliche Funktionen und Schnittstellen fokussiert,
damit sich mögliche Bottlenecks in einem Bereich der Sprache oder des Frameworks nicht auf
die gesamte Anwendung niederschlagen. Zweitens wurde darauf geachtet, die jeweils zusammen-
gehörigen Implementierungen in den beiden Frameworks möglichst ähnlich zu halten. Aufgrund
der unterschiedlichen Programmiersprachen kann der Code natürlich nicht gleich sein, auch die

20
Sie können auch lesen