Sie sind auf Seite 1von 80

Migration von REST nach GraphQL in einer

bestehenden Webapplikation

Bachelor-Thesis im Fachbereich Informatik

Autor Yannick Schröder


Informatik 102751
inf102751@fh-wedel.de

Erstgutachter Dr. Michael Predeschly


mpr@fh-wedel.de

Zweitgutachter M. Sc. Marcus Riemer


mri@fh-wedel.de

Eingereicht am 09. September 2020


Yannick Schröder
Migration von REST nach GraphQL in einer bestehenden
Webapplikation
Bachelor-Thesis im Fachbereich Informatik, 09. September 2020
Gutachter: Dr. Michael Predeschly und M. Sc. Marcus Riemer
Fachhochschule Wedel
Feldstraße 143
22880 Wedel
Inhaltsverzeichnis

1 Einleitung 1

2 Grundlagen 3
2.1 REST API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.1 Client-Server-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Zustandslosigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.3 Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.4 Einheitliche Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.5 Weitere Einschränkungen . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 GraphQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Ausgewählte Details des Typescript Typsystems . . . . . . . . . . . . . . 11
2.4 JSON Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5 Postgres jsonb und hstore Typen . . . . . . . . . . . . . . . . . . . . . . . 16

3 Anforderungsanalyse 18
3.1 Aktuelles System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.2 Praxisbeispiel - Erweiterung des Datenmodels . . . . . . . . . . . . . . . . 19
3.2.1 Anlegen des Typescript Interfaces . . . . . . . . . . . . . . . . . . 19
3.2.2 Generierung der JSON Schema Definitionen . . . . . . . . . . . . . 20
3.2.3 Anlegen des Models in Rails . . . . . . . . . . . . . . . . . . . . . . 21
3.2.4 Anlegen eines Controllers in Rails . . . . . . . . . . . . . . . . . . 21
3.2.5 Dataservices auf dem Client . . . . . . . . . . . . . . . . . . . . . . 23
3.2.6 Komponenten auf dem Client . . . . . . . . . . . . . . . . . . . . . 24
3.2.7 Anlegen einer neuen Sicht . . . . . . . . . . . . . . . . . . . . . . . 25
3.3 Vorteile des bisherigen Ansatzes . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3.1 Typescript Typsystem . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3.2 Typescript zu JSON Schema Generatoren . . . . . . . . . . . . . . 27
3.3.3 Modularität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3.4 Typsicherheit zur Kompilierungszeit . . . . . . . . . . . . . . . . . 27
3.3.5 Typsicherheit zur Laufzeit . . . . . . . . . . . . . . . . . . . . . . . 28

iii
3.3.6 Stabilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.4 Nachteile des bisherigen Ansatzes . . . . . . . . . . . . . . . . . . . . . . . 29
3.4.1 Manuelle SQL Queries . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.4.2 Auswahl von Attributen . . . . . . . . . . . . . . . . . . . . . . . . 29
3.4.3 camelCase und snake_case Notationen . . . . . . . . . . . . . . . . 30
3.4.4 Hoher Aufwand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.5 Anforderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.5.1 Darstellungsvielfalt . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.5.2 Typdefinitionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.5.3 Typsicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.5.4 Performance und Skalierbarkeit . . . . . . . . . . . . . . . . . . . . 37
3.5.5 Validierung von jsonb und hstore . . . . . . . . . . . . . . . . . . . 39
3.5.6 Benennungskonvention . . . . . . . . . . . . . . . . . . . . . . . . . 39

4 Implementierung 40
4.1 GraphQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.1.1 Integration von graphql-ruby . . . . . . . . . . . . . . . . . . . . . 42
4.2 Praxisbeispiel - Erweiterung des Datenmodels . . . . . . . . . . . . . . . . 44
4.2.1 Anlegen des Models in Rails . . . . . . . . . . . . . . . . . . . . . . 44
4.2.2 Anlegen eines GraphQL Objekttypen . . . . . . . . . . . . . . . . . 44
4.2.3 Anlegen eines Input Typen . . . . . . . . . . . . . . . . . . . . . . 46
4.2.4 Anlegen eines Query Endpunktes . . . . . . . . . . . . . . . . . . . 47
4.2.5 Anlegen der Resolver Klasse . . . . . . . . . . . . . . . . . . . . . . 48
4.2.6 Erstellen einer GraphQL Query . . . . . . . . . . . . . . . . . . . . 49
4.2.7 Codegenerierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.2.8 Anlegen einer Angular Komponenten . . . . . . . . . . . . . . . . . 52
4.2.9 Anlegen einer neuen Sicht . . . . . . . . . . . . . . . . . . . . . . . 53
4.3 Scalar Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.4 Enum Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.5 Mutationen als Objekttypen . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.6 Felder Auswahl, Sprachauswahl, Filtern und Sortieren . . . . . . . . . . . 57
4.7 Paginierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.7.1 Connection Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.7.2 Einheitliche Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.8 Validieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.8.1 Multilinguale Strings . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.8.2 JSON Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

iv
4.9 Unerwartete Hindernisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.9.1 Fehlerhafte Codegenerierung der Angular Services . . . . . . . . . 64
4.9.2 Unmöglichkeit der Modellierung mancher Typen . . . . . . . . . . 64

5 Fazit 65
5.1 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

6 Literaturverzeichnis 69

7 Eidesstattliche Erklärung 74

v
Einleitung 1
Ziel dieser Arbeit ist die Migration von REST nach GraphQL in der von Marcus Rie-
mer entwickelten Lehr-Entwicklungsumgebung BlattWerkzeug, mit Evaluierung ob die
Migration den Aufwand Wert ist.

GraphQL ist eine Abfragesprache und serverseitige Runtime zur Implementierung webba-
sierter APIs. Die Sprache stellt eine Alternative zu populären REST-basierten APIs dar,
wobei der Kernunterschied die Verlagerung der Entscheidung über die genauen Daten,
die von API-Aufrufen zurückgegeben werden, von den Servern auf die Clients ist [GB].
GraphQL wurde 2015 von Facebook als eine laufende Arbeit veröffentlicht [Inca] und im
November 2018 von Facebook in die neu gegründete GraphQL Foundation unter dem
Dach der gemeinnützigen Linux Foundation ausgegliedert. Es wurde schnell populär,
als neue Unternehmen und Hobbyisten begannen, es auszubauen. Schließlich wurde die
Technologie von größeren Unternehmen übernommen, beginnend mit GitHub im Jahr
2016 und später von Twitter, Yelp, The New York Times, Airbnb und anderen [Foud].

Das System hinter BlattWerkzeug hat seit Veröffentlichung im Jahre 2016 [Rie16b] einige
Veränderungen durchlebt. Zu Beginn wurde auf dem Client Typescript mit Angular 2
und auf dem Server Ruby mit Sinatra eingesetzt. Im Mai 2016 wurden JSON Schema
Dateien in einem Schema Ordner auf Projekt Ebene bereitgestellt [Rie16a]. Im Juni
2017 wurde dann der Grundstein für einen Rails Server gelegt [Rie17a] und 2 Monate
später auf eine PostgreSQL Datenbank umgestellt [Rie17b]. Das System funktioniert
einwandfrei. Problematisch ist jedoch, dass im Client die Anforderungen mit der Zeit
deutlich diverser wurden und zukünftig noch werden. Das aktuelle System stößt dabei
an die Grenzen des noch zu vertretenden Entwicklungsaufwands und läuft damit Gefahr
den Entwicklungsaufwand zu lähmen.

Im Kern dieser Arbeit wird eine vorhandene REST-artige Schnittstelle durch eine neue-
re Technologie (GraphQL) weitestgehend ersetzt. Zudem werden alle Berührungspunkte
der REST-artigen Schnittstelle ebenfalls auf die Nutzung von GraphQL angepasst. Dazu
gehören neben der naheliegenden Kommunikationsschnittstelle auf dem Server, auch cli-

1
entseitige Implementierungen, die durch die Migration von GraphQL angepasst werden
müssen. Nachfolgend werden Grundlagen beschrieben, deren Kenntnis im späteren Ver-
lauf vorausgesetzt wird. Anschließend werden das aktuelle System bewertet und Anforde-
rungen an ein neues System formuliert. Zuletzt wird die Implementierung von GraphQL
erläutert und ein Fazit gezogen, ob es eine lohnenswerte Migration für BlattWerkzeug
ist.

2
Grundlagen 2
Das Hauptmotiv bei der Nutzung des Internets ist die Informationsaufnahme [Eim]. In-
formationen werden auf unzähligen Webapplikationen bereitgestellt, die jeder mit einem
Internetzugang einsehen kann, solange der Zugriff auf die Informationen nicht sonderlich
geschützt wird.

Um persistente und sensible Daten gesichert und nicht für Jedermann zugreifbar lagern
zu können, werden sie serverseitig gehalten. Möchte man diese Daten zusätzlich filtern,
sortieren oder mehrere Datensätze miteinander verknüpfen, wird eine Datenbank benö-
tigt. Eine Datei, in der die Daten abgelegt werden, wäre auch eine Option, allerdings
müsste man alle Methoden zum Filtern, Sortieren und Verknüpfen selber implementie-
ren.

Die in einer Datenbank gespeicherten Informationen sind also aus Nutzersicht nur über
eine Anfrage an den Server abrufbar. Somit ist der Server das Bindeglied zwischen ei-
nem Client und der Datenbank und kümmert sich um Aufgaben wie Authentifizierung
des Nutzers und Überprüfung der Autorisierung bezüglich der angefragten Daten, aber
auch um die Zusammensetzung und Ausführung von Datenbankabfragen. Daraus geht
hervor, dass ein Client nur begrenzten Zugriff bekommt, da die Ausführung von vor-
definierten Funktionen, die Anfragen an die Datenbank beinhalten, lediglich angefragt
werden kann. Werden die vordefinierten Funktionen den Bedarf an Informationen nicht
gerecht, müssen neue Funktionen entwickelt oder mithilfe von mehreren Anfragen die
Daten zusammengesammelt werden.

Dieser Entwicklungsaufwand könnte verringert werden, indem der Client mehr Flexibili-
tät, Verantwortung und Effizienz besitzen würde, z.B. durch eine direkte Anbindung an
die Datenbank. Er könnte exakt die benötigten Daten mit nur einer Anfrage direkt und
effizient aus der Datenbank auslesen. Jedoch würde dieser Ansatz viele Gefahren mit sich
bringen. Ein Client der direkten Datenbankzugriff erlangt, könnte unerwünschte Trans-
aktionen in der Datenbank ausführen, wodurch der erwartete Datenbestand geändert,
Einträge gar gelöscht oder sensible Daten anderer Nutzer abgefragt werden könnten. Al-

3
so sollten Zugriffsbeschränkungen erteilt werden, die auf der Datenbankschicht realisiert
werden, da clientseitiger Code nach Belieben vom Nutzer eingesehen und verändert wer-
den kann. Hinzu kommen weitere Herausforderungen, wenn die Verbindung zur Daten-
bank veröffentlicht wird, wie zum Beispiel das Schützen vor zu exzessiver Nutzung oder
das Ausnutzen von bekannten Sicherheitslücken bei nicht aktuellsten Versionen [Groc].
Alles in allem ist das ein Verfahren, von dem dringend abgeraten wird, da es in den
wenigsten Fällen nutzbringend und sicher gehandhabt werden kann [svi].

Im Folgenden werden diese Probleme anhand von grundlegenden Inhalten, die aktuell
Gegenstand des von Marcus Riemers entwickelten Systems [Rie16b] sind wieder aufge-
griffen und bei Erläuterungen bzw. Code-Beispielen als bekannt vorausgesetzt. Dazu
gehören die Unterkapitel 2.1 „REST API“, 2.3 „Ausgewählte Details des Typescript
Typsystems“, 2.4 „JSON Schema“ und 2.5 „Postgres jsonb und hstore Typen“. Diese
müssen für die Schaffung und Umsetzung von Verbesserungen grundlegend verstanden
werden. Bei dem Kapitel 2.2 „GraphQL“ handelt es sich um eine Abfragesprache und
Laufzeitumgebung, für die im Laufe der Arbeit evaluiert wird, ob sie gewinnbringend in
das bestehende System migriert werden und Teile ersetzen kann.

2.1 REST API

Der Begriff Representational State Transfer (abgekürzt REST, seltener auch ReST) wur-
de erstmalig in der Dissertation „Architectural Styles and the Design of Network-based
Software Architectures“ von Roy Thomas Fielding im Jahr 2000 geprägt [Fie00]. Er be-
schreibt REST als einen Architekturstil für verteilte Systeme, welcher in eine einheitliche
Schnittstelle für Kommunikation mündet. Dieser Architekturstil oder auch Programmier-
paradigma wird durch verschiedene Software-Engineering-Prinzipien und Beschränkun-
gen definiert. Im Folgenden werden die Prinzipien von REST näher erläutert.

2.1.1 Client-Server-Modell

Der Ausgangspunkt des Client-Server-Modells ist eine strikte Trennung der Benutzer-
oberfläche von der Datenhaltung/-verwertung. Das bedeutet wiederum, dass kein HTML,
CSS und Javascript vom Server an den Client geschickt wird, sondern ausschließlich Da-
tensätze meist in Form von XML oder JSON, die clientseitig in die Benutzeroberfläche
eingebaut werden. Dadurch verbessert sich die Portabilität der Benutzeroberfläche in

4
Bezug auf die Anbindung an verschiedene Datenhaltungs/-verwertungs-Systeme, also
die Wiederverwendbarkeit und die Skalierbarkeit aufgrund der Vereinfachung der Ser-
verkomponenten.

2.1.2 Zustandslosigkeit

Zustandslosigkeit ist eine Beschränkung in Bezug auf die Kommunikation zwischen Ser-
ver und Client. Anfragen vom Client müssen alle Informationen beinhalten um diese
interpretieren zu können. Insbesondere werden Anfragen ohne Bezug zu früheren Anfra-
gen behandelt und keine Sitzungsinformationen - wie Authentifizierungs- und Authorisie-
rungsinformationen - ausgetauscht bzw. verwaltet. Diese befinden sich ausschließlich auf
dem Client und müssen bei Anforderung von geschützten Daten der Anfrage beigefügt
werden.

Vorteile aus dieser Beschränkung sind, dass Anfragen unabhängig voneinander betrach-
tet werden können und somit z.B. von mehreren Maschinen parallel ausgeführt werden
können, da jede Anfrage für sich eine vollständige Anforderung an den Server beschreibt.
Zudem kann einfacher auf den Misserfolg einer Anfrage reagiert werden als auf eine er-
folglose Kette von zusammenhängenden Anfragen und es ist nicht vonnöten Zwischenzu-
stände bzw. Status zu speichern, welche die Ressourcenauslastung erhöhen würde. Dies
kann jedoch zu einer verringerten Netzwerkleistung führen aufgrund von Zusatzinfor-
mationen, die sich bei mehreren verschiedenen Anfragen wiederholen und erneut mit
gesendet werden müssen.

2.1.3 Cache

In Hinblick auf das Verbessern der Netzwerkleistung wurde ein Cache als Einschränkung
hinzugefügt. Diese Einschränkung setzt voraus, dass Daten aus einer Antwort vom Server
implizit oder explizit als cachefähig oder nicht cachefähig gekennzeichnet werden. Werden
Daten in einer Antwort auf eine Anfrage als cachefähig gekennzeichnet, kann der Client
diese Information speichern und erhält das Recht diese bei einer späteren gleichwertigen
Anfrage wiederzuverwenden. Somit können Anfragen effizienter behandelt bzw. ganz
durch eine direkt aus dem Cache geladene Antwort ersetzt werden.

5
2.1.4 Einheitliche Schnittstelle

Ein zentrales Merkmal von REST ist die einheitliche und vom Dienst entkoppelte Schnitt-
stelle. Auf jede Ressource muss über einen einheitlichen Satz an URLs, hinter denen sich
Transaktionen zum Erstellen, Lesen, Aktualisieren und zum Löschen (CRUD) verbergen,
zugegriffen werden können.

Durch eine einheitliche Komponentenschnittstelle wird die Sichtbarkeit der einzelnen


Interaktionen erhöht. Dies bedeutet, dass es für jede Ressource eine Menge fest definierter
Interaktionen gibt, die sich in ihrer Struktur nur durch den Namen der Ressource und
ihre Fremdbeziehungen unterscheiden.

CRUD SQL HTTP URL Bedeutung


Operation
Create INSERT POST /projects Erstellen eines Projekts
Read SELECT GET /projects Abrufen aller Projekte
Read SELECT GET /projects/:id Abrufen eines Projekts
Read SELECT GET /projects/:id/ Abrufen aller Nutzer eines
users Projekts
Read SELECT GET /users/:id/ Abrufen aller Freunde eines
friends Nutzers
Update UPDATE PATCH/PUT /projects/:id Aktualisieren eines Projekts
Delete DELETE DELETE /projects/:id Löschen eines Projekts

Tab. 2.1: Einheitliche REST Schnittstellen

Das hat zur Folge, dass anwendungsspezifische Daten in einer standardisierten Form
übertragen werden müssen, wodurch die Effizienz der Datenübertragung Mängel aufwer-
fen kann. Insbesondere treten im Kontext der Arbeit zwei für REST bekannte Mängel
auf, die hier näher erläutert werden.

Overfetching
Aufgrund der einheitliche Komponentenschnittstelle gibt es für eine Liste aller
Projekte genau eine Route GET /projects, die zu jedem Projekt alle Attribute
ungekürzt liefert. Wird nur ein Teil der Attribute benötigt, sind weitere Angaben
nutzlos und damit ineffizient. Dennoch werden sie von der API mitgeliefert.

Underfetching
Ein weiteres Problem ist, dass mehrere Anfragen für Daten, die in Beziehung zu-
einander stehen, benötigt werden. In Abbildung 2.1 muss für jeden Nutzer eines

6
Projektes erfragt werden, welche Freunde dieser hat. Vorausgesetzt wird, dass eine
entsprechende Route für die Abfrage nach Freunden eines Nutzers existiert. Sind
einem Projekt N Nutzer zugeordnet, muss für diese Information die dritte Abfrage
N mal abgeschickt werden. Da zusätzlich noch die Liste von mit dem Projekt in
Beziehung stehenden Nutzern erfragt wird, spricht man von N + 1 Abfragen.

/projects/<id>
1 /projects/<id>/users
{
  "projects": { /users/<id>/friends
    "id": 1,
    "name": "Esqulino",
    "public": false
 }
}

/projects/<id>
2 /projects/<id>/users
{
  "user": [{ /users/<id>/friends
    "id": 1,
    "name": "Yannick",
  }, ... ]
}

/projects/<id>
3
/projects/<id>/users
{
  "friends": [{ /users/<id>/friends
    "id": 2,
    "name": "Philipp",
  }, ... ]
}

Abb. 2.1: Abfragen von Projektdaten inklusive aller Nutzer die das Projekt einsehen können

2.1.5 Weitere Einschränkungen

Zusätzlich gibt es noch die Einschränkung Layered System, welches das Prinzip eines hier-
archisch in Schichten aufgebauten Systems beschreibt und eine optionale Einschränkung
Code-On-Demand, welche die Client-Funktionalität durch Herunterladen und Ausführen
von Code in Form von Applets oder Skripten erweitert. Diese werden im Rahmen der
Arbeit nicht genutzt und deshalb nur am Rande erwähnt.

2.2 GraphQL

Bei GraphQL handelt es sich um eine Abfragesprache für APIs und eine Laufzeitumge-
bung zum Ausführen dieser Abfragen und Wiedergeben von Daten unter Verwendung

7
eines von für die Daten definierten Typensystems. Es ist an keinerlei Datenbanksysteme
gebunden und lässt sich gut mit vorhandenen Code und Daten verbinden.

Ein GraphQL Service entsteht durch das Definieren eines Typschemas, vergleichbar mit
Datenbanktabellen. Zu jedem Attribut (Feld) eines Typs lassen sich - genauso wie bei
Datenbanken - Datentypen und Restriktionen wie NOT NULL definieren.

1 type Project {
2 id: ID!
3 public: Boolean
4 }

Listing 2.1: Project Typdefinition

Das aufgeführte Codefragment 2.1 „Project Typdefinition“ definiert einen Typ Project
mit zwei Feldern, welcher ein Programmierprojekt darstellen soll.

• id: hat den von GraphQL vorgegebenen Typen ID, der als eindeutiger String ge-
wertet wird und nicht dazu gedacht ist vom Menschen „lesbar“ zu sein. Zusätzlich
wurde mit „!“ festgelegt, dass dieses Feld nicht Null sein darf.

• public: besitzt den Typen Boolean, der den Wert Null annehmen kann. Es gibt
an, ob das Projekt bereits veröffentlicht und für jeden zugreifbar gemacht wurde.

Im Gegensatz zu einem Datenbank-Schema ermöglicht das Typsystem von GraphQL zu


jedem Typen Argumente zu definieren, die wiederum einer Funktion (Resolver) überge-
ben werden können, die aufgerufen wird, wenn das Feld im Kontext einer Query aufgelöst
werden soll [TGb].

Für ein Beispiel wird der Typ Project um ein Feld erweitert:

1 enum LanguageEnum {
2 DE
3 EN
4 FR
5 }
6
7 type Project {
8 id: ID!
9 public: Boolean
10 name(language: LanguageEnum = DE): String!
11 }

Listing 2.2: Erweiterung der Typdefinition von Project und Einführung eines Enums mit
Ländercodes

8
• name: besitzt den Typ String, der ebenfalls nicht den Wert Null annehmen kann.
Zusätzlich wurde dem Feld ein Argument language zugeteilt, welches den selbst
definierten Datentypen LanguageEnum besitzt und den Default Wert DE setzt.

Zusätzlich wird eine Funktion benötigt, die das Argument language verarbeiten kann
und dementsprechend den Rückgabewert formuliert. Solche Funktionen nennen sich in
der Welt von GraphQL Resolver. Aufgabe dieses Resolvers ist es - durch Aufruf der in
Zeile 2 Listing 2.3 als Pseudocode aufgeführten Funktion translate - den Namen eines
Projektes in die übergebene Sprache zu übersetzen.

1 name(obj , args , context , info) {


2 return translate(obj ,args['language '])
3 }

Listing 2.3: Resolver des Feldes name

Damit nun ein Datentyp abgefragt werden kann, müssen Queries zu den Datentypen
definiert werden.

1 type Query {
2 projects: [Project !]!
3 }

Listing 2.4: GraphQL Query Typdefinition

Der Query Typ gehört zum Typsystem von GraphQL. Er beinhaltet alle für das Schema
definierten Queries (siehe Listing 2.4). In diesem Fall ist es lediglich eine Query mit
dem Bezeichner projects, die ein Array vom Typ Project als Rückgabewert erwartet.
Zusätzlich wurde angegeben, dass Projekte innerhalb des Arrays und das Array selber
nicht Null sein dürfen. Es wird also mindestens eine leeres Array erwartet, aber keinesfalls
Null oder ein Array, das mit Null-Werten gefüllt ist [Fouc].

Um Queries im GraphQL Schema bereitzustellen, wird auf oberster Ebene ein Einstiegs-
punkt definiert (siehe Zeile 2 Listing 2.5). Über diesen können alle Anfragen gefunden
werden, die der GraphQL Service behandeln soll.

1 schema {
2 query: Query
3 mutation: Mutation
4 }

Listing 2.5: GraphQL Schema Definition

Jetzt kann dem Client die Freiheit gewährt werden, eigene Abfragen für genau den
Datensatz, der benötigt wird, zu formulieren. Zudem lässt sich anhand der gestellten

9
Abfrage die Struktur der erhaltenen Antwort festlegen. Dies könnte wie in Listing 2.6
aussehen.

1 query Projects{
2 projects {
3 id
4 name(language: EN)
5 }
6 }

Listing 2.6: GraphQL Query mit dem Bezeichner Projects

Das aufgeführte Listing 2.6 „GraphQL Query mit dem Bezeichner Projects“ verkörpert
eine GraphQL Query mit dem Bezeichner Projects, die für alle vorhandenen Projekte
die Felder id und name zurück gibt. Aus dieser Query lässt sich nicht der Rückgabewert
von projects erschließen. Diese Information erhält man als Entwickler lediglich aus den
Query Definitionen. Bei Abschicken der Query wird neben der Query auch der Bezeichner
als operationName mit gegeben. Nachdem eine GraphQL Query gegen das Typsystem
validiert wurde, wird sie von dem GraphQL Modul ausgeführt und ein Ergebnis - typi-
scherweise in Form von JSON - zurückgegeben, das die Form und Struktur der Anfrage
spiegelt (siehe Listing 2.7).

1 {
2 "projects": [
3 {
4 "id": "368 b6ee9 -2b1f -4661 -a82f -ff7b62dc9251"
5 "name": "Esqulino"
6 },
7 {
8 "id": "b25c342e -f2b1 -4a74 -8124 - a7a688911380"
9 "name": "Trucklino"
10 }
11 ]
12 }

Listing 2.7: JSON Antwort auf die Projects Query

Dies könnte zum Beispiel der bereits definierte Query-Typ projects sein. Damit der
GraphQL Server eine Anfrage an eine an den Server gebundene Datenbank schicken
kann, wird die zum Query-Typ definierte Resolver-Funktion ausgeführt [Foub]. Innerhalb
dieser Resolver-Funktion ist der Zugriff auf das Dateisystem festgelegt, sodass neben
Datenbanken sogar Dateien als Speichermedium genutzt werden könnten.

GraphQL bietet neben Queries zum Abfragen von Datensätzen auch Anfragen zum Spei-
chern von Daten im gewählten Speichermedium. Solche Anfragen nennen sich Mutatio-
nen (siehe Listing 2.8). Genau wie bei Resolvern eines Feldes wird bei einer Mutation
Code hinzugefügt, der für das Erstellen oder Ändern von Datensätzen zuständig ist. Tech-
nisch gesehen könnte jede Query auch so implementiert werden, dass diese das Speichern

10
von Daten bewirkt. Es ist jedoch nützlich, eine Konvention festzulegen, dass alle Ope-
rationen, die Schreibvorgänge verursachen, explizit über eine Mutation gesendet werden
sollten [TGa].

1 type creationResponse {
2 id: String
3 errors: [String]
4 }
5
6 type Mutation {
7 createProject(name: String!, public: Boolean ):creationResponse
8 }
9
10 mutation CreateProject($name: String!, $public: Boolean ) {
11 createProject(name: $name , public: $public) {
12 id
13 errors
14 }
15 }

Listing 2.8: GraphQL Mutation zum Erstellen eines Projektes

• Zeile 1-4: Festlegung eines Typs mit den Feldern id und errors. Dieser Typ spiegelt
den Rückgabewert einer Mutation zum Erstellen neuer Datensätze wider. Wenn die
Mutation erfolgreich war, wird die id des neuen Datensatzes zurück gegeben. Wenn
Fehler aufgetreten sind, wird das errors Feld mit diesen gefüllt.

• Zeile 6-8: Definition eines Mutations-Typs, welcher alle Mutationen beinhaltet, ähn-
lich wie beim Queries-Typ in Listing 2.4. Dieser erhält die Mutation createProject,
welche zwei Argumente name und public übergeben bekommt. Letzteres ist dabei
optional.

• Zeile 10-15: Festlegen einer Mutation mit dem Operations Namen CreateProject,
die das Argument name als String zwingend erwartet und das optionale Argument
public bekommt. Diese Argumente werden dann der createProject Mutation
übergeben. Als Antwort auf die Mutation werden die Felder id und errors erwar-
tet.

2.3 Ausgewählte Details des Typescript


Typsystems

Typescript ist ein typisiertes Superset von Javascript, das zu reinem Javascript kom-
piliert [Cord]. Das heißt, es beinhaltet alle Funktionalitäten von Javascript und wurde
darüber hinaus erweitert und verbessert [Far]. Dazu gehört das in Typescript eingeführte
Typsystem. Selbstverständlich besitzt Javascript ebenfalls Typen, doch kann eine Varia-

11
ble, auf die ursprünglich eine number zugewiesen wurde, auch als string enden. Das
kann schnell zu unbedachten Seiteneffekten führen.

Ein Typsystem ist eine Menge von Regeln, die jeder Variable, jedem Ausdruck, jeder
Klasse, jeder Funktion, jedem Objekt oder Modul im System einen Typ zuweist. Diese
Regeln werden zur Kompilierungszeit (statische Typprüfung) oder zur Laufzeit (dynami-
sche Typprüfung) geprüft, um Fehler in einem Programm aufzudecken [Mwi].

Der Typescript-Compiler prüft zur Kompilierungszeit alle Variablen und Ausdrücke auf
ihren Typen und entfernt anschließend alle Typinformationen bei der Konvertierung zu
Javascript Code [Corb]. Die im folgenden Beispiel in Listing 2.9 deklarierte Funktion
gibt die zweite Hälfte eines übergebenen Strings zurück. Der erste Aufruf der Funktion
führt zu einem Fehler beim Kompilieren. Es wird also direkt darauf hingewiesen, dass es
sich um ein fehlerhaften Code handelt.

1 function printSecondHalf(s: string) {


2 return s.substr(s.length /2);
3 }
4 printSecondHalf (123); // Error bereits zur Kompilierungszeit
5 printSecondHalf("hello"); // Ok - "llo"

Listing 2.9: Typescript Funktion mit typisiertem Parameter

Nach der Kompilierung sind alle Typinformationen entfernt worden, wodurch erst durch
einen fehlerhaften Aufruf ein TypeError auftritt (siehe Listing 2.10).

1 function printSecondHalf(s) {
2 return s.substr(s.length /2);
3 }
4 printSecondHalf (123); // Error zur Laufzeit - TypeError: s.substr is not a function
5 printSecondHalf("hello"); // Ok - "llo"

Listing 2.10: Zu Javascript kompilierte Funktion

Nehmen wir an, wir möchten den in Kapitel GraphQL 2.2 „GraphQL“ erstellten Typen
Project nutzen, um eine Funktion zu schreiben, die einen neuen Project Datensatz an
den Server schickt. Um diesen Typen clientseitig nutzen zu können, können wir ein äqui-
valentes Typescript Interface erstellen oder eines generieren lassen (siehe Listing 2.11).

1 interface Project {
2 id: string ,
3 name: string
4 }

Listing 2.11: Typescript Project Interface

12
Wollen wir jetzt einen neuen Datensatz an den Server schicken, können wir das Interface
nutzen. Jedoch ist nur der Name des neuen Datensatzes bekannt, die id ist eine uuid
und wird serverseitig generiert. Also wollen wir die id beim clientseitigen Erstellen außen
vorlassen. Dafür bietet Typescript unter einer Vielzahl von Werkzeugen, die allgemeine
Typtransformationen ermöglichen [Corc], Omit<T,K>, das alle Attribute von T nimmt
und anschließend K aus den Attributen entfernt (siehe Listing 2.12).

1 type PostProject = Omit <Project , 'id'>; // Äquivalent zu Pick <Project , 'name '>

Listing 2.12: Transformierter Project Typ

Der Typ PostProject beinhaltet also alle Felder von Project, allerdings ohne id. Das
Gegenstück zu Omit<T,K> wäre Pick<T,K>, welches aus dem Typ T nur die Attribute K
nimmt. Mithilfe dieser Typen lässt sich eine typsichere Methode entwickeln, um einen
neuen Datensatz an den Server schicken zu können (siehe Listing 2.13).

1 interface ProjectResponse {
2 project: Project ,
3 error: string | null
4 }
5 const createProjectRecord = async (p: PostProject):Promise <ProjectResponse > => {
6 return xmlhttp.postProject(p);
7 }
8
9 const newRecord: PostProject = {
10 name: "esqulino"
11 };
12
13 const response: ProjectResponse = await createProjectRecord (newRecord);

Listing 2.13: Typen und Methode zum Abschicken eines Project-Datensatzes

Die Methode createProjectRecord erwartet also ein Project ohne id als Parameter
und gibt ein ProjectResponse wieder. Der Code im Methodenrumpf ist hierbei nur
Pseudocode. Der Typ ProjectResponse beinhaltet neben dem Project auch ein error
Feld, welches in dem Kontext angibt, ob ein neuer Datensatz auf dem Server erstellt
werden konnte oder nicht. Des Weiteren gibt es noch Exclude<T,U> wodurch sich von
T diejenigen Typen ausschließen lassen, die U zugeordnet werden können. Gäbe es meh-
rere „Response“-Typen, ließe sich das Feld extrahieren, über welches auf die Datensätze
zugegriffen werden kann (siehe Listing 2.14).

1 type DataKey = Exclude <keyof ProjectResponse , "error" >;


2 const key: DataKey = "project";
3 const project: Project = response[key];

Listing 2.14: Exclude zum Exkludieren von Schlüsseln

13
2.4 JSON Schema

JSON-Schema ist ein Vokabular, mit dem JSON-Dokumente annotiert und validiert
werden können [Orga]. Es wird zur Überprüfung genutzt, ob JSON Objekte die im JSON-
Schema beschriebene Struktur einhalten.

Der Vorgänger von JSON-Schema war das XML-Schema. Es erlaubt das Format eines
XML-Dokuments zu definieren, d.h. welche Elemente erlaubt sind, die Anzahl und Rei-
henfolge ihres Auftretens, welchen Datentyp sie haben sollen usw. Seit 2006 gibt es einen
neuen Akteur auf dem Gebiet der Datenformate, Javascript Object Notation (JSON). Die
JSON Daten sind viel kleiner als ihr XML-Gegenstück und ihre Instanzen sind gültige
JavaScript-Objekte, was es interessant für Webentwickler macht, da sie beim Laden von
Informationen in asynchronen Webanwendungen über AJAX (Asynchronous Javascript
and XML) keinen separaten Konvertierungsschritt mehr benötigen [Nog].

Nehmen wir an, wir möchten den in Kapitel GraphQL erstellten Typen Project aus
Listing 2.3 mit verschiedenen Attributen erweitern. Eine JSON Instanz soll mindestens
folgende Attribute beinhalten, wobei die Angabe, ob es sich bei dem Entwickler um einen
proudFather handelt, optional ist (siehe Listing 2.15).

1 {
2 "id":"de0d91a7 -61ae -49af -90d9 -5 a37dd883a01",
3 "name":"Esqulino",
4 "public": false ,
5 "createdAt":1452985200000 ,
6 "developer": {
7 "firstname":"Marcus",
8 "proudFather":true
9 }
10 }

Listing 2.15: Ein Projekt als JSON Objekt

Das passendes Schema dazu sieht folgendermaßen aus (siehe Listing 2.16).

Neben den verwendeten Schlüsselwörtern gibt es noch eine Vielzahl weiterer, die es un-
ter anderem erlauben Einschränkungen, Abhängigkeiten, Muster in Form von Regulären
Ausdrücken oder die maximale oder minimale Anzahl an zu einem Objekt gehörende At-
tribute festzulegen. Die hier verwendeten Schlüsselwörter haben folgende Bedeutung:

• Zeile 2: $schema besagt, dass dieses Schema nach einem bestimmten Entwurf des
Standards geschrieben ist, in erster Linie zur Versionskontrolle.

14
1 {
2 "$schema": "http ://json -schema.org/draft -07/ schema#",
3 "title": "project",
4 "description": "A project from Esqulino",
5 "type": "object",
6 "properties": {
7 "id": {
8 "type": "integer"
9 },
10 "name": {
11 "type": "string"
12 },
13 "public": {
14 "type": "boolean"
15 },
16 "createdAt": {
17 "description": "Date of creation in milliseconds",
18 "type": "number"
19 },
20 "developer": {
21 "description": "The developer of a project",
22 "type": "object"
23 "properties": {
24 "firstname": {
25 "description": "The forename of the developer",
26 "type": "string"
27 },
28 "proudFather". {
29 "description": "Indicator if he is a father or not",
30 "type": "boolean"
31 }
32 },
33 "required": [ "firstname" ]
34 },
35 },
36 "required": [ "id", "name", "developer", "createdAt" ]
37 }

Listing 2.16: JSON Schema zu Projekt Objekt

• Zeile 3: title/description haben nur beschreibenden Charakter.

• Zeile 5: Das Schlüsselwort type für die Typüberprüfung definiert die erste Beschrän-
kung für die JSON-Daten und in diesem Fall muss es sich um ein JSON-Objekt
handeln.

• Zeile 6: properties beschreibt, welche Attribute das Objekt haben darf.

• Zeile 7-35: Definierung der Attribute eines Projektes, wobei in Zeile 20-34 ein
weiteres Objekt als Attribut definiert wird. Dieses besitzt die beiden Attribute
firstname als String und proudFather als Boolean. Die Angabe von firstname
wird bei einem developer Objekt zwingend erwartet, proudFather ist optional.

• Zeile 36: Da das Schlüsselwort required ein Array von Strings beinhaltet, können
bei Bedarf mehrere Attribute angeben werden, die erwartet werden.

15
Nehmen wir an, ein Entwickler hat ein fehlerhaftes Projekt wie in Listing 2.17 „Ein
fehlerhaftes Projekt“ erstellt.

1 {
2 "id":"de0d91a7 -61ae -49af -90d9 -5 a37dd883a01",
3 "public": false ,
4 "developer":{
5 "firstname":"Michael",
6 "professor": true
7 },
8 "createdAt":"1452985200000"
9 }

Listing 2.17: Ein fehlerhaftes Projekt

Es kommt bei der Validierung dieses Objektes zu folgenden Verstößen:

• name: ist im required Array angegeben und muss somit vorhanden sein.

• createdAt: Es wurde ein falscher Datentyp angegeben, string statt number.

• professor: Dieses Attribut ist nicht im properties Objekt angegeben und da-
durch fehl am Platz.

Die händische Erstellung solcher JSON-Schema kann bei einer Vielzahl von Typen schnell
lästig werden. Um dem Problem entgegen zu wirken, lassen sich aus Typescript Interfaces
passende JSON-Schema Dateien generieren. Der Vorteil daran ist, dass sich clientseitig
definierte Datentypen durch die Generierung serverseitig validieren lassen; denn für die
meisten gängigen Programmiersprachen sind JSON-Schema Validatoren entwickelt wor-
den. Somit ist es unabhängig, welche Programmiersprache der Server nutzt [Orgb].

2.5 Postgres jsonb und hstore Typen

Das PostgreSQL Datenbanksystem kennt über den SQL-Standard hinaus die Datentypen
hstore und jsonb zur Speicherung von JSON Strukturen oder assoziativen Arrays, die
üblicherweise in NoSQL-Systemen gespeichert werden. Diese beiden Typen werden im
Kontext der Arbeit für Objekte genutzt, die sich nur mit sehr großem Aufwand und
zukünftigen Migrationen in ein Datenbankschema gießen lassen. Einer dieser Typen ist
ein Objekt, das in Listing 2.18 ein multilingualen String darstellen soll.

Zukünftig sollen weitere Sprachen ermöglicht werden. Würde man dieses Objekt als Ta-
belle definieren, müsste bei Hinzufügen oder Entfernen einer Sprache eine Rails Migra-

16
1 {
2 "DE": "Die Drei",
3 "EN": "The three"
4 }

Listing 2.18: Multilinguales Objekt

tion durchgeführt werden, um das Datenbankschema anzupassen. Damit diese Objekte


flexibel sein können, wird bei der Speicherung ein hstore Typ verwendet.

Hstore differenziert sich von jsonb, indem es nur eine Ebene von Schlüssel-Werte-Paaren
ohne weitere Verschachtelungen zulässt und diese als String abspeichert. Erst durch die
Einführung von jsonb wurde aus Postgres auch eine dokumentenorientierte Datenbank.
Denn im Gegensatz zu hstore können jsonb Datensätze beliebig tief verschachtelt werden
und darüber hinaus werden sie in einem dekomprimierten Binärformat gespeichert, wo-
durch die Eingabe aufgrund des zusätzlichen Konvertierungs-Overheads etwas langsamer,
die Verarbeitung jedoch erheblich schneller ist, da kein Reparsen erforderlich ist [Grob].
Ansonsten haben beide Typen in vielen Dingen die gleichen Verhaltensweisen. Wie bei
der Eingabe doppelter Schlüssel wird nur der letzte Wert beibehalten. Zudem wurden für
beide Datentypen eine beachtliche Menge an Operationen und Funktionen bereitgestellt,
die es möglich machen, auf SQL Ebene einen hstore oder jsonb Datensatz fast wie ein
Hash in Ruby oder ein JSON-Objekt in Javascript zu behandeln [Groa].

17
Anforderungsanalyse 3
Ziel dieser Arbeit ist die Evaluierung und Migration von REST nach GraphQL in die
von Marcus Riemer entwickelte Lehr-Entwicklungsumgebung BlattWerkzeug zur Verbes-
serung des aktuell genutzten Systems. Nachfolgend wird in diesem Kapitel die Funkti-
onsweise des aktuellen Systems erläutert. Anschließend werden Anforderungen, die ein
neues System erfüllen muss, formuliert und evaluiert.

3.1 Aktuelles System

Marcus Riemer hat im Rahmen seiner Master-Thesis an der Fachhochschule Wedel die
Lehr-Entwicklungsumgebung BlattWerkzeug als Webapplikation entwickelt, die sich an
Kinder und Jugendliche richtet. Mit BlattWerkzeug lassen sich, gestützt durch Drag &
Drop-Editoren, für beliebige SQLite-Datenbanken Abfragen formulieren und Oberflächen
entwickeln [Rie16b, S. 2]. Seit dem Abschluss der Master-Thesis wird BlattWerkzeug im
Rahmen eines Promotionsvorhabens weiterentwickelt.

Der Server dieser Web-App ist auf Basis von Ruby on Rails gebaut. Er dient haupt-
sächlich der Speicherung und Auslieferung von Daten. Kommuniziert wird über eine
REST-artige JSON-Schnittstelle [Rie16b, S. 94].

Der Client wurde als eine Single-Page Application mit rein clientseitiger Visualisierung
aufgebaut, die lediglich für den Zugriff auf serverseitige Ressourcen (Datenbank, gespei-
cherte Ressourcen, gerenderte Seiten) Anfragen zum Server schickt. Programmiert wurde
sie 2016 [Rie16b, S. 1] auf Basis von Angular 2 in Typescript. Zum aktuellen Zeitpunkt
wird allerdings auf die Angular Version 10.0.4 gesetzt.

Für die Wahl des einzusetzenden Datenbanksystems wurde sich beim Entwicklungsstart,
auf Grund der Kriterien „Kostenlose Verfügbarkeit“, „Einfacher Betrieb“, „Einfache
Backups“, „Tools zur Modellierung“ und „Externe Tools zur Entwicklung von SQL-
Abfragen“ für eine SQLite Datenbank entschieden [Rie16b, S. 99–100]. Im November

18
2017 ist dann der Grundstein gelegt worden, um den Server mit einer PostgreSQL Da-
tenbank zu verbinden [Rie17b], da diese es unter anderem ermöglicht JSON Objekte
direkt zu speichern, ohne diese in Text Datentypen konvertieren zu müssen.

Anhand eines Praxisbeispiels wird im Weiteren die Funktionsweise des Systems in Hin-
blick auf das Hinzufügen neuer Daten unter Gewährleistung der Typsicherheit (siehe
Unterabschnitt 3.5.3 „Typsicherheit“) verdeutlicht.

3.2 Praxisbeispiel - Erweiterung des


Datenmodels

Damit neue Datensätzen zwischen Server und Client typsicher mithilfe der bislang ge-
nutzten REST API ausgetauscht werden können, sind mehrere Schritte erforderlich. Die
Reihenfolge der nachfolgend aufgeführten Schritte ergibt sich aus dem bisherigen Ent-
wicklungsprozess.

3.2.1 Anlegen des Typescript Interfaces

Als erstes wird ein Typescript Interface für den Datensatz erstellt, der abgebildet werden
soll. Wir erweitern den Datentyp Project aus Listing 2.2 erneut (siehe Listing 3.1):

1 export interface Project {


2 id: string;
3 name: MultiLangString;
4 public ?: boolean ;
5 slug ?: string;
6 userId ?: string;
7 createdAt ?: string;
8 updatedAt ?: string;
9 }
10
11 export interface User {
12 id: string;
13 name: string;
14 }

Listing 3.1: Typescript Interface für die Darstellung eines Projektes

• Zeile 2: id/public siehe Listing 2.2.

• Zeile 3: name hat sich zu einem multilingualen Feld geändert. MultiLangString


repräsentiert eine Map von String auf String ([key: string]: string;).

19
• Zeile 5: slug ist ein aus einem oder wenigen Wörtern bestehender benutzer- und
suchmaschinenfreundlicher Text (sprechender Name) als Bestandteil einer URL
[Wik20]. Diese Angabe ist optional.

• Zeile 6: userId ist die ID des Nutzers, dem dieses Project zugeordnet ist (Fremd-
schlüsselbeziehung).

• Zeile 7: createdAt ist die optionale zeitliche Angabe, wann dieses Project erstellt
wurde.

• Zeile 8: updatedAt ist die optionale zeitliche Angabe, wann dieses Project zuletzt
verändert wurde.

• Zeile 11-14: User ist der mit dem Projekt in Beziehung stehende Nutzer.

Wird ein Datensatz mit einem Projekt beim Server angefragt, lässt sich die Antwort
des Servers auf eine Variable mit dem Typ Project zuweisen. Dadurch wird zur Kom-
pilierungszeit ermöglicht, typsicher auf die einzelnen Felder des Interfaces zugreifen zu
können. Im nächsten Schritt wird das Interface dem Server zur Verfügung gestellt.

3.2.2 Generierung der JSON Schema Definitionen

Das Interface aus Listing 3.1 wurde clientseitig erstellt und kann auch nur dort verwen-
det werden. Um es serverseitig nutzen zu können, wird daraus eine JSON Schema Datei
generiert. Für die Generierung sind Einträge in einem Makefile nötig (siehe Listing 3.2),
welches die Erstellung aller JSON Schema Dateien realisiert. Nach der Generierung be-
finden sich zu jedem aufgeführten Typescript Interface ein passendes JSON Schema in
einer eigenen Datei. Diese werden dann in einem Schema Ordner auf Projekt Ebene
gehalten. Dass jedes Schema in einer eigenen Datei gespeichert ist, kommt dem Server
bei der Validierung zu gute. Dieser lädt die Datei - deren Namen äquivalent zum ur-
sprünglichen Typescript Interface ist - aus dem Ordner, liest das Schema aus und kann
dieses zu Validierungszwecken nutzen. Mithilfe der JSON Schema Dateien können dann
Datensätze validiert werden.

1 Project.json : $(SRC_PATH)/shared/project.ts
2 $(CONVERT_COMMAND )

Listing 3.2: TypeScript Interface für die Project Darstellung in einer Liste

20
3.2.3 Anlegen des Models in Rails

Sollte das Interface aus Listing 3.1 einer neuen Datenbanktabelle entsprechen, muss eine
Active Record Migration erstellt werden, die das Datenbankschema erweitert [Hanb].

1 create_table "projects", id: :uuid , do |t|


2 t.string "slug"
3 t.hstore "name", default: {}, null: false
4 t.uuid "user_id"
5 t.datetime "created_at", null: false
6 t.datetime "updated_at", null: false
7 end

Listing 3.3: Rails Migration zum hinzufügen einer projects Datenbanktabelle

Durch Ausführung der Migration aus Listing 3.3 wird eine neue Tabelle mit der Be-
zeichnung projects erstellt. Außerdem wird ein Active Record Model benötigt (siehe
Listing 3.4), dem die projects-Tabelle zugeordnet wird. Zur Realisierung wird eine Ru-
by Klasse, die von der Klasse ApplicationRecord erbt, mit dem selben Namen, den die
Tabelle hat, erstellt. Die Rails Konvention sieht vor, dass Datenbanktabellen im Plural
und das dazugehörige Model im Singular benannt werden [Hana].

1 class Project < ApplicationRecord


2 # der Nutzer eines Projektes
3 belongs_to :user
4 end

Listing 3.4: Model

Auf diese Weise entsteht die Möglichkeit, die Spalten jeder Zeile in dieser Tabelle mit
den Attributen der Instanzen des Models abzubilden. Jede Zeile dieser Tabelle stellt also
ein „Projekt“ Datensatz mit den in Listing 3.2.1 aufgeführten Feldern dar.

3.2.4 Anlegen eines Controllers in Rails

Um nun auf Anfragen reagieren und Daten aus Model Instanzen an den Client liefern
zu können, bedarf es einem Controller. Controller haben die Aufgabe Anfragen zu verar-
beiten, die vom Router (siehe Listing 3.5) an sie weitergeleitet wurden. Die Funktionen
innerhalb eines Controllers sind dafür verantwortlich die angefragte Funktionalität auszu-
führen und die entsprechende Antwort zu erzeugen. Bei einer Anfrage, die Projekt-Daten
ausgeliefert bekommen soll, übernimmt die Controller Funktion die Aufgabe alle Daten
aus dem Project-Model zu holen und gibt diese dann wie bei REST APIs üblich in
JSON Form zurück.

21
1 scope 'project ' do
2 get '/', controller: 'projects ', action: :index
3 end

Listing 3.5: Route entspricht URL ’/project/’ und leitet Anfrage an die ProjectsController
Funktion index weiter

1 class ProjectsController < ApplicationController


2 def index
3 render json: Project.all.map (&: to_full_api_response )
4 end
5 end

Listing 3.6: Controller mit Funktion zum zurückgeben aller Project Instanzen

In Zeile 3 des Controllers in Listing 3.6 werden alle Projekte aus der Datenbank geladen
inkl. aller Beziehungen und in JSON Form zurück gegeben. Im aktuellen System hingegen
werden die Projekte portioniert an den Client geliefert, damit nicht aus Versehen riesige
Datensätze an den Client übertragen werden. Um gewährleisten zu können, dass die
Antwort vom Server auch die erwarteten Daten liefert, wird ein Test geschrieben (siehe
Listing 3.7), der prüft, ob die Antwort dem clientseitig erstellten Interface aus Listing 3.1
entspricht. Für die Validierung wird ein für Ruby entwickelter JSON Schema Validator
genutzt.

1 it 'lists a single project ' do


2 FactoryBot.create (: project , :public)
3 get "/project/"
4
5 expect(response).to have_http_status (200)
6
7 parsed = JSON.parse(response.body)
8 expect(parsed['data ']. length).to eq 1
9
10 # Validierung gegen das "Project" interface
11 expect(parsed['data '][0]).to validate_against "Project"
12 end

Listing 3.7: Test überprüft, ob bei Anfrage der Route ’/project/’ eine Antwort vom Typ Project
folgt

• Zeile 2: erstellt eine Project Instanz und speichert diese in der Datenbank.

• Zeile 3: schickt ein GET Request an die Route „/project/“.

• Zeile 5: erwartet den HTTP Status 200 .

• Zeile 7: parst den response body in JSON.

• Zeile 8: erwartet, dass die Länge der empfangenen Datensätze 1 ist.

• Zeile 11: validiert die Antwort gegen das JSON Schema Project.

22
Der Server hat nun die Fähigkeit auf eine Anfrage nach allen Projekten zu antworten.
Somit muss der Client noch die Möglichkeit erhalten, eine Anfrage zu erstellen und die
Antwort grafisch abbilden zu können.

3.2.5 Dataservices auf dem Client

In der Welt von Angular gibt es eine strikte Trennung zwischen Darstellung und Verar-
beitung von Daten [Gooc]. Für die Verarbeitung von Daten - wie das Abrufen - werden
Angular Services genutzt. Diese sind typischerweise Typescript Klassen, deren Verwen-
dungszwecke genau definiert sind. Der in Listing 3.10 aufgeführte Service hat die Aufgabe
Projekt-Daten zu verarbeiten.

1 @Injectable ()
2 export class ProjectsDataService {
3 constructor ( private http: HttpClient) { }
4 // Die Antwort soll dem Typparameter "Project" entsprechen
5 readonly projects = this.http.get <Project >('/project/');
6 }

Listing 3.8: Funktion zum Abruf aller Projekte vom Server

• Zeile 1: @Injectable stellt sicher, dass der Compiler die notwendigen Metadaten
erzeugt, um die Abhängigkeiten der Klasse zu erstellen, wenn die Klasse zur Lauf-
zeit injiziert wird.

• Zeile 2: Deklarierung der Klasse/des Services ProjectsDataService

• Zeile 3: Injektion des HttpClient [Gooe] in den Service

• Zeile 5: Nutzung des HttpClient zur Erstellung und zum Abschicken eines typi-
sierten HTTP-Requests an die Route aus Listing 3.5. Dieser wird auf die readonly
Variable projects geschrieben.

Hinzuzufügen ist, dass dieses Beispiel nur bedingt dem aktuellen System entspricht, da
eigentlich eine einheitliche Service Klasse mit einem Cache verwendet wird, von der der
ProjectsDataService erbt. Diese Komplexität wurde aus Gründen der Übersichtlichkeit
ausgelassen. Die Angabe des Antworttyps Project in Zeile 5 fungiert dabei zur Kom-
pilierungszeit als Type Assertion [Cora] und erleichtert den Zugriff auf die Attribute
der Antwort. Der vom Typescript Compiler erzeugte Javascript Code führt während
der Laufzeit jedoch keine Überprüfung durch. Um Typfehler während der Laufzeit zu
verhindern, muss der Entwickler spezielle Prüfungen durchführen, wie z.B. der Test in
Listing 3.7.

23
Die Darstellung der erhaltenen Daten übernehmen dann Angular Komponenten, in die
Services „injiziert“ werden können. Dadurch können Komponenten die Funktionen eines
injizierten Services nach Belieben nutzen.

3.2.6 Komponenten auf dem Client

Eine Angular Komponente entspricht einem Teilbaum des DOM-Baums, auch View ge-
nannt. Somit wird eine Komponente für einen bestimmten Zweck erstellt, der in unserem
Fall die grafische Auflistung der Projekt-Daten ist (siehe Listing 3.9).

1 @Component ({
2 selector: "project -list",
3 templateUrl: "templates/project -list.html",
4 })
5 export class ProjectListComponent {
6 // Injizierung des ProjectsDataService
7 constructor ( private _projectsData: ProjectsDataService) {}
8
9 readonly projects = this._projectsData.projects;
10 }

Listing 3.9: Funktion zum Abruf aller Projekte vom Server

• Zeile 1: Annotation einer Typescript-Klasse als Komponente.

• Zeile 2: Der Wert von selector kann als HTML-Tag in Templates genutzt werden
(<project-list></project-list>), um diese Komponente instanziieren und das
zugehörige Template innerhalb des Bezeichners rendern zu können.

• Zeile 3: Festlegung des Pfades, wo sich das zu rendernde Template, also der darzu-
stellende HTML Code, befindet.

• Zeile 5: Deklarierung der Klasse/des Komponente ProjectListComponent.

• Zeile 7: Das Injizieren des ProjectsDataService [Gooe] in die Komponente.

• Zeile 9: Speichern der Funktion aus dem ProjectsDataService zum Abrufen der
Projekt-Daten auf eine Instanzvariable.

Eine Komponente stellt HTML Code dar, der in einer Datei gespeichert wird, die als
Template bezeichnet wird. Innerhalb des Templates ist der Zugriff auf die nicht priva-
ten Variablen der Komponenten gegeben. Das zugehörige Template project-list.html
sieht wie in Listing 3.10 aus.

24
1 <project -list -item
2 *ngFor="let project of projects | async"
3 [project ]="project"
4 ></project -list -item >

Listing 3.10: Funktion zum Abruf aller Projekte vom Server

• Zeile 1: Aufruf der Komponente mit dem selector „project-list-item“. Diese über-
nimmt hier die Darstellung eines einzelnen Projektes innerhalb einer Liste und
verdeutlicht damit die Modularität von Angular Komponenten.

• Zeile 2: *ngFor ist die „Repeater“-Direktive [Goob] in Angular. Sie ermöglicht ein
gegebenes HTML Template einmal für jeden Wert in einem Array zu wiederholen,
wobei jedes Mal der Array-Wert als Kontext übergeben wird. Das Array projects
kommt aus der Komponente in Listing 3.9 Zeile 9.

• Zeile 3: Übergibt den Wert aus dem Array an eine mit @Input() annotierte Variable
project in der Komponente mit dem Selektor project-list-item .

Diese Schritte sind in ihrer Gänze nur bei der Einführung neuer Entitäten notwendig.
Bei der Nutzung von Subtypen kann ein Teil der umgesetzten Schritte wiederverwendet
werden, bzw. ist nur einmal erforderlich, wie das Ausführen einer Datenbankmigration.

3.2.7 Anlegen einer neuen Sicht

Schritt Beschreibung Aktuelles


System

3.2.1 Anlegen eines Interfaces

3.2.2 Eintrag in Makefile
Anlegen einer Datenbank Migration X
3.2.3
Anlegen des Models X

Route definieren
Anlegen des Controllers X
3.2.4 √
Controller Funktion schreiben

Tests schreiben
Anlegen eines Angular Services X
3.2.5 √
Funktion zum Abschicken einer Query

Anlegen einer Angular Komponenten
3.2.6 √
Anlegen eines Templates
Tab. 3.1: Funktionsweise des aktuellen Systems in Bezug auf die Erstellung neuer Sichten auf
bereits vorhandene Datensätze

Das Hinzufügen eines neuen Datensatzes erfolgt in 12 Schritten (siehe Tabelle 3.1). Der
Entwicklungsaufwand für diesen einmaligen Prozesses ist noch vertretbar. Problematisch

25
wird allerdings das wiederholte Anlegen einer neuen Sicht. Wird nur eine Teilmenge der
Attribute des erstellten Datensatzes benötigt, müssen die meisten Schritte (8 von 12)
wiederholt werden. Je diverser die Sichten auf dem Client werden, desto öfter muss der
Prozess, in dem Server und Client gleichermaßen involviert sind, erneut durchlaufen
werden.

Um Daten zu liefern, die zu der in einem Interface definierten Teilmenge passen, wurden
serverseitig mit der Funktion .slice nur die geforderten Attribute extrahiert (ansons-
ten Overfetching). Somit entsteht für jede Sicht ein neuer Scope (SQL Abfrage) im
Model. Bisher existierten zwei verschiedene Scopes pro Model: to_list_api_response
ist für die Darstellung von Projekten für jeden Nutzer im Frontpage Bereich gedacht.
to_full_api_response liefert alle im Model enthaltenen und mit dem Model verbun-
denen Daten für den Admin Bereich. Wird eine Sicht benötigt, die zu jedem Projekt
noch den Namen des zugehörigen Nutzers anzeigt, muss mit der Funktion .includes
die Beziehung zur Ergebnismenge hinzugefügt werden, da ansonsten übermäßig viele
SQL Queries ausgeführt werden (Underfetching).

Möchte der Client also eine neue Sicht erstellen, müssen auf dem Server eine Route,
eine Controller Funktion und dazu Tests entwickelt sowie ein Scope für die Antwort
geschrieben werden. Daraus ergibt sich ein beachtlicher Aufwand, den es im Kontext der
Arbeit zu minimieren gilt.

3.3 Vorteile des bisherigen Ansatzes

Die Verwendung des derzeitigen Systems hat viele Vorteile, deren Gewichtung es in
Hinsicht auf die Migration von REST nach GraphQL zu evaluieren gilt. Nachfolgend
werden die wichtigen Vorteile erläutert.

3.3.1 Typescript Typsystem

Ein Vorteil ist die Verwendung des umfangreichen Typescript Typsystems. Dieses ermög-
licht neben Typüberprüfungen zur Kompilierungszeit auch Vererbungen zwischen Inter-
faces, die Abbildung verschiedenster Typvarianten wie Union Types, zur Ermöglichung
verschiedener Typen innerhalb einer Variablen, Intersection Types zum zusammenfügen
von Typen, Generische Typen sowie Utility Types um bestehende Typen zu manipulie-

26
ren. In Abschnitt 2.3 „Ausgewählte Details des Typescript Typsystems“ wurden bereits
mehrere Möglichkeiten dazu vorgestellt.

3.3.2 Typescript zu JSON Schema Generatoren

Zusätzlich können „Typescript zu JSON Schema Generatoren“ Annotationen innerhalb


der Typescript Interfaces verarbeiten [You]. Dadurch können Wertebereiche vorgegeben
bzw. eingeschränkt werden, wie das Setzen eines Minimums bzw. Maximums bei Zahlen,
die Verwendung von regulären Ausdrücken für Zeichenketten, die Angabe wie viele Ele-
mente ein Array minimal bzw. maximal aufnehmen kann, sowie die Angabe, wie viele
Attribute ein Objekt minimal bzw. maximal haben darf und welche erwartet werden.
Zudem ermöglichen die Generatoren die Bereitstellung der clientseitig erstellten Typde-
finitionen in Form von JSON Schema Dateien.

3.3.3 Modularität

Aus Unterabschnitt 3.3.2 „Typescript zu JSON Schema Generatoren“ ergibt sich eine
bessere Modularität. Der Kern des Systems zur typsicheren Kommunikation besteht
aus den drei Punkten: Typescript Interface, Typescript zu JSON Schema Generator
und JSON Schema Validator. Die ersten beiden Punkte sind unabhängig vom Backend.
Zudem sind JSON Schema Validatoren in 19 Sprachen [Orgb] bereitgestellt worden,
wodurch das Backend austauschbar ist. Daher kann der Client auch mit anderen APIs
typsicher kommunizieren, wenn diese Zugriff auf dessen Typdefinitionen erhalten.

3.3.4 Typsicherheit zur Kompilierungszeit

Im Client sorgt das Typescript Typsystem durch für Typsicherheit zur Kompilierungs-
zeit. Typsicherheit auf dem Server stellt sich schwieriger dar. In der untypisierten Welt
von Ruby wird nur mit Objekten ohne expliziter Typangabe gearbeitet. Auf eine Va-
riable, in der eine Zeichenkette steckt, kann z.B. eine Zahl zugewiesen werden. Wenn
anschließend eine Funktion der String Klasse im guten Glauben, dass es sich bei der
Variablen noch um eine Zeichenkette handelt, auf einer Zahl aufgerufen wird, kommt es
zu einem Laufzeitfehler. Ein ausreichendes Sicherheitsgefühl wurde dennoch durch um-
fangreiches Testen der Controller, Models und Helper erlangt. Ungeachtet dessen besteht

27
das Problem, dass zur Laufzeit eingehende Daten ungewollte Beschaffenheiten aufweisen
können.

3.3.5 Typsicherheit zur Laufzeit

Beim Kompilieren von Typescript zu Javascript werden alle Typinformationen entfernt.


Wenn also Daten von einer Schnittstelle abgerufen werden, kann nicht sichergestellt
werden, dass diese korrekt ankommen, woraus ungewolltes Verhalten resultieren kann.
Um ungewolltem Verhalten vorzubeugen, wurden Fehlerbehandlungen hinzugefügt, bei
denen jede Anfrage auf Korrektheit geprüft wird. Voraussetzung ist, dass eingehende
Anfragen gegen den selben Typ validiert werden, den der Client für das Abschicken
nutzt und dieser Typ bei Anfragen zum Erstellen oder Ändern von Daten kompatibel mit
dem Datenbankschema ist. Hinzuzufügen ist, dass - wie in Abschnitt 3.2.2 beschrieben
- aus Interfaces JSON Schema Dateien erzeugt werden, die für die Validierung vom in
BlattWerkzeug genutzten JSON Schemer Validator [dav] gebraucht werden. Somit wird
indirekt gegen die Typescript Interfaces validiert.

Zusätzlich wurden auf dem Server Request Specs genutzt. Diese können zur Kompilie-
rungszeit Laufzeitfehler in der API Kommunikation bestmöglich ausschließen. Sie testen
das Verhalten der Controller durch Abschicken von HTTP-Requests und prüfen, ob die
Antwort die erwartete Beschaffenheit ausweist. Diese Tests wurden in den Deployment
Prozess der Webapplikation eingebaut. Bei fehlgeschlagenen Tests wird die Bereitstel-
lung der Software verhindert, wodurch zur Laufzeit ein typsicheres Verhalten suggeriert
wird (siehe Listing 3.1).

SCHEMA

Typsicher
Anfrage Validierung
Validierung
Tests
Typescript Typsicher REST Ruby
Typsicher RESTApi
Api
CLIENT SERVER
Request Specs
Antwort

Abb. 3.1: Typsichere Kommunikation zwischen Typescript Client und Ruby Server

28
3.3.6 Stabilität

Die Software BlattWerkzeug befindet sich seit mehreren Jahren in der Entwicklung. Seit
2016 wird eine REST-artige JSON-Schnittstelle verwendet (siehe Kapitel 4.1. Client-
Server-Architektur [Rie16b]). Im Laufe der Zeit wurden zahlreiche Sichten und Funk-
tionalitäten unter Nutzung der typsicheren REST Kommunikationsschnittelle 3.1 erfolg-
reich entwickelt. Somit konnte sich das aktuelle System bewähren.

3.4 Nachteile des bisherigen Ansatzes

Auch wenn ein System funktionstüchtig ist, ist es nicht zwangsläufig perfekt. In diesem
Abschnitt werden die relevanten Nachteile der bisherigen Umsetzung ausgearbeitet.

3.4.1 Manuelle SQL Queries

Für alle im Client darzustellenden Models müssen die zwei Funktionen to_full_api-
_response und to_list_api_response entwickelt werden. Die Funktion to_full_api-
_response selektiert alle Attribute die ein Admin sehen darf. Nur ein Teil dieser Attri-
bute wird im Admin Bereich auch wirklich gebraucht, sie sollen trotzdem dem Admin
zugängig gemacht werden. to_list_api_response selektiert nur die Attribute, die ein
normaler Nutzer sehen darf. Dieses Verfahren ist sehr unflexibel und wird schnell auf-
wendig, wenn noch weitere Rollen mit anderen Berechtigungen - in Bezug auf den Zugriff
von Daten - dazu kommen.

3.4.2 Auswahl von Attributen

Bei allen im Kontext dieser Arbeit relevanten Listendarstellungen im Client treten Over-
fetching Probleme auf. Die Listendarstellung von Projekten im Admin Bereich werden 3
von 11 Attributen in der Liste angezeigt. Um dies als Nachteil zu untermauern, wird die
Größe des im aktuellen System übertragenen JSON Objektes mit einem JSON Objekt
verglichen, welches nur die drei anzuzeigenden Attribute der Projektdaten beinhaltet
(siehe Tabelle 3.2). Die Größe eines Objektes ohne überflüssige Attribute ist bei einer
Listengröße von fünf Projekten fast fünfmal kleiner. Für die Ermittlung der Werte wur-
de die verwendete Controller Funktion so umgeschrieben, dass diese nur noch die drei

29
angezeigten Attribute liefert. Die Größe der Antwort konnte dann aus dem Firefox Netz-
werkanalyse Tool abgelesen.

Anzahl Attribute Übertragen Größe


3 1,12 KB 594 B
11 3,41 KB 2,87 KB
Tab. 3.2: Vergleich der Übertragungsgrößen von ausführlicher Antwort (11 Attribute) mit Ant-
wort ohne überflüssige Daten (3 Attribute)

Möchte man zusätzlich noch Beziehungen abbilden kann ebenfalls das in Abschnitt 2.1.4
beschriebene Underfetching Problem auftauchen. Dieses ließe sich im aktuellen System
durch manuelle Erstellung der SQL Queries beheben.

3.4.3 camelCase und snake_case Notationen

Da in Ruby für Benennungen von Variablen, Methoden, Dateien etc. Snake Case und auf
dem Typescript Client Camel Case verwendet werden, müssen Datensätze nach Auslesen
aus der Datenbank vor dem Ausliefern an den Client zu Camel Case konvertiert werden.
Das Gleiche gilt, wenn Anfragen zum Erstellen von Datensätzen an den Server geschickt
werden. Bevor diese in die Datenbank eingefügt werden können, muss die Schreibweise
der einzelnen Attribute zu Snake Case verändert werden.

3.4.4 Hoher Aufwand

Die Haltung der zur JSON Schema Generierung benötigten Informationen zu jedem
Interface - Pfad der Datei, Name des Interfaces, Name der generierten Zieldatei - in
einem Makefile ist unübersichtlich und deren Eintragung kann vergessen werden. Eine
Einschränkung des Entwicklers und damit ein weiterer Nachteil ist der Programmierauf-
wand bei der Erstellung neuer Abfragen bzw. neuer Sichten (siehe Tabelle 3.2.7). So wie
das System aktuell ist, skaliert es noch ausreichend. Je größer allerdings die Anwendung
wird, je mehr generierte Typen verwendet werden und je mehr unterschiedliche Sichten
gebraucht werden, desto schlechter skaliert es.

30
3.5 Anforderungen

In diesem Abschnitt sind Kernanforderungen an ein neues System formuliert. Aus dem
Abschnitt 3.1 „Aktuelles System“ konnten sich bereits mehrere dieser Anforderungen
ableiten lassen.

3.5.1 Darstellungsvielfalt

Als wichtigste Anforderung wird die Darstellungsvielfalt definiert. In einer Webapplikati-


on, in der Nutzer verschiedene Rollen zugewiesen bekommen, wodurch sie Berechtigungen
erhalten, werden je nach Rolle unterschiedliche Funktionalitäten und Sichten auf Daten
gewährt.

Im aktuellen System wird zwischen den Rollen Admin, Owner und User unterschieden -
beschrieben in Kapitel 3.2.6 „Rollen und Autorisierung“ in der Abschlussarbeit von Tom
Hilge [Hil19]. Die relevanten Berechtigungen der einzelnen Rollen sind in Tabelle 3.3
beschrieben.

Berechtigung Beschreibung Admin Owner User


√ √ √
Projekt Liste
√ √ √
Sichten im Frontpage Bereich Projekt Details
√ √ √
Projekt Erstellen

Erweiterte Projekt Liste X X
Sichten im Admin Bereich √
Erweiterte Projekt Details X X
√ √
Geplante Sichten Projekt Liste eines Owners X
Tab. 3.3: Zugriffsberechtigungen auf Projekt Daten der verschiedenen Rollen zur Hervorhebung
der benötigten Darstellungsvielhalt

Viele dieser Sichten nutzen unterschiedliche Subtypen des Projekttypen. Beispielsweise


zeigt die Projektliste auf der Frontpage nur öffentliche Projekte - bei denen das Attribut
public auf true gesetzt ist - und benötigt andere Attribute als die Projektliste im
Adminbereich.

Zu diesen unterschiedlichen Sichten kommen noch weitere Spezifikationen in der Darstel-


lung, die gefordert sind.

31
Mehrsprachigkeit

Die Webapplikation von Marcus wird aktuell in zwei Sprachen angeboten, Deutsch und
Englisch. In Abbildung 2.7 „JSON Antwort auf die Projects Query“ wurde bereits sug-
geriert, dass das Feld name eines Projektes nach Sprachen gefiltert werden kann. Im
aktuellen System werden mehrsprachige Felder als hstore (Hash mit Tiefe 1) in der Da-
tenbank gehalten (siehe Listing 3.11). Der Schlüssel gibt den zweistelligen Ländercode
nach ISO Alpha-2 [Staa] an und der dazugehörige Wert den Namen des Projektes in der
jeweiligen Sprache.

1 {
2 "de"=>"Drei Fragezeichen",
3 "en"=>"Three Investigators"
4 }

Listing 3.11: Speicherung der Projektnamen als hstore

Das Sprachenangebot gilt bei einer Systemmigration weiterhin als gefordert und soll
zukünftig erweiterbar sein.

Sortieren und Paginierung

Die Paginierung ist das Portionieren großer Datensätze zur übersichtlicheren und schnel-
leren Verarbeitung und Darstellung. In BlattWerkzeug werden alle Listenansichten im
Admin Bereich in einer Tabelle paginiert angezeigt. Ein Datensatz, der einer Liste mit
30 Einträgen entspricht, würde bei einer Paginierung mit der Seitengröße fünf auf sechs
Seiten aufgeteilt werden. Ein Menü zum Traversieren der Einträge könnte wie in Abbil-
dung 3.2 aussehen.

Pro Seite: 5 Zurück 1 2 3 4 5 6 Weiter

Abb. 3.2: Menüleiste zum Wechseln der Seite und Einstellen der Seitengröße

Außerdem soll es möglich sein, eine Liste nach verschiedenen Attributen sortieren zu
können, auch wenn diese mehrsprachig sind und in der Datenbank als hstore gespeichert
werden.

32
Filtern

Für Listendarstellungen soll es möglich sein, serverseitig Einträge effizient zu filtern -


also möglichst direkt in SQL.

Ein neues System muss also folgende Anforderungen erfüllen:

• Auswahl von Feldern

• Sprachauswahl

• Sortieren

• Paginierung

• Filtern

3.5.2 Typdefinitionen

Typdefinitionen (als ganzes Typschema genannt) sind die Grundlagen für typsichere
Webapplikationen. Gefordert wird, dass sich sowohl serverseitige als auch clientseitige
Applikationen ein Typschema teilen. Die Alternative, dass jede Applikation ein eigenes
Typschema besitzt, ist keine Option. Diese müssten mit viel Aufwand zu jedem Zeit-
punkt synchron gehalten werden, wodurch bei jeder Änderung eines Schemas alle Wei-
teren angepasst werden müssten. Dies würde keine Verbesserung zum aktuellen System
bedeuten.

Somit muss ein Typschema, wie in Listing 3.5.3 „Typsicherheit“ erwähnt, systemüber-
greifend zur Verfügung gestellt werden.

Übersetzung des Typschema durch Codegenerierung

Codegeneratoren ermöglichen das Übersetzen eines Typschemas in ein anderes, wie z.B.
von Typescript zu JSON Schema. Sie sind für die zentrale Haltung und Nutzung nur
eines Typschemas ausschlaggebend. Gefordert ist bei der Einführung eines Typschemas,
dass entsprechende Generatoren für alle angebundenen Applikationen vorhanden sind.

33
Datenbankschema

Zusätzlich wird gefordert, dass das Typschema mit dem bereits vorhandenen Daten-
bankschema kompatibel ist. Bei Abweichungen, wie der Speicherung eines Datums in
verschiedenen Datumsformaten, werden Übersetzungen nach Auslesen oder vor dem Ein-
fügen in die Datenbank gefordert. Codegeneratoren können hierbei nur in eine Richtung
genutzt werden. Diese bezieht sich auf die Generierung von Typen aus dem Datenbank-
schema zum Beispiel mithilfe des Rubygems schema2type [kaw]. Die Generierung eines
Datenbankschemas aus Typdefinitionen ist nicht zu empfehlen. Zum einen muss für je-
den Typen angegeben werden, ob dieser eine Datenbanktabelle darstellt oder z.B. nur ein
Subtyp ist. Zum anderen sind komplexe Beziehungen zwischen Typen schwer abzubilden.
Also ist mit einer Koexistenz zwischen zentralem Typschema und Datenbankschema zu
rechnen.

Synchronisation der Typdefinitionen

Sollte das zentrale Typschema für eine Applikation übersetzt werden, muss das über-
setzte Schema synchron zum Ursprünglichen bleiben. Dies sollte in den Prozess der Be-
reitstellung der Software eingebunden werden. Ein neues System muss also folgende
Anforderungen erfüllen:

• Zentrales Typschema

• Übersetzung des Typschemas für jede Applikation

• Synchronität aller Schemata

• Kompatibilität mit Datenbankschema

3.5.3 Typsicherheit

Eine weitere grundlegende Anforderung bei der Entwicklung einer Webapplikation ist
die Typsicherheit. Im Kontext der Arbeit werden Typsicherheit auf dem Client, dem
Server und die typsichere Kommunikation zwischen Client und Server miteinander in
Bezug gebracht. Sind die drei Punkte gegeben, wird von einer typsicheren Webapplika-
tion gesprochen.

34
Auf dem Client und Server

Die Typsicherheit ist im aktuellen System clientseitig und serverseitig ausreichend ge-
geben (siehe Unterabschnitt 3.3.4 „Typsicherheit zur Kompilierungszeit“ und Unterab-
schnitt 3.3.5 „Typsicherheit zur Laufzeit“). Es wird mindestens gefordert, dass sich diese
mit Einführung eines neuen Systems nicht verschlechtert.

Kommunikation zwischen Server und Client

Während des Datenaustausches zwischen Client und Server kann es durch fehlerhafte Da-
ten zu Laufzeitfehlern kommen. Dieses Problem lässt sich bei eingehenden Daten durch
Validierungen jeder Anfrage und Antwort auf ihre Korrektheit lösen. Voraussetzung da-
für ist, dass jede eingehende Anfrage gegen den selben Typen validiert wird, den der
Client auch für das Abschicken nutzt und kompatibel mit dem Datenbankschema ist.
Umgekehrt gilt, dass jede Antwort gegen den selben Typen serverseitig validiert wird,
den der Client zur Speicherung der Antwort verwendet. Dafür sind systemübergreifende
Typdefinitionen, wie in Abbildung 3.1 SCHEMA genannt, unabdingbar.

Die folgenden Beispiele zeigen in, welchem Ausmaß die Validierung der Daten gefordert
ist. In Abbildung 3.3 wird durch ein Formular die Erstellung eines Projektes demons-
triert. Die Inhalte des Formulars werden auf ein Objekt vom Typ CreateProjekt (siehe
Listing 3.12) geschrieben und an den Server geschickt. Dort wird der eingehende Da-
tensatz gegen den selben Typ validiert. Treten hierbei Fehler auf, wird der Datensatz
nicht in die Datenbank geschrieben und der HTTP Status 400 zurück gegeben. Wird der
Datensatz erfolgreich validiert, wird der Datensatz in der Datenbank gespeichert und
der HTTP Status 200 an den Client geschickt.

1 interface CreateProject {
2 name: string;
3 slug ?: string;
4 public: boolean ;
5 }

Listing 3.12: Interface zum Erstellen eines Projektes

35
CLIENT SERVER

{
New Project   "name": "Esqulino",
name Esqulino   "slug": "sqln",
  "public": true Validierung gegen INSERT INTO projects [...]
slug sqln } CreateProject

public
DATENBANK

SUBMIT

   newProject: CreateProject = { Status 200


      "name": input.name,
      "slug": input.slug,
      "public": input.public
   };

CLIENT SERVER

New Project {
  "name": "Esqulino",
name Esqulino   "slug": "sqln" Validierung gegen
} CreateProject
slug sqln

DATENBANK

SUBMIT

   newProject: CreateProject = { Status 400


      "name": input.name,
      "slug": input.slug,
      "public": input.public
   };

Abb. 3.3: Validierung von Requests zum Erstellen eines neuen Projekt Datensatzes

In Abbildung 3.4 wird eine Liste von Projekten, deren Attribut public auf true gesetzt
ist, beim Server angefragt. Bevor diese Liste an den Client zurück gegeben wird, wird
gefordert, dass diese gegen das Interface validiert wird, welches der Client zur Darstellung
nutzt (siehe Listing 3.13). Schlägt die Validierung fehl, wird der HTTP Status 400 zurück
gegeben, ansonsten der Datensatz - inkl. HTTP Status 200.

Ein neues System muss als Anforderung mindestens den aktuellen Grad an Typsicherheit
aufweisen durch:

• Systemübergreifendes Validieren gegen die selben Typen

• Validierung von Anfragen

36
• Validierung von Antworten

• Testen der Schnittstellen, um Laufzeitfehler vorzubeugen.

1 interface ListProject {
2 id: string;
3 name: string;
4 slug ?: string;
5 }

Listing 3.13: Interface zum Auflisten von Projekten

CLIENT SERVER
SELECT id, name, slug
{
FROM projects
  "filter": {
WHERE "public" = true
projects: ListProject[];     "public": true
 }
Project list }

id name slug
DATENBANK
1 Esqul.. sqln
[{
  "id": 1,
  "name": "Esqulino", {
  "slug": "sqln", Validierung gegen   "id": 1,
}] ListProject   "name": "Esqulino",
  "slug": "sqln"
}

CLIENT SERVER
SELECT *
{
FROM projects
  "filter": {
WHERE "public" = true
projects: ListProject[];     "public": true
 }
Project list }

id name slug
DATENBANK
1 Esqul.. sqln
{
  "id": 1,
  "name": "Esqulino",
Validierung gegen   "slug": "sqln",
Status 400
ListProject   "public": true,
  "createdAt": [...],
  "updatedAt": [...]
}

Abb. 3.4: Validierung von Responses zum Auflisten von Projekt Datensätzen

3.5.4 Performance und Skalierbarkeit

Performance und Skalierbarkeit sind bei der Wahl eines Systems ausschlaggebende Kri-
terien. Das aktuelle System weist im Bereich der Kommunikation Schwächen auf, die es
zu beheben gilt.

37
Over- und Underfetching

Over- und Underfetching sind für eine REST API typische Probleme. In Unterabschnitt
3.4.2 „Auswahl von Attributen“ wurde bereits gezeigt, weshalb diese ein Nachteil in
der Performance darstellen. Ein neues System sollte diese Probleme beheben können.
Wichtig ist, dass nicht für jede gekürzte Sicht ein Subtyp und eine neue Route inkl.
Controller Funktion erstellt wird. Des weiteren dürfen Beziehungen zwischen Daten nicht
das Abfeuern übermäßig vieler Requests bedeuten.

N+1 Query Problem

Ein weiterer Aspekt der, die Performanz und Skalierbarkeit einschränkt, ist das N+1
Query Problem bei Datenbankabfragen. Dieses unterscheidet sich kaum zu dem bereits
erwähnten N+1 Query Problem bei Abfragen an die Web API. Beide beschreiben einen
Engpass, der bei Hochskalierung von Anfragen zu Einbußen in der Performance führt.

Datenbankabfragen werden auch nach der Migration von REST nach GraphQL serversei-
tig von Rails ausgeführt. Wurde das N+1 Query Problem beim Anfragen der Web API
gelöst, kann es dennoch zum Abschicken von N+1 Datenbankabfragen kommen. Dieses
Problem gilt es als Anforderung zu lösen.

Cache

Die Nutzung eines Caches soll bei HTTP Anfragen möglich sein, um Anfragen einsparen
zu können. Der Cache ermöglicht bei wiederholten Abfragen das Laden der bereits vom
Server übertragenen Antwort aus dem lokalen Speicher eines Clients. Da die schnellsten
Anfragen die sind, die nicht verschickt werden müssen, wird gefordert einen Cache mit
wenig Aufwand integrieren und nutzen können.

• Lieferung nur benötigter Daten

• Abschicken möglichst weniger Anfragen an die Web API

• Abschicken möglichst weniger Anfragen an die Datenbank

• Möglichkeit zur Nutzung eines Caches

38
3.5.5 Validierung von jsonb und hstore

Das Validieren von Entitäten aus der Datenbank, die ein JSON Objekt beinhalten wird
gefordert. Komplexe Datentypen mit tiefen Verschachtelungen oder flexible Datentypen
- die sich oft ändern - lassen sich erschwert in ein Datenbankschema gießen. Sie können
dann als json, jsonb oder hstore in ihrer Gesamtheit gespeichert werden, ohne das für
Schlüssel und Werte Bedingungen (Constraints) auf Datenbankebene definiert werden
können.

Gefordert wird das diese JSON Objekte gegen einen Typen aus dem Typschema validiert
werden, bevor sie in die Datenbank eingefügt werden. Sollten die JSON Objekte ebenfalls
nicht im Typschema des neues Systems abbildbar sein und dadurch implizit validiert
werden können, müssen sie mindestens gegen das bereits existierende JSON Schema
validiert werden.

3.5.6 Benennungskonvention

Die Verwendung verschiedener Benennungskonventionen kann problematische Folgen ha-


ben. In der Welt von Ruby werden Worttrennungen in Dateinamen, Variablennamen
sowie Funktionsnamen mit Unterstrichen getrennt (Snake Case). In Typescript sieht die
Konvention vor Variablen- und Funktionsnamen in Camel Case und Klassennamen in
Pascal Case zu schreiben [Mic].

Diese Gegebenheit erschwert die Kommunikation zwischen Typescript Client und Ruby
Server. Gefordert ist, dass ein neues System mit diesen Unterschieden umgehen kann.

39
Implementierung 4
Dieses Kapitel beschreibt den Implementierungsprozess bei der Migration von GraphQL
in die Webapplikation BlattWerkzeug. Zusätzlich wird anhand eines Praxisbeispiel der
Prozess zum Erweitern des Datenmodells mit einem neuen Datensatz zur Prozedur im
vorherigen Systems verglichen. Aus dem Implementierungsprozess und dem Praxisbei-
spiel lässt sich der erfolgte Entwicklungsaufwand unter Berücksichtigung der in Kapitel
3 „Anforderungsanalyse“ beschriebenen Anforderungen evaluieren.

4.1 GraphQL

Bei der Migration muss vor Beginn des Entwicklungsprozesses entschieden werden, nach
welchem der folgenden Ansätze die Migration vollzogen werden soll.

Schema-first
Bei dem Schema-first Ansatz wird zuerst das Schema in der GraphQL SDL (sche-
ma definition language) [Prib] entwickelt, welches die Quelle der Wahrheit sein
soll. Es wird in die Serverapplikation geladen und in eine interne Repräsentati-
on geparst (siehe Abbildung 4.1). Anschließend werden Resolver geschrieben, um
Queries aufzulösen und den angefragten Feldern Werte liefern zu können. Diese
müssen stets konsistent zu den SDL Definitionen sein, da es sonst zu Fehlverhalten
kommt. Die SDL ist so konzipiert, dass sie sprachunabhängig ist. Jedoch werden im
Entwicklungsprozess unzählige Tools benötigt, um verschiedenste Problematiken
zu bekämpfen, wie:

• Inkonsistenzen zwischen Schemadefinition und Resolver

• Modularisierung von GraphQL-Schemata

• Redundanzen in Schemadefinitionen

40
Schema

Server Client

Abb. 4.1: Entwicklungsprozess beim Schema-first Ansatz

Code-first
Bei Code-first gibt es keine manuell gepflegte Version des Schemas, sondern Code
in der Sprache des Servers, der das Schema implementiert und aus dem sich SDL
generieren lässt, um das Schema dem Client zugänglich zu machen (siehe Abbil-
dung 4.2).

Server / Code Schema Client

Abb. 4.2: Entwicklungsprozess beim Code-first Ansatz

Es gibt eine breite Auswahl an Frameworks in verschiedenen Sprachen [Pric], unter


anderem auch das Framework graphql-ruby [Mosh], welches für Ruby on Rails Web-
applikationen entwickelt wurde. Aus folgenden Gründen ist der Code-first Ansatz
gewählt worden, da graphql-ruby:

• ein neuerer Ansatz (siehe Abbildung 4.3) und auf Ruby on Rails zugeschnitten
ist.

• ausführliche und für die Migration ausreichende Dokumentationen wie graphql-


ruby Guides [Mosi] und graphql rubydoc [Mosf], sowie Tutorials von how-
tographql.com [Stab], dev.to [Levb], web-crunch.com [Leva] und viele mehr
bereitstellt.

• bereits im Einsatz bei GitHub, Shopify und Kickstarter und damit auch in
größeren Applikationen skalieren kann.

• im Gegensatz zu Schema-first den geringeren Einsatz von Tools erfordert [NB].

41
Abb. 4.3: Die Evolution der GraphQL-Server-Entwicklung [Pria]

4.1.1 Integration von graphql-ruby

In graphql-ruby wird das Schema serverseitig in Ruby implementiert. Zur Umsetzung


wurde das Rubygem graphql [Mosg] in der Rails Applikation installiert und ein /graphql
Verzeichnis im app-Ordner auf Ebene der Models und Controller erstellt. Innerhalb des
Ordners wurde die in Listing 4.1 gezeigte Ordnerstruktur gewählt.

1 |-- graphql
2 | |-- mutations
3 | | |-- block_language
4 | | |-- grammar
5 | | |-- news
6 | | |-- projects
7 | |-- resolvers
8 | |-- types
9 | | |-- base
10 | | |-- scalar
11 | |-- validators

Listing 4.1: Ordnerstruktur des /graphql Verzeichnisses

Wie in der SDL in Listing 2.5 wird nun auf oberster Ebene das Schema mit den zugehö-
rigen Einstiegspunkten definiert. Dafür wurde die Datei /graphql/server-schema.rb erstellt
(sieh Listing 4.2).

1 class ServerSchema < GraphQL :: Schema


2 default_max_page_size 100
3
4 # Erwartet
5 query(Types :: QueryType)
6 # Optional
7 mutation(Types :: MutationType)
8
9 # Fügt eingebaute Connections zur Paginierung hinzu
10 use GraphQL :: Pagination :: Connections
11 end

Listing 4.2: graphql-ruby Schema Definition und Festlegung der Einstiegpunkte query und
mutation

• Zeile 1: Definition des Schemas.

• Zeile 2: Setzen einer maximalen Seitengröße für die Paginierung mit Connections.

• Zeile 5: Definition des Einstiegspunktes aller Queries.

42
• Zeile 7: Definition des Einstiegspunktes aller Mutationen.

• Zeile 10: Hinzufügen von Connections (siehe Unterabschnitt 4.7.1).

Die Klasse GraphQL::Schema vererbt die Funktion .execute zum Ausführen von Gra-
phQL Anfragen gegen das definierte Schema. Der Rails Server benötigt also eine Route,
die alle GraphQL Anfragen entgegen nimmt und an eine Controller Funktion verweist,
in der die .exectue Funktion aufgerufen wird. Innerhalb dieser Funktion wird der Name
der Query (operationName) genutzt und nach einer gleichnamigen Query im Schema
Ordner gesucht. Ist eine solche Query vorhanden, wird diese geladen und der .execute
Funktion anstelle der in der Anfrage enthaltenen Query übergeben. Kann eine Query
mit dem angefragten Namen nicht gefunden werden oder wurde kein Name übergeben,
wird eine Fehlermeldung an den Client zurückgegeben. Dieses Verfahren stellt sicher,
dass keine schädlichen GraphQL Queries ausgeführt werden können, sondern nur die
im Projekt genutzten. Zusätzlich werden die Meta Daten (Context), welcher Nutzer die
Anfrage geschickt hat und in welcher Sprache der Nutzer die Webseite lädt, beigefügt
(siehe Abbildung 4.4). Anschließend werden die Anfragen vom GraphQL Modul verar-
beitet. Dieser kommuniziert mit der Datenbank, erzeugt eine Antwort und gibt diese als
Rückgabewert der .execute Funktion an den Controller zurück, der die Antwort an den
Client weiterleitet.

GraphQL Modul

CLIENT SERVER

DATENBANK

Abb. 4.4: Koppelung des GraphQL Modules an die bestehende Applikation

Auf dem Client könnten eigens Angular Services entwickelt werden, um GraphQL Que-
ries an den Server zu schicken. Eine bessere Alternative dazu, die zudem Features, wie
Batching oder Caching bietet, ist die Nutzung eines GraphQL Clients [Foua].

Batching
Batching ist der Prozess, bei dem eine Gruppe von Anfragen zu einer einzigen
zusammengefasst wird.

43
Aufgrund der Bereitstellung dieser Features und der Kompatibilität zu Angular [apob]
wurde sich für den Apollo Client [apoa] entschieden.

Nachfolgend wird anhand eines Praxisbeispiels die Umsetzung des gesamten Systems
verbildlicht.

4.2 Praxisbeispiel - Erweiterung des


Datenmodels

Nachfolgend wird das in Abschnitt 3.2 beschriebene Praxisbeispiel nach erfolgter Migrati-
on von GraphQL wiederholt, um aufzuzeigen, welche Schritte bei Nutzung von GraphQL
durchgeführt werden müssen und wie diese sich zum alten System unterscheiden.

4.2.1 Anlegen des Models in Rails

Als ersten Schritt empfehle ich das Anlegen und Durchführen einer Datenbankmigration
und das Erstellen des neuen Models, genau wie in Unterabschnitt 3.2.3 „Anlegen des
Models in Rails“ erläutert.

4.2.2 Anlegen eines GraphQL Objekttypen

Wurde der GraphQL Typ nicht aus dem Datenbankschema generiert, müssen Daten-
typen, die den Model Instanzen aus der Rails Applikation entsprechen, in Form von
GraphQL-Objekttypen definiert werden. Jedes GraphQL Objekt hat Felder, die Daten
extrahieren und anhand des Namens abgefragt werden können. Die Felder sollten die
Benennung der Model-Attribute übernehmen, um direkten Zugriff auf diese zu erlangen.
Das spart Code, da für jedes Feld, dessen Name nicht einem Bezeichner einer Spalte in
der Datenbank entspricht und diese Spalte auch nicht manuell durch einen SQL Alias
hinzugefügt wurde, ein zusätzlicher Resolver bereitgestellt werden muss.

Nachfolgend wird der Projekt Typ aus Listing 3.1 „Typescript Interface für die Darstel-
lung eines Projektes“ in graphql-ruby ausgedrückt und erweitert.

44
1 # Base Klasse definiert einen benutzerdefinierten Connection Typ BaseConnection
2 class Types :: BaseObject < GraphQL :: Schema :: Object
3 connection_type_class Types :: Base :: BaseConnection
4 end
5
6 class Types :: ProjectType < Types :: Base :: BaseObject
7 field :id , ID , null:false
8 field :name , Types :: Scalar :: LangJson , null: false
9 field :public , Boolean , null:true
10 field :slug , String , null:false
11 field :user , Types :: UserType , null:true
12 field :code_resources , [Types :: CodeResourceType], null:true
13 field :code_resource_count , Integer , null:true
14
15 field :created_at , GraphQL :: Types :: ISO8601DateTime , null:false
16 field :updated_at , GraphQL :: Types :: ISO8601DateTime , null:false
17 [...]
18 end

Listing 4.3: Definition des ProjectTypes zum Abbilden von Project Model Instanzen

In Listing 4.3 sind mehrere Auffälligkeiten, die es zu erklären gilt. Zuerst ist zu erwähnen,
dass in Zeile 2 Listing 4.3 eine Base-Klasse eingeführt wird. Base-Klassen wie BaseObject
in können genutzt werden, um das Typsystem von graphql-ruby zu erweitern [Mosc].
Darüber hinaus bekommt jedes Feld (field) mindestens drei Attribute zugewiesen.

Name des Feldes

Das erste Attribut ist ein Symbol und beschreibt den Namen des Feldes.

In Zeile 11 Listing 4.3 wird der Nutzer eines Projektes (user) modelliert. Dieses Feld er-
hält die Bezeichnung :user und entspricht damit nicht der Benennung :user_id aus dem
Model. Dennoch wird kein Resolver zum Auflösen des Feldes benötigt, da im Project
Model aus Listing 3.4 der Zugriff auf den in Beziehung stehenden Nutzer, mit dem Aufruf
von .user auf einer Project-Instanz, ermöglicht wird.

Zudem wurden in Zeile 15 und 16 Listing 4.3 die Felder :created_at und :updated_at
in Snake Case definiert, was zwar der Benennungskonvention von Ruby entspricht, je-
doch nicht der im Client verwendeten Camel Case Schreibweise und damit inkompatibel
zum Client sein müsste (siehe Anforderung in Unterabschnitt 3.5.6). Die Konvention
sieht allerdings vor, dass Felder und Argumenten-Bezeichner in Snake Case geschrieben
werden, da bei der Auflösung eines Feldes per Default versucht wird, eine Methode mit
dem Namen des Feldes auf dem übergebenen Objekt aufzurufen. Ist solch eine Methode
nicht vorhanden und das Objekt ein Hash, wird nach einem Schlüssel, der den Namen
als String oder Symbol entspricht, gesucht [Mose]. Anschließend werden die Felder im
zugrundeliegenden GraphQL Typ zu Camel Case konvertiert [Mosk].

45
Passend dazu wurde in Zeile 13 ein Feld hinzugefügt, dass keinem Attribut des Project
Models oder einer Beziehung zu einem anderen Model entspricht. Die Auflösung dieses
Feldes wird dennoch implizit durch Zugriff über den gleichnamigen Schlüssel im Ergebnis
der Query (Scope) erreicht. Wie der Wert in das Ergebnis der Query gerät, wird im
Unter-Unterabschnitt 4.2.5 „Anlegen der Resolver Klasse“ erklärt.

Datentyp des Feldes

Das zweite Attribut bei der Deklarierung eines Feldes stellt den Datentyp dar. graphql-
ruby bietet alle von GraphQL bereitgestellten Typen (Scalar Typen) an. Zu denen gehö-
ren ID, String, Int, Float und Boolean. Darüber hinaus hat graphql-ruby noch weitere
Typen wie ISO8601DateTime im Repertoire. Da diese nicht ausreichen, wurden eigens
Scalar Typen hinzugefügt (siehe Abschnitt 4.3). Einer davon ist der in Zeile 8 Listing 4.3
aufgeführte LangJson, der ein multilinguales Feld darstellt. Der Suffix Types::Scalar::
beschreibt den Pfad vom /graphql Verzeichnis aus, wo der Typ zu finden ist - in diesem
Fall /graphql/types/scalar/lang_json.rb.

In Zeile 11 und 12 Listing 4.3 wurden Typen verwendet, die zu einer Model-Instanz aus
der Rails Applikation passen. In Zeile 11 wird eine User-Instanz erwartet, in Zeile 12 ein
Array gefüllt mit CodeResource-Instanzen.

Nullable

Als drittes Attribut wurde festgelegt, ob das Feld null als Rückgabewert haben darf.
Diese Angabe kann aus dem Datenbankschema abgelesen werden.

Insgesamt gibt es noch weitere Attribute, mit denen Felder eingeschränkt oder Meta
Daten hinzugefügt werden können [Mosd]. Weitere Felder des ProjectType werden aus
Gründen der Relevanz nicht aufgeführt.

4.2.3 Anlegen eines Input Typen

Input-Objekttypen sind komplexe Eingaben für GraphQL-Operationen. Sie eignen sich


hervorragend für Felder, die viele strukturierte Eingaben benötigen, wie Mutationen
oder Suchfelder [Mosj]. Bei der Migration von GraphQL wurden Input Typen für die
Auswahl von Sprachen (:languages), für das Filtern (:filter) und für das Sortieren

46
(:order) genutzt. Für jeden Typen, der einer Model-Instanz entspricht und deren Instan-
zen im Client in Listen-Form dargestellt werden, wird definiert, welche der Attribute des
Models sortierbar sind und nach welchen Attributen gefiltert werden kann. Diese Infor-
mationen fließen bei der Definition des zur Klasse gehörenden Input-Typen ein (siehe
Listing 4.4).

1 class Types :: ProjectType < Types :: Base :: BaseObject


2 [...]
3 # Enum mit Feldern nach denen sortiert werden soll
4 class OrderFieldEnum < Types :: Base :: BaseEnum
5 value 'name '
6 value 'slug '
7 end
8
9 # Typ zum Sortieren nach den Feldern aus OrderFieldEnum mit der
10 # Sortierrichtung aus dem OrderDirectionEnum aus der BaseEnum Klasse
11 class OrderType < Types :: Base :: BaseInputObject
12 argument :orderField , OrderFieldEnum , required: false
13 argument :orderDirection , Types :: Base :: BaseEnum :: GraphQL:OrderDirectionEnum ,
required: false
14 end
15
16 # Enum mit Feldern die multilingual sind
17 class MultilingualColumnsEnum < Types :: Base :: BaseEnum
18 value "name"
19 end
20
21 # Typ zum Filtern nach den als Argumenten aufgeführten Feldern
22 class FilterFieldType < Types :: Base :: BaseInputObject
23 argument :id , type: ID , required: false
24 argument :name , type: String , required: false
25 argument :slug , type: String , required: false
26 argument :public , type: Boolean , required: false
27 end
28
29 # Input Typ zum Sortiere , Filter und Auswählen von Sprachen
30 class InputType < Types :: Base :: BaseInputObject
31 argument :order , OrderType , required: false
32 argument :filter , FilterFieldType , required: false
33 argument :languages , [Types :: Base :: BaseEnum :: LanguageEnum], required: false
34 end
35 end

Listing 4.4: Definition der Input Typen in /graphql/types/project_type.rb

Nach Hinzufügen des Input Typen ist die Klasse ProjectType fürs erste vollständig.
Also wurden bislang ein Schema definiert, eine Rails Migration durchgeführt und ein
Objekttyp inkl. Input-Typ erstellt, der zur migrierten Datenbanktabelle passt. Nun folgt
die Logik für die Verarbeitung von Queries.

4.2.4 Anlegen eines Query Endpunktes

Damit der Datentyp über den Einstiegspunkt query aus dem Schema abgefragt werden
kann, wurde die Klasse QueryType in Listing 4.5 definiert. Diese beinhaltet alle defi-
nierten Queries, die die Einstiegspunkte zu den Model-Instanzen widerspiegeln. Für jede

47
Model-Instanz die, abgefragt werden soll, wurde somit ein Feld in der Klasse QueryType
definiert.

1 class QueryType < GraphQL :: Schema :: Object


2 field :projects , Types :: ProjectType.connection_type , null: false do
3 argument :input , Types :: ProjectType :: InputType , required: false
4 end
5
6 def projects(input: nil)
7 if input
8 Resolvers :: ProjectsResolver ::new(context: @context , ** input).scope
9 else
10 Resolvers :: ProjectsResolver ::new(context: @context).scope
11 end
12 end
13 end

Listing 4.5: Definition des QueryType als Einstiegspunkt in alle Queries. /graphql/types/
query_type.rb

In Zeile 2 Listing 4.5 wurde ein Feld mit dem Namen :projects vom Typ ProjectType-
.connection_type erstellt, welches ein Argument (argument) mit dem Bezeichner :input
vom Typ ProjectType::InputType mit der Einschränkung, dass das Argument nicht
zwingend erwartet wird (required: false), besitzt. Zusätzlich wurde in Zeile 6 Lis-
ting 4.5 ein Resolver für die projects query hinzugefügt. Dieser bekommt das Argument
als Parameter übergeben.

4.2.5 Anlegen der Resolver Klasse

Der Resolver projects in Listing 4.5 ist für das Auflösen einer Query zuständig. Im
Falle, dass die Query eine Liste mit Filter- und Sortierfunktion erwartet, delegiert ein
Resolver diese Aufgabe an eine spezifisch zum angefragten Model passende Resolver
Klasse weiter und gibt deren Instanzvariable .scope zurück. Das Argument input wird
ebenfalls weiter an die Resolver Klasse übergeben, wenn dieses nicht nil ist.

Die Klasse in Listing 4.6 und deren Oberklasse beinhalten umfangreiche Logik um effizi-
ente SQL Queries zusammenzubasteln, die exakt nur die angefragten Felder mit Daten
bedient. Hier werden die geforderten Lösungen des Overfetching, Underfetching und des
N+1 Query Problems umgesetzt. Zudem wird das Sortieren, Filtern und Auswählen von
Sprachen ermöglicht.

Im Konstruktor in Listing 4.6 wird zunächst der Scope auf die von ApplicationRecord
erbende (Model-)Klasse Project gesetzt, die der gleichnamigen Datenbanktabelle ent-
spricht. Anschließend wird geprüft, ob das Feld code_resource_count in der Query

48
1 class ProjectsResolver < Resolvers :: BaseResolver
2
3 attr_reader (: scope)
4
5 def initialize(context:nil ,filter:nil ,order:nil ,languages:nil)
6 scope = Project
7 # requested_columns gibt in der Query angefragte Felder als String Array zurück
8 if requested_columns (context).include ?("code_resource_count ")
9 # Löst das n+1 query Problem durch einen left join
10 scope = scope.left_joins (: code_resources).select('COUNT(code_resources) AS
code_resource_count ').group('projects.id')
11 end
12 super(Project ,context:context ,scope:scope ,filter:filter ,order:order ,languages:
languages ,order_dir: "asc",order_field:"name")
13 end
14 end

Listing 4.6: Klasse zum Auflösung der Query :projects. /graphql/resolvers/projects_resolver.rb

abgefragt wird. Wenn das der Fall ist, wird ein Left Outer Join mit der CodeResour-
ce Tabelle durchgeführt, um die Anzahl der zu einem Projekt in Beziehung stehenden
CodeResource-Instanzen zählen zu können und als code_resource_count zu selektieren,
wodurch das angefragte Feld :code_resource_count aus dem Objekttypen implizit auf-
gelöst werden kann. Durch dieses Verfahren können zum einen beliebige Felder einem
Graphql-Objekttypen hinzugefügt werden, ohne dass es diese als Spalte in der Daten-
bank gibt, und zum anderen das N+1 Query Problem durch SQL Joins gelöst werden.
Hieraus wird deutlich, dass obwohl GraphQL die SQL Queries automatisch zusammen
stellt bei Bedarf in den Prozess des Zusammensetzens eingegriffen werden kann.

Zuletzt wird in Zeile 12 in Listing 4.6 der Konstruktor der Oberklasse BaseResolver
(in Abschnitt 4.6 beschrieben) aufgerufen und Argumente zum Sortieren, Filtern und
Auswählen von Sprachen übergeben.

Serverseitig wurde nun die Logik erstellt, um Project Datensätze abzufragen und eine
Antwort zu erstellen. Nun benötigt der Client eine Query, die genau die Daten abfragt,
die er benötigt und den dazugehörigen Antworttyp als Typescript Interface, um die
Antwort typsicher verarbeiten zu können.

4.2.6 Erstellen einer GraphQL Query

Für die gewollte Listendarstellung im Client muss eine zum Endpunkt aus Listing 4.5
passende GraphQL Query geschrieben werden. Wir benötigen also eine Query, die den
Bezeichner projects beinhaltet. Zusätzlich muss die Query einen einzigartigen Namen
erhalten, damit Generatoren diesen den daraus generierten Typescript Interfaces zuwei-

49
sen können und ein Entwickler die Interfaces intuitiv wiederfindet. Zusätzlich werden
Argumente zum Filtern, Sortieren und für die Sprachauswahl hinzugefügt sowie für die
Paginierung. Für letzteres werden Felder, die den Status der Paginierung anzeigen ab-
gefragt. Neben den Argumenten und Metadaten sind noch die Felder abzufragen, die
dargestellt werden sollen. In Listing 4.7 wird die Query eingeführt, die für die erweiterte
Projekt Liste aus Tabelle 3.3 genutzt wird.

1 query AdminListProjects (
2 $first: Int
3 $after: String
4 $before: String
5 $last: Int
6 $input: ProjectInputType
7 ) {
8 projects(
9 first: $first
10 after: $after
11 before: $before
12 last: $last
13 input: $input
14 ) {
15 nodes {
16 id
17 name
18 slug
19 codeResourceCount
20 }
21 totalCount
22 pageInfo {
23 hasPreviousPage
24 hasNextPage
25 startCursor
26 endCursor
27 }
28 }
29 }

Listing 4.7: GraphQL Query für eine paginierte Listendarstellung mit Möglichkeit des Filterns,
der Sortierung und der Sprachauswahl

• Zeile 1: Setzen von AdminListProjects als Namen der Query.

• Zeile 2-5: Definition von Argumenten, die für die Paginierung genutzt werden (siehe
Abschnitt 4.7). first und last sind der erste und letzte Index beginnend mit 1 in-
nerhalb der gelieferten Ergebnismenge und mit Base64 kodiert. hasPreviousPage
und hasNextPage geben an, ob eine vorherige bzw. eine nächste Seite existiert.

• Zeile 8: Definition, welcher Einstiegspunkt gewählt werden soll.

• Zeile 9-13: Zuweisen der in der Query definierten Argumente auf die im Schema
definierten Argumente.

• Zeile 15: nodes ist ein Feld aus der Connection und gibt Zugriff auf die Felder des
ProjectTypes.

50
• Zeile 16-19: Setzen der für die Listendarstellung verwendeten Felder.

• Zeile 21: totalCount ist ein Feld aus der benutzerdefinierten Connection und gibt
Auskunft darüber, wie viele Datensätze die Query im Ergebnis beinhaltet (siehe
Abschnitt 4.7.1).

• Zeile 22-26: pageInfo ist ein Feld aus der Connection und beinhaltet Felder, die
Auskunft über den Zustand der Paginierung geben.

Nun muss die Query dem Client durch Generierung eines Angular Services, der die Query
und eine Funktion zum Abschicken beinhaltet, zugreifbar gemacht werden.

4.2.7 Codegenerierung

graphql-ruby stellt einen eingebauten Rake Task bereit, der aus dem GraphQL Schema
eine JSON Darstellung generiert [Mosa]. Diese wird in einem Schema-Ordner auf Wur-
zelebene der gesamten Applikation (Ebene, auf der sich der Server und Client Ordner
befindet) gehalten. Durch Nutzung des Tools graphql-code-generator [dot] wird dann aus
der in Listing 4.7 vorgestellten Query und dem GraphQL Schema in JSON Darstellung
folgender Typescript Code (siehe 4.8, 4.9 und 4.10) generiert:

1 @Injectable ({
2 providedIn: "root",
3 })
4 export class AdminListProjectsGQL extends Apollo.Query <
5 AdminListProjectsQuery ,
6 AdminListProjectsQueryVariables
7 > {
8 document = AdminListProjectsDocument ;
9 }

Listing 4.8: Generierter Service enthält den Aufruf der Query als Instanzvariable

Die Klasse AdminListProjectsGQL in Listing 4.8 ist ein Angular Service und enthält in
der Variablen document den Aufruf einer Funktion, mit der die Query aus Listing 4.7
abgeschickt werden kann. Der passende Antworttyp zu der Query wurde ebenfalls gene-
riert und ist in Listing 4.9 gezeigt. Zusätzlich wurde aus dem GraphQL Schema ein Typ
für die Argumente generiert (siehe Listing 4.10), die in Listing 4.5 der Query projects
hinzugefügt wurden.

Die Ausführung des Rake Tasks und des anschließenden Codegenerators werden über ein
Makefile realisiert, wodurch es nur der Aufruf des im Makefile zugewiesenen Kommandos
benötigt, um alle Typen für den Client generieren zu können.

51
1 export type AdminListProjectsQuery = { __typename ?: "Query" } & {
2 projects: { __typename ?: "ProjectConnection " } & Pick <
3 ProjectConnection ,
4 "totalCount"
5 > & {
6 nodes ?: Maybe <
7 Array <
8 Maybe <
9 { __typename ?: "Project" } & Pick <
10 Project ,
11 "id" | "name" | "slug" | "codeResourceCount "
12 >
13 >
14 >
15 >;
16 pageInfo: { __typename ?: "PageInfo" } & Pick <
17 PageInfo ,
18 "hasPreviousPage" | "hasNextPage" | "startCursor" | "endCursor"
19 >;
20 };
21 };

Listing 4.9: Generierter Antworttyp

1 export type AdminListProjectsQueryVariables = {


2 first ?: Maybe <Scalars["Int"]>;
3 after ?: Maybe <Scalars["String"]>;
4 before ?: Maybe <Scalars["String"]>;
5 last ?: Maybe <Scalars["Int"]>;
6 input ?: Maybe <ProjectInputType >;
7 };

Listing 4.10: Generierter Parametertyp

4.2.8 Anlegen einer Angular Komponenten

Zur Darstellung wird eine Komponente verwendet. Diese bekommt den generierten Ser-
vice aus Listing 4.8 im Konstruktor „injiziert“.

Die in Listing 4.11 gezeigte Komponente enthält aus Gründen der Übersichtlichkeit ledig-
lich die Logik um eine Query abzuschicken. Im Gegensatz dazu verwendet die im System
verbaute Komponente zusätzlich eine weitere Komponente PaginatorTableGraphql-
Component (siehe Abschnitt 4.7.2 „Einheitliche Tabelle“) zur paginierten Darstellung
des Ergebnisses aus der Query in einer Angular Material Tabelle [Goog]. Der Aufruf
.watch in Listing 4.11 Zeile 13 gibt ein QueryRef Objekt zurück, welches das Attribut
valueChanges hat, das ein Observable [Good] ist. Durch den Aufruf von .subscribe()
[Goof] oder der Nutzung einer Async-Pipe [Gooa] im Angular template wird die Que-
ry abgefeuert. Die in Listing 4.11 Zeile 14 und 15 verwendeten Parameter der .watch
Methode geben die Variablen der Query (Zeile 14) und die von Apollo bereitgestellten
Optionen (Zeile 15) an. notifyOnNetworkStatusChange: true ermöglicht die Nutzung
einer Ladeanzeige (loading indicator). Des Weiteren gibt der Parameter fetchPolicy

52
an, wie die Daten geladen werden sollen. Hier besteht die Möglichkeit anzugeben, dass
ein Cache automatisch genutzt werden soll. Jedoch wurde nach Erstellung eines neuen
Datensatzes und Weiterleitung auf die Listendarstellung der gerade erstellte Datensatz
nicht gezeigt, da die Liste aus dem Cache geladen wurde. Als Lösung wurde an dieser
Stelle die Option „network-only“ genutzt. Diese verbietet die Verwendung eines Caches,
so dass immer eine Query geschickt wird, wenn die Liste benötigt wird.

1 import {
2 AdminListProjectsGQL ,
3 } from "../../../ generated/graphql";
4
5 @Component ({
6 templateUrl: "./ templates/overview -project.html",
7 })
8 export class OverviewProjectComponent {
9
10 constructor ( readonly projectsService: AdminListProjectsGQL ) {}
11
12 pageSize: number = 25;
13 readonly query = this.projectsService.watch(
14 { first: this.pageSize },
15 { notifyOnNetworkStatusChange : true , fetchPolicy: "network -only" }
16 ).valueChanges;
17 }

Listing 4.11: Angular Komponente zum Anzeigen der Projekte in Listendarstellung

4.2.9 Anlegen einer neuen Sicht

Um das nach GraphQL migrierte System mit dem alten zu vergleichen, wird das Anlegen
einer neuen Variante der Sicht auf Projekt-Daten, wie in Abschnitt 3.2 „Praxisbeispiel -
Erweiterung des Datenmodels“ simuliert. In Tabelle 4.1 sind alle Schritte aufgeführt, die
für das Hinzufügen eines neuen Datensatzes durchlaufen werden müssen. Zusätzlich sind
die Schritte, die beim Erstellen einer neuen Sicht ausgeführt werden, mit einem Haken
markiert worden.

Es wird deutlich, dass nur eine dezimierte Anzahl von Schritten (4 von 11) im Gegensatz
zum alten System (8 von 12) benötigt werden. Es mussten keine Routen, Controller
Funktionen oder weitere Tests geschrieben werden, da es nur einen GraphQL Endpunkt
(Route und Controller Funktion) auf dem Server gibt, der alle Anfragen entgegen nimmt
und im GraphQL Modul verarbeitet. Dort werden die Anfragen auf Korrektheit geprüft,
SQL Anfragen zusammengestellt, ausgeführt und die Antwort an die Controller Funktion
zurückgegeben. Somit sind alle Schritte, die in Tabelle 3.1 „Funktionsweise des aktuellen
Systems in Bezug auf die Erstellung neuer Sichten auf bereits vorhandene Datensätze“
serverseitig durchgeführt wurden, obsolet und der Server bedarf keiner Änderungen.

53
Schritt Beschreibung GraphQL
Anlegen einer Datenbank Migration X
4.2.1
Anlegen des Models X
4.2.2 Anlegen eines GraphQL Objekttypen X
4.2.3 Anlegen eines Input Typen X
Anlegen eines Query Endpunktes X
4.2.4
Anlegen einer Resolver Funktion X
4.2.5 Anlegen einer Resolver Klasse X

4.2.6 Erstellen einer GraphQL Query

4.2.7 Codegenerierung

Anlegen einer Angular Komponenten
4.2.8 √
Anlegen eines Templates
Tab. 4.1: Funktionsweise von GraphQL bei Erstellung neuer Sichten auf bereits vorhandene
Datensätze

Letztendlich muss lediglich eine Angular Komponente mit zugehörigem Template ge-
schrieben werden, in die der generierte Service injiziert wird.

4.3 Scalar Typen

Ein GraphQL-Objekttyp hat einen Namen und Felder, aber irgendwann müssen diese
Felder in konkrete Daten aufgelöst werden. An dieser Stelle kommen die skalaren Ty-
pen ins Spiel: Sie stellen die Blätter der Abfrage dar. Skalare Typen sind Instanzen von
sehr einfach strukturierten Ruby Klassen. Diese enthalten lediglich zwei Funktionen zum
Auflösen des Feldes bei Queries self.coerce_result(value, _context) und zum Ver-
arbeiten von Eingaben bei Mutationen self.coerce_input(value, _context). Beide
Funktionen erhaltenden einen Wert und den Context der Query. Die Funktion zum
Auflösen eines Feldes wird ausgeführt, nachdem der zum Feld passende Wert aus der
Datenbank ausgelesen wurde und bevor dieser an die anfragende Client Applikation zu-
rückgegeben wird. Da Scalar Typen erst benötigt werden, wenn ein bisher genutzter Typ
nicht mit der GraphQL SDL formuliert werden kann, ist die Funktion coerce_result
der beste Ort für die Validierung zum Beispiel gegen das JSON Schema, welches den
Typ beschreibt. Das gleiche gilt bei der Ausführung einer Mutation zum Hinzufügen ei-
nes Datensatzes in die Datenbank. Die Funktion coerce_input wird ausgeführt, bevor
der Wert des Feldes an den zum Feld gehörenden Resolver gegeben wird. Somit ist dies
ebenfalls der richtige Ort für die Validierung der Eingabe.

54
Wie diese Scalar Typen genau aussehen können, wird anhand eines Beispiels in Ab-
schnitt 4.8 „Validieren“ vorgeführt.

4.4 Enum Typen

Die in BlattWerkzeug verfügbaren Sprachen (LanguageEnum) und Sortierrichtungen wer-


den in Enums innerhalb der Klasse Types::Base::BaseEnum aus Listing 4.12 gespeichert.
Zusätzlich gibt es eine statische Funktion self.enum_values, die die Werte eines Enums
extrahiert und diese als Array zurück liefert. Diese wird bei der Sortierung und Auswahl
der Sprache im Base Resolver in Abschnitt 4.6 genutzt.

1 class Types :: Base :: BaseEnum < GraphQL :: Schema :: Enum


2 def self.enum_values
3 values.values.map (&: value)
4 end
5
6 class LanguageEnum < Types :: Base :: BaseEnum
7 value 'de'
8 value 'en'
9 end
10 [...]
11 # weitere Enum Definitionen
12 end

Listing 4.12: Funktion zum Extrahieren der Enum Werte in ein Array. /graphql/types/base/
base_enum.rb

4.5 Mutationen als Objekttypen

In diesem Abschnitt wird beschrieben, wie die Funktionalitäten zum Erstellen, Ändern
und Löschen von Datensätzen von den bestehenden Rails Controllern nach GraphQL
Mutationen migriert wurden. Zuerst müssen dem Schema für die Ausführung von Mu-
tationen entsprechende Einstiegspunkte hinzugefügt werden (siehe Listing 4.13). Im Un-
terschied zu den Queries werden zur Auflösung der Anfrage keine Resolver Funktionen
verwendet sondern Ruby Klassen, die von der Klasse GraphQL::Schema::RelayClassic-
Mutation erben.

1 class MutationType < Types :: Base :: BaseObject


2 field :create_project , mutation: Mutations :: Projects :: CreateProject
3 end

Listing 4.13: Definition eines Endpunktes zum Aufruf der Mutation für Erstellung eines
Projektes /graphql/types/mutation_type.rb

55
Der Einstiegspunkt aus Listing 4.13 verweist auf die Klasse Mutations::Projects::-
CreateProject aus Listing 4.14. Innerhalb der Klasse können neben Feldern auch Ar-
gumente definiert werden. Da die Funktionalität der benötigten Mutationen bereits in
den Rails Controller Funktionen umgesetzt ist, wurde dort abgelesen, welche Argumen-
te eine Mutation benötigt. Vorteilhaft ist, dass zu den Argumenten ein Typ angegeben
wird und ob diese zwingend vorhanden sein müssen. Somit können sich ohne manuelle
Prüfungen keine fehlerhaften Daten einschleichen, wenn die Definition der Argumente
korrekt umgesetzt wurde.

Die Argumente einer Mutation werden der Funktion resolve übergeben. Der Methoden-
rumpf der Funktion konnte in diesem Fall (siehe Listing 4.14) ebenfalls von der passenden
create Funktion aus dem ProjectsController übernommen werden. Dies gilt auch für je-
de andere Mutation, da der Rails Server zu jeder im Kontext dieser Arbeit benötigten
Mutation eine äquivalente Controller Funktion besitzt.

1 class Mutations :: Projects :: CreateProject < Mutations :: Projects :: Projects


2
3 argument :name , Types :: Scalar :: LangJson , required:true
4 argument :slug , String , required:true
5
6 def resolve (** args)
7 project = Project.new(
8 name:args [: name],
9 slug:args [: slug],
10 user_id:context [: user ].id)
11 save_project(project)
12 end
13 end

Listing 4.14: Definition der Mutation CreateProject /graphql/mutations/projects/create_project.


rb

Zusätzlich zu den Argumenten lassen sich Felder hinzufügen. Möchte man zum Beispiel
im Client über ein einfaches Formular einen Datensatz erstellen und dann anhand der Id
des Datensatzes zu dessen Detailansicht weitergeleitet werden, kann als Rückgabewert
der Mutation eben diese Information erfragt werden. Jeder Mutation wird ein :errors
Feld beigefügt (siehe Listing 4.15) um auf Fehler in einer Mutation reagieren zu können.

Die Anfrage der Mutation sieht dementsprechend wie in Listing 4.16 aus.

56
1 class BaseMutation < GraphQL :: Schema :: RelayClassicMutation
2 end
3
4 class Mutations :: Projects :: Projects < Mutations :: BaseMutation
5
6 field :id , ID , null: true
7 field :errors , [String], null: false
8
9 def save_project(project)
10 if project.save
11 {
12 id: project.id ,
13 errors: []
14 }
15 else
16 {
17 id: nil ,
18 errors: project.errors.full_messages
19 }
20 end
21 end
22 end

Listing 4.15: Definition der Oberklasse aller projects Mutationen /graphql/mutations/projects/


projects.rb

1 mutation CreateProject($name:LangJson!,$slug:String !){


2 createProject(input :{ name:$name ,slug:$slug }) {
3 errors
4 id
5 }
6 }

Listing 4.16: Anfrage der CreateProject Mutation

4.6 Felder Auswahl, Sprachauswahl, Filtern und


Sortieren

Erhält eine Query Argumente zum Filtern, Sortieren und für die Sprachauswahl, durch-
läuft sie im GraphQL Modul eine Reihe von Resolvern, (siehe Abbildung 4.5) in der die
Argumente sukzessive in eine SQL Abfrage integriert werden. Zuerst wird die Resolver
Funktion zum Auflösen eines Query Einstiegspunktes aufgerufen. Innerhalb dieser wird
eine Instanz einer spezifisch zum abgefragten Model passenden Resolver Klasse (hier
ProjectsResolver) erstellt, Argumente übergeben und der Rückgabewert des Konstruk-
tor zurückgegeben. Der Konstruktor der spezifischen Resolver Klasse erstellt einen Scope
(SQL Abfrage) und inkludiert Beziehungen in die Ergebnismenge dieses Scopes. Anschlie-
ßend wird der Konstruktor der Oberklasse (BaseResolver) aufgerufen, der Context, die
Argumente, der Scope und der Name der Model Klasse übergeben sowie Default Wer-
te für die Sortierung gesetzt (siehe Zeile 12 in Listing 4.6). Der BaseResolver ist eine
einheitliche Komponente die den Scope wie nachfolgend erklärt erweitert. Somit werden

57
die in diesem Kapitel relevanten Funktionalitäten in der Resolver Funktion und in der
spezifischen Resolver Klasse nur konfiguriert und erst im BaseResolver implementiert.

Query Resolver ProjectsResolver BaseResolver

Argumente Felder- und Sprachauswahl


Beziehungen Auflösen

Context Funktion Filtern

Default Werte für Sortierung


Felder Sortieren

Abb. 4.5: Durchlauf einer Query durch alle Resolver im GraphQL Modul

Felder- und Sprachauswahl


Zu Beginn wird geprüft, ob dem BaseResolver ein Array mit Ländercodes (languages)
übergeben wurde. Wenn das nicht der Fall ist, werden alle vorhandene Sprachen als
Array auf eine Instanzvariable @languages gesetzt. In einer Methode select_relevant-
_fields werden dann alle angefragten Felder zuzüglich aller Felder, die eine Fremd-
schlüsselbeziehung darstellen (Felder, die auf _id enden) sowie das Feld Id, in einem
Array zusammengefasst. Für jedes Element des Arrays wird geprüft, ob es ein in der
Datenbank als hstore gespeichertes Feld ist, also einen multilingualen String darstellt.
Trifft dies zu werden mithilfe der Funktion SLICE [Groa] nur die Schlüssel/Wert-Paare
extrahiert, deren Schlüssel im @languages Array enthalten sind, andernfalls wird das
gesamte Feld selektiert.

Filtern
Anschließend wird bei der Verarbeitung jedes Filters bestehend aus filter_key und
filter_value zwischen fünf PostgreSQL Datentypen unterschieden. filter_key stellt
dabei das Feld dar, auf welches der Filter angewendet werden soll. filter_value hinge-
gen ist der Wert, nachdem gefiltert wird.

uuid
Ist das zu filternde Feld eine uuid, muss der Wert des Feldes in einen Text gecastet
werden, bevor es mit dem LIKE Operator verglichen werden kann.
scope.where "#{@model_class.table_name}.#{filter_key}::text LIKE ?", filter_value

hstore
Handelt es sich bei dem Feld um einen multilingualen String werden, alle Werte zu
den in @languages vorhandenen Schlüsseln mit dem Operator hstore -> text[]

58
extrahiert und mit dem Operator ILIKE ANY geprüft, ob filter_value zu einem
der extrahierten Werte passt [Incc]. Die Funktion to_single_quotes_array parst
ein Array in Stringdarstellung mit einfachen Anführungszeichen.
scope.where "'#{filter_value}' ILIKE ANY (#{@model_class.table_name}.#{filter_key} -> ARRAY#{

to_single_quotes_array(@languages)})"

boolean
Entspricht das Feld einem Boolean, muss mit einem =-Zeichen gefiltert werden.
scope.where "#{@model_class.table_name}.#{filter_key} = ?", filter_value

datetime
Bei einem Datetime Feld muss der Filter erweitert werden. In BlattWerkzeug exis-
tiert ein Model News (Neuigkeiten). Instanzen des Models besitzen das Attribut
published_from, welches angibt, wann eine Neuigkeit veröffentlicht werden soll.
Um eine Liste zu erzeugen, in der nur bereits veröffentlichte News-Instanzen prä-
sentiert werden, dürfen nur die selektiert werden, deren Erscheinungsdatum kleiner
(früher) ist als das heutige Datum. Damit die Auswahl flexibel bleibt, wurde der
Wert des Filters in zwei Argumente unterteilt. Das erste (:date) gibt das Datum
an, nachdem gefiltert werden soll, das zweite Argument (:until) gibt an, ob nach
Daten gefiltert werden sollen, die vor oder nach dem zu filternden Datum liegen.
Wird kein Wert für das erste Argument angegeben wird das heutige Datum statt-
dessen genommen.
comparator = filter_value[:until] ? "<=" : ">"

date = filter_value[:date] ? filter_value[:date] : Date.today

scope.where "#{@model_class.table_name}.#{filter_key} #{comparator} ?", date

default
Alle anderen Datentypen werden mit dem LIKE Operator gefiltert.
scope.where "#{@model_class.table_name}.#{filter_key} LIKE ?", filter_value

Sortieren
Als letztes werden Sortierfunktionen in das SQL Statement eingebaut. Wie bei der
Sprachauswahl wird erneut von multilingualen Feldern unterschieden. Da diese einen
Hash darstellen, muss angegeben werden, nach welcher der Schlüssel/Wert-Paare sor-
tiert werden soll. Hier kommt die Funktion COALESCE [Web] ins Spiel. Sie kann eine
beliebige Anzahl von Parametern verarbeiten, die der Reihe nach ausgewertet werden.
Der erste Ausdruck, der einen Wert ungleich NULL zurückliefert, bestimmt das Ergeb-

59
nis der Funktion. Sind mehrere Sprachen ausgewählt worden, muss bestimmt werden,
nach welcher Sprache vorrangig sortiert werden soll. Da nicht jedes multilinguale Feld
Werte für allen Sprachen besitzt, ist COALESCE hierbei die Lösung des Problems (siehe
Listing 4.17).

1 if is_multilingual_column ? order_key
2 # Erstellt einen String wie folgt "name ->'de ',name ->'en ',name ->'it ',name ->'fr '"
3 coalesce = @languages.map {|l| "#{ @model_class.table_name }.#{ order_key }->'#{l}'"}. join
(',')
4 scope = scope.order Arel.sql("COALESCE (#{ coalesce }) #{ order_dir}")
5 end

Listing 4.17: Sortieren von Feldern in der Klasse BaseResolver

4.7 Paginierung

Die geforderte Paginierung wurde durch einen Connection Typ im GraphQL Schema
und durch eine Angular Komponente ermöglicht, die eine einheitliche Tabelle mit Mög-
lichkeit der Paginierung und Sortierung ist. Der serverseitige Code erfordert nur einen
Methodenaufruf (siehe Abschnittgraphql:connections), wohingegen im Client eine um-
fangreiche (165 Zeilen Typescript Code zuzüglich 25 Zeilen HTML Code) Komponente
geschrieben wurde [varb].

4.7.1 Connection Typen

Der Connection Typ ist die Lösung für das Problem der Paginierung [Mosb]. Es sind
generische Objekte, die eine Eins-zu-viele-Beziehung darstellen, Metadaten über die Liste
beinhalten und Zugriffsmöglichkeiten auf die Elemente besitzen. Wird .connection-
_type an einen Query Typ angehängt, wird eine Connection daraus. Das bedeutet, dass
wie bei ProjectType.connection_type in Listing 4.5 implizit Argumente zur Cursor-
basierten Paginierung first, last, after, before hinzugefügt [Incb] werden. Zusätzlich
wird jeder Query das Feld pageInfo beigefügt, über das die Informationen hasNextPage,
hasPreviousPage, startCursor und endCursor erhältlich sind.

Zusätzlich wurden durch eine benutzerdefinierte Connection Klasse die Connection-Typen


um ein Feld erweitert. Dies geschieht in Listing 4.18.

60
1 class Types :: Base :: BaseConnection < GraphQL :: Types :: Relay :: BaseConnection
2
3 field :total_count , Integer , null: false
4
5 def total_count
6 object.items.size
7 end
8 end

Listing 4.18: Benutzerdefinierte Connection Klasse mit hinzugefügtem Feld

4.7.2 Einheitliche Tabelle

Die Tabelle im Client sieht wie in Abbildung 4.6 aus. Unterhalb der Tabelle kann die
Seitengröße eingestellt oder eine Seite weiter bzw. zurück gegangen werden. Die Anzeige
1-5 steht dabei für die ersten fünf Elemente, die gezeigt werden. Drückt man auf den Pfeil
nach rechts werden die Argumente der GraphQL Query erneut gesetzt first: pageSize,
after: endCursor und dadurch ein neuer Request abgeschickt. Der Wert von endCursor
geht aus der letzten Antwort der „AdminListProjects“ Query aus Listing 4.7 hervor. Der
Wert pageSize wird innerhalb der Angular Komponente gehalten.

Des Weiteren wird ein neuer Request durch Setzen des Input-Typen der Query gesendet,
wenn auf die Spalten Bezeichner „name“ oder „slug“ gedrückt wird. Es sind nur die
Spalten sortierbar die in Listing 4.4 im OrderFieldEnum enthalten sind.

Abb. 4.6: Einheitliche Material Angular Tabelle zum Paginieren und Sortieren

61
4.8 Validieren

Wie bereits in 3.5.5 „Validierung von jsonb und hstore“ erwähnt, müssen Felder, die nicht
implizit vom GraphQL Typschema validiert werden, gegen die bestehenden JSON Sche-
ma Definitionen validiert werden. Im Folgenden wird gezeigt, wie multilinguale Strings
und Typen gegen JSON Schema Definitionen validiert werden.

4.8.1 Multilinguale Strings

Multilinguale Strings werden im GraphQL Schema als ein eigens definierter Scalar Typ
behandelt. Dieser heißt LangJson und besitzt wie für Scalar Typen üblich die beiden
Funktionen self.coerce_input und self.coerce_result. In beiden dieser Methoden
wird der eingehende bzw. ausgehende Datensatz mithilfe des Validators Languages aus
Listing 4.19 geprüft. Bei der Prüfung wird die Schnittmenge zwischen den Schlüsseln
des zu validierenden Objekts als Array und den im Enum aufgelisteten verfügbaren Län-
dercodes als Array gebildet. Wenn die Schnittmenge leer ist beinhaltet das übergebene
Objekt keine valide Sprache und ist damit ungültig. Sollte die Schnittmenge nicht leer
sein, wird geprüft, ob die Differenzmenge von den Schlüsseln des Objektes ohne die ver-
fügbaren Ländercodes nicht leer ist. Ist das der Fall, wurden Schlüssel angegeben, die
nicht kompatibel zu den vorhandenen Ländercodes sind (siehe Abbildung 4.7).

DB JSON Keys LanguageEnum {


  "de": "Die Drei",
  "en": "The Three"
[ "de", "en" ] [ "de", "en" ]
}

[]

JSON Keys LanguageEnum {


  "de": "Die Drei",
  "it": "i tre"
[ "de", "it" ] [ "de", "en" ]
}

[ "it" ]

Abb. 4.7: Beispiel für die Validierung von Multilingualen Strings

62
1 class Languages
2 def self.validate !( args)
3 if (Types :: Base :: BaseEnum :: LanguageEnum.values.keys & args.keys).empty?
4 raise GraphQL :: ExecutionError , "Language Keys are missing for #{ args}"
5 elsif not (args.keys - Types :: Base :: BaseEnum :: LanguageEnum.values.keys).empty?
6 raise GraphQL :: ExecutionError , "Unknown Language Keys provided in #{ args}"
7 end
8 end
9 end

Listing 4.19: Validator Klasse zum Prüfen, ob der übergebene Parameter die Beschaffenheit
eines multilingualen Strings aufweist. /graphql/validators/languages.rb

4.8.2 JSON Schema

Die Validation von im Schema nicht abbildbaren Typen gegen vorhandene JSON Sche-
ma funktioniert genau wie bei multilingualen Strings. Der einzige Unterschied ist der
verwendete Validator (siehe Listing 4.20). Beim Aufruf der validate! Funktion werden
der Name des JSON Schemas und das zu validierende Objekt als Parameter übergeben.
Beim Laden der JSON Schema Definitionen und beim Validieren wurde sich das bereits
bestehende System zu Nutze gemacht, indem der JsonSchemaHelper eingebunden wurde
und dessen Funktion json_schema_validate verwendet.

1 class GraphqlValidator
2 extend JsonSchemaHelper
3 def self.validate !( schema_name:, document :)
4 result = json_schema_validate (schema_name , document)
5 if result.length > 0
6 raise GraphQL :: ExecutionError.new("Given document of type #{ schema_name} does not
match the schema [...]", extensions: { code: 'VALIDATION ' })
7 end
8 end
9 end

Listing 4.20: Validator Klasse zum Prüfen, ob der übergebene Parameter die Beschaffenheit
des JSON Schema aufweist. /graphql/validators/graphql_validator.rb

4.9 Unerwartete Hindernisse

Im Laufe der Migration traten einige unerwartete Hindernisse bei der Nutzung von Code-
generatoren auf. Manche der Hindernisse ließen sich durch Nachfragen beim Entwickler
und Erstellen eines Github Issues auflösen.

63
4.9.1 Fehlerhafte Codegenerierung der Angular Services

Beim Testen von Angular Komponenten, die generierte Angular Services injiziert beka-
men, war der Zugriff auf this.apollo undefiniert [Sch]. Dies resultierte daraus, dass in
der Testumgebung die Apollo Abhängigkeiten beim Injizieren eines Services nicht richtig
eingebunden wurden. Die Klasse Query aus dem Apollo Framework bekam im Konstruk-
tor nicht den Zugriff auf die Apollo Instanz übergeben. Somit schlugen alle Tests fehl,
die einen aus GraphQL generierten Angular Service nutzten.

Die Lösung dieses Problems war das manuelle Übergeben der Apollo Instanz an die
Query Klasse durch Injizieren von Apollo in die generierten Services und Übergeben der
Instanz im Aufruf des Konstruktors der Oberklasse (siehe Listing 4.21).

1 export class AdminListProjectsGQL extends Apollo.Query <


2 AdminListProjectsQuery ,
3 AdminListProjectsQueryVariables
4 > {
5 document = AdminListProjectsDocument ;
6 constructor (apollo: Apollo.Apollo) {
7 super(apollo);
8 }
9 }

Listing 4.21: Injizieren von Apollo in generierten Angular Service und Übergeben der Apollo
Instanz an den Konstruktors der Oberklasse

4.9.2 Unmöglichkeit der Modellierung mancher Typen

In Unterabschnitt 4.8.2 und Abschnitt 4.3 wurde bereits aufgegriffen, dass manche Typen
wie LangJson unmöglich im GraphQL Schema modelliert werden können und stattdessen
Scalar Typen verwendet wurden. Die Verwendung der skalaren Typen für nicht abbildba-
re Typen hat allerdings den Nachteil, dass die Codegeneratoren an dieser Stelle lediglich
den Typ any zuweisen können, da sie keine Kenntnis über die zugrunde liegenden JSON
Schema Definitionen besitzen (siehe Listing 4.22).

1 export type Scalars = {


2 ID: string;
3 String: string;
4 Boolean: boolean;
5 Int: number;
6 Float: number;
7 LangJson: any;
8 [...]
9 };

Listing 4.22: Auflistung aller skalaren Typen nach der Codegerierung zu Typescript Typen

64
Fazit 5
Dieses Fazit soll einen Überblick darüber verschaffen, welche Ziele aus der Anforderungs-
analyse erreicht wurden und welche Features für die Zukunft noch interessant wären.
Zu Beginn sei vorweggenommen, dass GraphQL bzw. explizit graphql-ruby Lösungen
zu allen beschriebenen Problemen und Anforderungen aus Kapitel 3 „Anforderungsana-
lyse“ ermöglicht. Zudem konnte durch die Migration nach GraphQL die Skalierbarkeit
des Entwicklungsprozesses deutlich erhöht werden und zusätzlich der Grundstein für die
Implementierung der in Ausblick aufgeführten Features, gelegt werden.

Typschema
Durch die Definition des Typschemas in Ruby und der Übersetzungsmöglichkeiten in
andere Sprachen mithilfe von Codegeneratoren werden applikationsübergreifende und
zentrale Typdefinitionen als JSON Schema, die sogar größtenteils zu den alten JSON
Schema Definitionen passen, geschaffen (siehe Abschnitt 4.2.7). Im Gegensatz zum alten
System hat sich die Verantwortlichkeit für die Definition von Typen also vom Client auf
den Server verlagert. Diese werden jetzt dem Client in Form von JSON Schemata zur
Verfügung gestellt. Ausbaufähig an dieser Stelle ist das in Unterabschnitt 4.9.2 gezeigte
Defizit in der Codegenerierung.

Validierung
Des Weiteren wurden für Typescript Interfaces wie MultiLangString (siehe Listing 3.1),
die sich nicht im GraphQL Schema abbilden lassen, Scalar Typen entwickelt, deren Wer-
te bei lesenden und schreibenden Zugriffen gegen das alte JSON Schema validiert werden
(siehe Abschnitt 4.8). Alle abbildbaren Typen werden automatisch auf Korrektheit ge-
prüft. Die Prüfungen werden beim Lesen eingehender Anfragen und beim Erzeugen der
Antworten durchgeführt. Dadurch wird im Vergleich zum alten System ein höherer Grad
an Typsicherheit erlangt, da nun zusätzlich die Antworten zur Laufzeit vor dem Auslie-
fern an den Client validiert werden. Dies geschah vorher nur in Form von Request Specs
während des Deployment Prozesses (siehe Unterabschnitt 3.3.5).

65
Bennennungskonvention
graphql-ruby übersetzt automatisch alle Benennungen von der auf dem Server genutzte
Snake Case Schreibweise zur im Schema verwendeten Camel Case Schreibweise, nach
dem Auflösen eines Feldes. Somit kann auf dem Server weiterhin Snake Case für al-
le Benennungen genutzt werden und trotzdem wird Code in Camel Case Schreibweise
generiert.

Sicherheit
Ein weiterer Aspekt zur Sicherheit ist, dass alle GraphQL Queries, die im gesamten
Projekt genutzt werden, in einzelnen Dateien in einem für den Server zugreifbaren
Ordner gehalten werden. Stellt der Client eine Anfrage, wird geprüft, ob ein Name
(operationName) übermittelt wurde und dieser zu einer der im Ordner existierenden
Queries passt. Wenn ja, wird diese geladen und ausgeführt, andernfalls eine Fehlermel-
dung zurückgegeben. Somit können wie im alten System nur vordefinierte Interaktionen
ausgeführt werden.

Darstellungsflexibilität
In der Implementierung wurde obendrein ein System geschaffen, das alle für variierende
Darstellungsformen benötigten Features - wie Felder- und Sprachauswahl sowie Filtern,
Sortieren und Paginieren (siehe Unterabschnitt 3.5.1) - umsetzt. Innerhalb einer ser-
verseitigen Klasse BaseResolver, deren Konstruktor bei Listendarstellungen von allen
GraphQL Queries durchlaufen werden (siehe Abbildung 4.5), werden SQL Queries zu-
sammengesetzt. Diesem Prozess können Werte zum Paginieren, Filtern, Sortieren und
für die Sprachauswahl übergeben werden. Zudem kann aus dem Kontext einer Query
ausgelesen werden, welche Felder ausgewählt wurden.

Performance und Skalierbarkeit


Durch Auswählen der benötigten Felder ist das Overfetching Problem beseitigt wor-
den. Darüber hinaus kann in den Prozess des Zusammensetzens der SQL Query im
BaseResolver eingegriffen werden. Bei Anfragen, die ein N+1 Query Problem aufwei-
sen, können zur Beseitigung manuell Beziehungen in die Ergebnismenge einer Query
inkludiert werden. Somit lässt sich jedes Underfetching Problem - wie in Abbildung 4.6
mit der Anzahl der Code Ressourcen - manuell lösen. Vergleicht man zusätzlich die Ta-
bellen 4.1 und 3.1, in denen die Schritte zu Erstellung einer neuen Sicht beschrieben
werden, wird erkenntlich, dass durch die Migration nach GraphQL der Entwicklungsauf-
wand deutlich reduziert werden konnte.

66
5.1 Ausblick

Die Migration nach GraphQL ermöglicht eine Menge Features, die es sich lohnt, zukünf-
tig zu integrieren. Nachfolgend werden die wichtigsten Features aufgelistet und kurz
erläutert.

Batching
Wie bereits erwähnt, ist Batching ein Verfahren um mehrere Anfragen gebündelt zu
verschicken. Es könnte beispielsweise bei Anforderung einer Anfrage ein Zeitlimit (50ms)
gesetzt werden, welches abgewartet wird, bevor die Anfrage verschickt wird. Fordert
der Client innerhalb dieses Zeitlimits eine neue Anfrage an, wird diese mit der ersten
zusammengefasst. Nach Ablauf der 50ms werden dann alle zusammen als eine an den
Server geschickt.

Client Cache
Apollo bietet die Möglichkeit an einen Cache zu nutzen. In der gezeigten Implementierung
wird bei Anfragen aktuell die Option fetchPolicy auf network-only gestellt um bei
jeder Anfrage die neusten Daten zu erhalten (siehe Unterabschnitt 4.2.8). Diese Option
könnte zum Beispiel nur nach Ausführung einer Mutation gesetzt werden, sodass nur
neue Daten geladen werden, wenn sich auf dem Server etwas verändert hat.

Autorisierung
Der einzige Grad an Sicherheit, der implementiert wurde, ist, dass nur im Schema Ordner
definierte GraphQL Queries ausgeführt werden können. Die Nutzung einer Library für
rollenbasierte Autorisierung wie Pundit [vara] ist zu empfehlen.

Sorbet und Typesafe-Ruby


Sorbet [Str] und Typesafe-Ruby [Rex] sind Rubygems, die es unter anderem ermöglichen,
bei Funktionen typisierte Rückgabewerte zu definieren oder einem übergebenen Parame-
ter einen Typ aufzuzwingen. Dadurch würde - analog zum Client - die Typsicherheit in
der Serverapplikation garantiert werden.

Überschreiben von generierten Typdefinitionen


Das erwähnte Problem der löchrigen Codegenerierung (siehe Unterabschnitt 4.9.2) kann
durch Überschreiben der any-Typen mit den ursprünglich in Typescript definierten In-
terfaces, aus denen JSON Schemata generiert wurden, gelöst werden. Andernfalls kann

67
durch zum Beispiel die Ausführung eines Scripts die generierte Typescript Datei manuell
angepasst werden.

68
Literaturverzeichnis 6
[Fie00] Roy Thomas Fielding. Dissertation. 2000 (zitiert auf Seite 4).

[GB] Marco Tulio Valente Gleison Brito Thais Mombach. „Migrating to GraphQL: A Practical
Assessment“. Studie. Brazil: ASERG Group, Department of Computer Science, Federal
University of Minas Gerais (zitiert auf Seite 1).

[Hil19] Tom Hilge. „Implementierung von Authentifizierung und Autorisierung in eine bereits
vorhandene Webanwendung“. Wedel, 2. Sep. 2019 (zitiert auf Seite 31).

[Rie16b] Marcus Riemer. „BlattWerkzeug - Eine Datenzentrierte Entwicklungsumgebung Für


Den Schulunterricht“. Master-Thesis. Wedel: Fachhohschule Wedel, 31. Okt. 2016 (zitiert
auf den Seiten 1, 4, 18, 29).

Quellen im Internet

[apoa] apollographql. Apollo Client. url: https : / / github . com / apollographql / apollo - client
(besucht am 1. Sep. 2020) (zitiert auf Seite 44).

[apob] apollographql. Apollo Docs. url: https : / / www . apollographql . com / docs / angular/
(besucht am 1. Sep. 2020) (zitiert auf Seite 44).

[Cora] Microsoft Corporation. Type assertions. url: https://www.typescriptlang.org/docs/


handbook/basic- types.html#type- assertions (besucht am 17. Juli 2020) (zitiert auf
Seite 23).

[Corb] Microsoft Corporation. TypeScript Language Specification. url: https://github.com/


microsoft/TypeScript/blob/master/doc/spec.md#1-introduction (besucht am 3. Juli
2020) (zitiert auf Seite 12).

[Corc] Microsoft Corporation. TypeScript Utility Types. url: https://www.typescriptlang.org/


docs/handbook/utility-types.html (besucht am 3. Juli 2020) (zitiert auf Seite 13).

[Cord] Microsoft Corporation. Typescript Website. url: https : / / www . typescriptlang . org/
(besucht am 3. Juli 2020) (zitiert auf Seite 11).

[dav] davishmcclurg. JSONSchemer Github. url: https://github.com/davishmcclurg/json_


schemer (besucht am 18. Aug. 2020) (zitiert auf Seite 28).

[dot] dotansimha. graphql-code-generator. url: https : / / github . com / dotansimha / graphql -


code-generator#readme (besucht am 1. Sep. 2020) (zitiert auf Seite 51).

[Eim] Beate Frees Birgit van Eimeren Heinz Gerhard. ARD/ZDF-Online-Studie 2001: Inter-
netnutzung stark zweckgebunden. url: http://www.ard-zdf-onlinestudie.de/files/2001/
Online01_Nutzung.pdf (besucht am 1. Juli 2020) (zitiert auf Seite 3).

69
[Far] inc Farlex. superset. url: https://encyclopedia2.thefreedictionary.com/superset (be-
sucht am 3. Juli 2020) (zitiert auf Seite 11).

[Foua] GraphQL Foundation. GraphQL Clients. url: https://graphql.org/graphql-js/graphql-


clients/ (besucht am 1. Sep. 2020) (zitiert auf Seite 43).

[Foub] GraphQL Foundation. Graphql Query Execution. url: https : / / graphql . org / learn /
execution/ (besucht am 30. Juni 2020) (zitiert auf Seite 10).

[Fouc] GraphQL Foundation. Introduction to GraphQL. url: https : / / graphql . org / learn/
(besucht am 30. Juni 2020) (zitiert auf Seite 9).

[Foud] GraphQL Foundation. Who’s using GraphQL? url: https://graphql.org/users/ (be-


sucht am 1. Sep. 2020) (zitiert auf Seite 1).

[Gooa] Google. AsyncPipe. url: https : / / angular . io / api / common / AsyncPipe (besucht am
1. Sep. 2020) (zitiert auf Seite 52).

[Goob] Google. Displaying data in views. url: https : / / angular . io / guide / displaying - data
(besucht am 18. Aug. 2020) (zitiert auf Seite 25).

[Gooc] Google. Introduction to services and dependency injection. url: https://angular.io/


guide/architecture-services (besucht am 16. Juli 2020) (zitiert auf Seite 23).

[Good] Google. Observables in Angular. url: https://angular.io/guide/observables-in-angular


(besucht am 1. Sep. 2020) (zitiert auf Seite 52).

[Gooe] Google. Setup for server communication. url: https://angular.io/guide/http#requesti


ng-data-from-a-server (besucht am 17. Juli 2020) (zitiert auf den Seiten 23, 24).

[Goof] Google. Subscribing. url: https://angular.io/guide/observables#subscribing (besucht


am 1. Sep. 2020) (zitiert auf Seite 52).

[Goog] Google. Table. url: https://material.angular.io/components/table/overview (besucht


am 1. Sep. 2020) (zitiert auf Seite 52).

[Groa] PostgreSQL Global Development Group. PostgreSQL hstore Datatype. url: https://
www.postgresql.org/docs/9.1/hstore.html (besucht am 2. Juli 2020) (zitiert auf den
Seiten 17, 58).

[Grob] PostgreSQL Global Development Group. PostgreSQL json Datatype. url: https : / /
www.postgresql.org/docs/9.5/datatype-json.html (besucht am 2. Juli 2020) (zitiert auf
Seite 17).

[Groc] PostgreSQL Global Development Group. PostgreSQL Security. url: https : / / www .
postgresql.org/support/security/ (besucht am 15. Juli 2020) (zitiert auf Seite 4).

[Hana] David Heinemeier Hansson. 2.1 Naming Conventions. url: https://edgeguides.rubyon


rails.org/active_record_basics.html#naming-conventions (besucht am 16. Juli 2020)
(zitiert auf Seite 21).

[Hanb] David Heinemeier Hansson. Active Record Migrations. url: https://guides.rubyonrails.


org/active_record_migrations.html (besucht am 16. Juli 2020) (zitiert auf Seite 21).

[Inca] Facebook Inc. First Commit. url: https://github.com/graphql/graphql-spec/releases/


tag/July2015 (besucht am 1. Sep. 2020) (zitiert auf Seite 1).

70
[Incb] Facebook Inc. GraphQL Cursor Connections Specification. url: https : / / relay . dev /
graphql / connections . htm # sec - Forward - pagination - arguments (besucht am 3. Sep.
2020) (zitiert auf Seite 60).

[Incc] Snowflake Inc. ILIKE ANY. url: https : / / docs . snowflake . com / en / sql - reference /
functions/ilike_any.html (besucht am 3. Sep. 2020) (zitiert auf Seite 59).

[kaw] kawamataryo. schema2type. url: https://github.com/kawamataryo/schema2type (be-


sucht am 24. Aug. 2020) (zitiert auf Seite 34).

[Leva] Andy Leverenz. How to Use GraphQL with Ruby on Rails. url: https://web- crunch.
com/posts/how-to-use-graphql-with-ruby-on-rails (besucht am 3. Sep. 2020) (zitiert
auf Seite 41).

[Levb] Isa Levine. Ruby on Rails GraphQL API Tutorial: From ’rails new’ to First Query. url:
https://dev.to/isalevine/ruby- on- rails- graphql- api- tutorial- from- rails- new- to- first-
query-76h (besucht am 3. Sep. 2020) (zitiert auf Seite 41).

[Mic] Microsoft. TypeScript conventions. url: https://makecode.com/extensions/naming-


conventions# : ~ : text = TypeScript % 20conventions & text = Names % 20are % 20 % E2 %
80%9Ccontracted%E2%80%9D%20meaning%20the, methods%2C%20fields%20are%
20camel%20cased. (besucht am 24. Aug. 2020) (zitiert auf Seite 39).

[Mosa] Robert Mosolgo. Class: GraphQL::RakeTask. url: https : / / graphql - ruby . org / api -
doc/1.11.4/GraphQL/RakeTask (besucht am 1. Sep. 2020) (zitiert auf Seite 51).

[Mosb] Robert Mosolgo. Connection Concepts. url: https : / / graphql - ruby . org / pagination /
connection_concepts.html (besucht am 29. Aug. 2020) (zitiert auf Seite 60).

[Mosc] Robert Mosolgo. Extending the GraphQL-Ruby Type Definition System. url: https :
/ / graphql - ruby . org / type _ definitions / extensions . html (besucht am 30. Aug. 2020)
(zitiert auf Seite 45).

[Mosd] Robert Mosolgo. Field Introduction. url: https://graphql-ruby.org/fields/introduction


(besucht am 29. Aug. 2020) (zitiert auf Seite 46).

[Mose] Robert Mosolgo. Field Resolution. url: https://graphql-ruby.org/fields/introduction#


field-resolution (besucht am 29. Aug. 2020) (zitiert auf Seite 45).

[Mosf] Robert Mosolgo. graphql. url: https://www.rubydoc.info/gems/graphql (besucht am


3. Sep. 2020) (zitiert auf Seite 41).

[Mosg] Robert Mosolgo. graphql 0.9.2. url: https://rubygems.org/gems/graphql/versions/0.9.


2?locale=de (besucht am 29. Aug. 2020) (zitiert auf Seite 42).

[Mosh] Robert Mosolgo. GraphQL Ruby. url: https://graphql-ruby.org/ (besucht am 27. Aug.
2020) (zitiert auf Seite 41).

[Mosi] Robert Mosolgo. Guides. url: https://graphql- ruby.org/guides (besucht am 3. Sep.


2020) (zitiert auf Seite 41).

[Mosj] Robert Mosolgo. Input Typen. url: https://graphql-ruby.org/type_definitions/input_


objects.html (besucht am 29. Aug. 2020) (zitiert auf Seite 46).

[Mosk] Robert Mosolgo. Objects. url: https : / / graphql - ruby . org / type _ definitions / objects .
html#object-classes (besucht am 29. Aug. 2020) (zitiert auf Seite 45).

71
[Mwi] Luke Mwila. Understanding TypeScript’s Type System. url: https : / / medium . com /
better - programming / understanding - typescripts - type - system - a3cdec8e95ae (besucht
am 3. Juli 2020) (zitiert auf Seite 12).

[NB] Prisma Nikolas Burk. The Problems of „Schema-First“ GraphQL Server Development.
url: https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-
x1mn4cb0tyl3#conclusion-sdl-first-could-potentially-work-but-requires-a-myriad-of-
tools (besucht am 3. Sep. 2020) (zitiert auf Seite 41).

[Nog] Falco Nogatz. From XML Schema to JSON Schema -Comparison and Translationwith
Constraint Handling Rules. url: http://www.informatik.uni- ulm.de/pm/fileadmin/
pm/ home / fruehwirth / drafts / Bsc - Nogatz . pdf (besucht am 5. Juli 2020) (zitiert auf
Seite 14).

[Orga] Json Schema Organisation. JSON Schema Documentation. url: https://json-schema.


org/ (besucht am 30. Juni 2020) (zitiert auf Seite 14).

[Orgb] Json Schema Organisation. JSON Schema Implementations. url: https://json-schema.


org/implementations.html (besucht am 30. Juni 2020) (zitiert auf den Seiten 16, 27).

[Pria] Prisma. The evolution of GraphQL server development. url: https://www.prisma.io/


blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3#the-evolution-
of-graphql-server-development (besucht am 27. Aug. 2020) (zitiert auf Seite 42).

[Prib] GraphQL Community Prisma. Core Concepts. url: https://www.howtographql.com/


basics/2-core-concepts/ (besucht am 27. Aug. 2020) (zitiert auf Seite 40).

[Pric] GraphQL Community Prisma. The Fullstack Tutorial for GraphQL. url: https://www.
howtographql.com/ (besucht am 27. Aug. 2020) (zitiert auf Seite 41).

[Rex] Dario Rexin. typesafe-ruby 0.0.2. url: https : / / rubygems . org / gems / typesafe - ruby /
versions/0.0.2?locale=de (besucht am 21. Aug. 2020) (zitiert auf Seite 67).

[Rie16a] Marcus Riemer. 3. Mai 2016. url: https://bitbucket.org/marcusriemer/esqulino/comm


its/65b26c600ea72e753a5ae1414a953faac871ad88 (besucht am 4. Sep. 2020) (zitiert auf
Seite 1).

[Rie17a] Marcus Riemer. 22. Juni 2017. url: https://bitbucket.org/marcusriemer/esqulino/co


mmits/cc2b96b8e37aa1a2b47c76352ff0986ca8914431 (besucht am 4. Sep. 2020) (zitiert
auf Seite 1).

[Rie17b] Marcus Riemer. 3. Nov. 2017. url: https://github.com/MarcusRiemer/esqulino/comm


it/cc6112e0c8fa69d7d5185363df1518b7e668837e (besucht am 15. Mai 2020) (zitiert auf
den Seiten 1, 19).

[Sch] Yannick Schröder. ‘this.apollo is undefined‘ when testing components using generated
classes extending Apollo.Query. url: https://github.com/dotansimha/graphql- code-
generator/issues/4366/ (besucht am 1. Sep. 2020) (zitiert auf Seite 64).

[Staa] International Organization for Standardization. ISO alpha 2. url: https://www.iso.


org/obp/ui/#search (besucht am 22. Aug. 2020) (zitiert auf Seite 32).

[Stab] Radoslav Stankov. graphql-ruby Tutorial - Introduction. url: https://www.howtograph


ql.com/graphql-ruby/0-introduction/ (besucht am 3. Sep. 2020) (zitiert auf Seite 41).

[Str] Stripe. sorbet. url: https://sorbet.org/ (besucht am 21. Aug. 2020) (zitiert auf Seite 67).

72
[svi] svidgen. Is there any reason not to go directly from client-side Javascript to a database?
url: https://softwareengineering.stackexchange.com/questions/180012/is-there-any-
reason - not - to - go - directly - from - client - side - javascript - to - a - database (besucht am
30. Juni 2020) (zitiert auf Seite 4).

[TGa] Inc The Guild. GraphQL Mutations Documentation. url: https://graphql.org/learn/


queries/#mutations (zitiert auf Seite 11).

[TGb] Inc The Guild. GraphQL Resolver Documentation. url: https://www.graphql- tools.
com/docs/resolvers/ (besucht am 5. Juli 2020) (zitiert auf Seite 8).

[vara] varvet. includes. url: https://github.com/varvet/pundit (besucht am 3. Sep. 2020)


(zitiert auf Seite 67).

[varb] varvet. paginator-table-graphql.component.ts. url: https://bitbucket.org/marcusriemer/


esqulino / raw / aa23b8efb21251a58616fc7cdb565d1e48357118 / client / src / app / shared /
table / paginator - table - graphql . component . ts (besucht am 3. Sep. 2020) (zitiert auf
Seite 60).

[Web] PostgreSQL Tutorial Website. PostgreSQL COALESCE. url: https://www.postgresql


tutorial.com/postgresql-coalesce/ (besucht am 3. Sep. 2020) (zitiert auf Seite 59).

[Wik20] Wikipedia. Clean URL — Wikipedia, Die freie Enzyklopädie. 2020. url: https://de.
wikipedia.org/w/index.php?title=Clean_URL&oldid=200746668 (besucht am 7. Sep.
2020) (zitiert auf Seite 20).

[You] YousefED. Annotationen. url: https://github.com/YousefED/typescript-json-schema


#annotations (besucht am 21. Aug. 2020) (zitiert auf Seite 27).

73
Eidesstattliche Erklärung 7
Ich erkläre hiermit an Eides statt, dass ich die vorliegende Arbeit selbstständig und ohne
Benutzung anderer als der angegebenen Hilfsmittel angefertigt habe; die aus fremden
Quellen direkt oder indirekt übernommenen Gedanken sind als solche kenntlich gemacht.
Die Arbeit wurde bisher in ähnlicher Form keiner anderen Prüfungskommission vorgelegt
und auch nicht veröffentlicht.

Wedel, 09. September 2020 Yannick Schröder


Git Revision: 3082eeae 2020-09-07 20:11:46 +0200

75

Das könnte Ihnen auch gefallen