Upload
vuonghanh
View
224
Download
0
Embed Size (px)
Citation preview
SEMINARARBEIT
im Studiengang Informationsmanagement und Computersicherheit
Lehrveranstaltung Systemintegration
Projekt Autoverleih
Beispiel Web-Service
Hermann Wagner, BSc (1410303021)
Jovica Zuljic-Suljuzovic, BSc (1410303028)
Lukas Schweitzer, BSc (1410303015)
Begutachter: Dipl.-Ing. Dr. Johannes Osrael
Wien, 05.04.2015
2
Inhaltsverzeichnis
1 Aufgabenstellung ................................................................................................... 3
2 Architektur .............................................................................................................. 4
2.1 Applikation ............................................................................................................. 4
2.2 WS1 – Autoverleih ................................................................................................. 6
2.3 WS2 – Währungsrechner ......................................................................................17
2.4 WS3 – Google Maps Webservice .........................................................................17
3 Implementierung ...................................................................................................18
3.1 Applikation und WS1 – Autoverleih .......................................................................18
3.2 WS2 – Währungsrechner ......................................................................................38
3.2.1 getAllCurrencies....................................................................................................39
3.2.2 getCurrencyRate ...................................................................................................39
3.2.3 getChange ............................................................................................................39
3.3 WS3 – Google Maps Webservice .........................................................................41
3
1 Aufgabenstellung
Im Rahmen dieses Projektes sollen verschiedene
Arten von Web Services entwickelt und in einer
Applikation integriert werden.
Web Service 1: Autoverleih
Entwickeln Sie ein REST-Style Autoverleih Web Service: Das Buchen von Autos in
verschiedenen Währungen soll möglich sein. Für die Währungsumrechnung soll folgendes
Web Service verwendet werden:
Web Service 2: Währungsrechner
Entwickeln Sie ein SOAP/WSDL basierendes Währungsrechner Web Service. Dieses
soll im Hintergrund die Daten von der Europäischen Zentralbank integrieren:
http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml
Das Web Service soll auch sogenannte Cross-Rates berechnen und diese zur Verfügung
stellen. Z.B. kann man aus den EUR/USD und EUR/GBP Kursen den USD/GBP Kurs
berechnen.
Web Service 3: Google Maps API Web Services
Verwenden Sie eines der Google Maps API Web Services:
https://developers.google.com/maps/documentation/webservices/
Die integrierte Applikation soll die oben genannten Web Services benutzen. Es kann sich dabei
um eine Web-Applikation oder eine Android oder iOS App handeln.
Generell dürfen Sie Technologien Ihrer Wahl verwenden. Sie müssen jedoch für die selbst
entwickelten Web Services zwei verschiedene Technologien verwenden. z.B. Java für das
Autoverleih Web Service und .NET für den Währungsrechner.
4
2 Architektur
Dieser Abschnitt erläutert die Architektur unseres Webservices. Zur Architektur gehören die
verwendeten Technologien, die Schnittstellen und die Kommunikation zwischen den einzelnen
Services. Diese wurde vor der Umsetzung erarbeitet und haben als Grundlage für die
Verteilung der Arbeitspakete gedient.
2.1 Applikation
Die Applikation ist das User Interface mit dem der Benutzer des Autoverleih-Services
interagieren kann und stellt damit unser Front-End dar.
Technologien
Unser Front-End wird als Single-Page-Application (SPA)
umgesetzt. Das heißt, dass der benötigte Code (HTML, CSS,
JavaScript) nur mit einem einzigen Seitenaufruf geladen wird und alle später benötigten
Ressourcen dynamisch, asynchron vom Webserver im Hintergrund nachgeladen und in die
Seite (per JavaScript) eingefügt werden. Dadurch besteht die Webseite nicht mehr aus
Verlinkungen von einer HTML-Seite zur nächsten mit ständigem „Page Reload“, sondern es
gibt nur mehr ein einziges „Page Load“ beim Aufrufen der Website.
Dieser Ansatz hat folgende Vor- bzw. Nachteile:
Das Back-End braucht das Front-End nicht mehr dynamisch generieren (zb. PHP, JSP,
…), was zu einer Entkoppelung von Back- und Front- End führt und die Programmierung
und Arbeitsteilung erleichtert.
Der/die Back-End-Server werden entlastet
Weniger Netzwerk-Traffic, da nur mehr kleine Datenpakete und nicht gesamte Webseiten
ausgetauscht werden müssen (ähnlich iFrames)
Durch den Entfall des Reloads wird die Seite um einiges schneller und reaktionsfähiger
Der Benutzer muss JavaScript aktiviert haben, damit die Seite funktioniert
Der erste Seitenaufruf (Page Load) dauert eventuell etwas länger
Suchmaschinen führen oft keine JavaScript-Ausführung durch. Dadurch wurde
(zumindest in der Vergangenheit) die „Search Engine Optimization“ (SEO) für SPAs zu
einem Problem. Mittlerweile hat Google jedoch schon ein „Best practice“ für SEO bei SPAs
herausgegeben, damit auch das klappt.
Anforderungen & Funktionen
Die verschiedenen Anforderungen an unserer Applikation werden nachfolgend in Form von
„User Stories“ erläutert. Da wir die User Stories bereits vorgefiltert haben, lassen sich daraus
1 zu 1 die Funktionen der App und des WS1 ableiten.
5
Als Benutzer möchte ich aus verschiedenen Mietwägen ein Fahrzeug auswählen
können
Ich wünsche mir zu jedem Fahrzeug einige Detailinfos (Preis, Leistung,
Kilometerstand, Marke, Modell, …)
Ich möchte als User einstellen können in welcher Währung ich die Preise angezeigt
bekomme
Ich will den Preis/Tag sehen
Ich will auswählen können wie viele Tage ich das Auto benötige
Wenn ich nach der Auswahl die Währung ändere, dann sollen meine Daten nicht
verloren gehen. Ich will einfach nur sofort den Preis in einer anderen Währung sehen
Beim Klick auf Buchen soll das ausgewählte Fahrzeug für den ausgewählten Zeitraum
verbindlich gebucht werden
Ich will auf einer Karte sehen wo genau ich das Fahrzeug abholen kann
Schnittstellen & Kommunikation
Durch die SPA ergibt sich für uns bei der Umsetzung der Vorteil, dass wir das Front-End (fast)
nicht am Webserver rendern müssen, da die SPA sich selbst dynamisch, in Abhängigkeit von
der Benutzerinteraktion, anpasst. Nur zum Zugriff auf die Datenbank erfolgt eine (RESTful)
Kommunikation mit dem Web Service 1 über JSON und HTTP. Das heißt, dass unsere
Applikation, die am Client läuft direkt die Schnittstellen, die das WS1 zur Verfügung stellt,
benutzt, die empfangenen Daten weiterverarbeitet und diese anschließend dem Benutzer in
geeigneter Form präsentiert.
Auf dieselbe Art und Weise wie mit dem WS1 erfolgt auch der Datentransfer von WS3 zur
Applikation. WS3 ist das „Google Maps API Web Service“ und übermittelt auf HTTP Anfrage
verschiedenste Daten. Welche Schnittstellen die Google Maps API bietet ist hier zu finden:
https://developers.google.com/maps/documentation/webservices/
6
2.2 WS1 – Autoverleih
Das WS1 stellt der Applikation, also dem Front-End, alle Daten die für das Autoverleih benötigt
werden zur Verfügung, nimmt Reservierungen entgegen und persistiert diese. Das heißt, dass
das WS1 auch an eine Datenbank angebunden sein muss. Weiters überträgt das WS1 auch
die Single Page Application an den Client, wenn dieser das erste Mal die Webseite ansurft.
Darüber hinaus bedient sich das WS1 beim WS2 zur Währungsumrechnung.
Technologien
Da es sich bei unserem Front-End um eine Webseite handelt und wir dadurch
um JavaScript ohnehin nicht herumkommen, haben wir für das WS1
entschieden den JavaScript-Gedanken weiterzuspielen und gleich ein Web-
Service auf Basis des sogenannten MEAN-Stacks zu implementieren, wobei
MEAN für MongoDB, Express.js, Angular.js und Node.js steht. Das heißt, dass
wir für das WS1 einen Node.js Server verwenden, der uns erlaubt serverseitig
JavaScript einzusetzen. Um den Aufwand in Grenzen zu halten setzen wir
unser WS1 nicht direkt auf Node.js auf, sondern verwenden das Web Application
Framework Express.js, welches verschiedene „Middleware“ Funktionalitäten zur Verfügung
stellt. Und auch unsere dokumentenbasierte NoSQL Datenbank MongoDB verwendet
JavaScript für die Abfragen und JSON ähnliche Dokumente.
Bei Angular.js handelt es sich um ein Web Application Framework von Google, welches
speziell bei der Entwicklung von SPAs sehr hilfreich ist und es erlaubt den Client-Code nach
dem Model-View-Controller Pattern zu strukturieren.
7
Funktionen & Schnittstellen
Bevor wir näher auf die Schnittstellen eingehen werden, folgt zunächst erst einmal eine
Erläuterung an welche REST – Regeln und Best Practices wir uns halten, da REST
(Representational State Transfer) ja für das WS1 als Randbedingung vorgegeben wurde:
Roy Fielding (der Schöpfer von REST) hat in seiner Dissertation folgende 6 Kernpunkte von
REST definiert:
Uniform Interface – Einheitliche und gleichbleibende Schnittstellen zwischen Client
und Server entkoppeln die Architektur. Dadurch wird ermöglicht, dass beide
unabhängig voneinander weiterentwickelt werden können. Um das zu erreichen gibt
es folgende 4 Grundsätze:
o Resource-Based - Einzelne Ressourcen können über eine URI, die als
Ressource Identifier dient, identifiziert werden. Die Ressourcen selbst sind vom
Konzept her getrennt von der Repräsentation wie sie dem Client zur Verfügung
gestellt werden. Der Server sendet also nicht die ganze Datenbank, sondern
zB. HTML, JSON oder XML Dateien, die Auszüge aus der Datenbank
darstellen.
o Manipulation of Resources Through Representations – Sobald der Client
eine Repräsentation einer Ressource (inklusive Metadaten) hat, so muss er
genügend Informationen haben um die Ressource am Server ändern oder
löschen zu können, sofern er die Berechtigung dazu hat.
o Self-descriptive Messages – Jede gesendete Nachricht enthält genügend
Informationen damit der Empfänger weiß, wie er die Nachricht verarbeiten
kann.
o Hypermedia as the Engine of Application State (HATEOAS) – Die Clients
übermitteln Zustände über den Inhalt des Bodies der http-Nachricht, den URL
Übergabeparametern, Request Header und der angefragten URI.
Der Server übermittelt Zustände an die Clients über den http-Body, http-
Response-Codes und Response Headers. ( Hypermedia)
Weiters besagt HATEOS, dass wo es notwendig ist, Links im zurückgegebenen
Body oder Header angegeben werden, um die URI für das Wiederauffinden des
Objekts zur Verfügung stellen zu können.
Stateless
Zustandslosigkeit ist die Schlüsseleigenschaft bei REST. Der benötigte (Applikations-
) Zustand um einen Anfrage bearbeiten zu können, muss also in der Anfrage selbst,
als Teil der URI, der URL-Übergabeparameter (URL Query Strings), des Bodies oder
des Headers enthalten sein. Die URI dient dabei als Ressource Identifier und der Body
enthält den Zustand oder die gewünschte Zustandsänderung der Ressource. Während
eine Ressource für gewöhnlich in einer Datenbank zu finden ist und für alle Clients
8
konstant ist, kann der Applikationszustand für jeden Client bzw. bei jeder Anfrage
unterschiedlich sein.
Nachdem der Server die Anfrage verarbeitet hat, antwortet dieser mit dem neuen
Zustand der Ressource.
Sessions sind demnach also nicht erlaubt, da bei denen der Server Zustände über
mehrere http-Anfragen hinweg erhalten und updaten muss. Bei REST dagegen, muss
der Client alle benötigten Daten bei jeder Anfrage immer wieder mitsenden.
Zustandslosigkeit ermöglicht eine bessere Skalierbarkeit des Servers, da dieser keine
Benutzerdaten in Form von Sessions managen muss.
Wir verwenden Session für den Benutzerlogin wegen der Einfachheit halber und
weil sie für eine Web-Applikation, so wie wir sie haben, sehr gut funktionieren.
Cacheable
Das World Wide Web ermöglicht den Client Server-Antworten zu cachen. Daher muss
jeder Response implizit oder explizit angeben ob es möglich ist diese zu cachen oder
nicht, damit Clients nicht veraltete und falsche Daten bei erneuten Anfragen
verwenden. Gezieltes Caching reduziert wiederum die Client-Server-Kommunikation
und verbessert dadurch die Skalierbarkeit und Performance.
Client-Server
Die Trennung von Clients und Server über gleichbleibende Schnittstellen bedeutet zum
Beispiel, dass Clients (kann ein Web-Server, Single-Page-Application oder auch eine
Mobile App sein) sich nicht um die Datenspeicherung kümmern müssen, da diese ein
Implementierungsdetail eine jeden Servers ist und dadurch die Portabilität des Client
Codes erhöht wird.
Server kümmern sich im Gegenzug dafür nicht um das User Interface oder den User
Zustand, sodass diese wiederum einfacher werden und besser skalieren.
Server und Clients können im Endeffekt ausgetauscht bzw. getrennt voneinander
entwickelt werden, solange sich die Schnittstellen zwischen den beiden nicht ändern.
Layered System
Ein Client kann nicht unterscheiden ob er direkt mit dem End-Server verbunden ist,
oder über einen Intermediary Server. Diese können dazu verwendet werden um die
Skalierbarkeit durch Load-Balancing oder geteilten Caches zu erhöhen.
Code on Demand
Ein Server kann temporär die Funktionalität des Clients anpassen oder erweitern
indem er Logik (in Form von JavaScript-Code oder Java Applets) an den Client sendet,
der diesen dann ausführt.
Verwenden wir nicht.
9
Von diesen 6 Kernpunkten kann nur „Code on Demand“ als optional angesehen werden. Wenn
ein Web Service einen anderen Kernpunkt nicht einhält, so kann es nicht als streng RESTful
bezeichnet werden.
Da diese 6 Kernpunkte sehr allgemein sind halten wir uns bei der Umsetzung noch an folgende
REST Quick Tipps, die zu besser benutzbaren und wiederverwendbaren Webservices führen
sollen:
Use HTTP Verbs to Mean Something
Jeder API-Benutzer hat die Möglichkeit Anfragen mit GET, POST, PUT und DELETE
HTTP-Verben zu senden, da diese verdeutlichen was eine Anfrage genau macht. So
dürfen z.B. GET-Requests keine Ressource-Daten verändern.
Sensible Resource Names
Leicht verständliche und einfache Ressourcen-IDs (Pfade), wie zum Beispiel
“/posts/23” anstatt “/api?type=posts&id=23”, erhöhen das Verständnis dafür was ein
bestimmter Request tut. Die Verwendung eines URL Übergabeparameters kann zwar
sehr gut fürs Filtern benutzt werden, jedoch nicht für die Ressourcenamen selbst.
Durch diese Vorgehensweise werden Ressourcen in der API hierarchisch strukturiert,
was die Verwendung der API noch verständlicher macht.
Um das Ganze noch klarer zu machen, sollten die Ressourcennamen immer Nomen
sein. Um anzugeben was mit der Ressource gemacht wird (also der Verb-Anteil), sollen
die http Methoden (Get, Put, Delete, Post) verwendet werden.
Achtung: Die angezeigte URL im Browser muss nicht der verwendeten Web-Service
URI entsprechen. Diesen Fall hatten wir sehr oft bei unserer SPA.
XML and JSON
Solange dadurch die Kosten nicht zu hoch werden lässt man idealerweise den API-
Konsumenten entscheiden ob er XML oder JSON als Dateiformat verwenden möchte,
indem dieser einfach die Dateiendung .xml oder .json angibt.
Zusätzlich ist oft auch der Support von AJAX-Style User Interface als „wrapped JSON
(.wjson) oder XML(.wxml) response“ sehr hilfreich.
Man sollte aber JSON als Default bevorzugen. JSON gibt zwar vom Standard her nur
syntaktische Vorgaben (nicht über den Inhalt oder das Layout), doch gerade das macht
die Nachrichten sehr leichtgewichtig. Und auch bei XML in REST spielen die Standards
und Konventionen keine wirkliche Rolle – sondern nur die Syntax. So werden zB.
Namensräume und Schemata in RESTful Services nicht verwendet. Dadurch hat XML
gegenüber JSON keine wirklichen Vorteile mehr, sondern nur einen Overhead.
Dennoch verwenden einige, wenige Konsumenten noch immer XML Responses.
WS1 bietet nur Kommunikation über JSON.
Create Fine-Grained Resources
10
Bei der Implementierung eines Services ist es viel einfacher eine API zu erstellen, die
einfach die ihr zugrunde liegende Applikation oder Datenbankarchitektur nachstellt. Zu
irgendeinem Zeitpunkt möchte man dann vielleicht verschiedene Services, die auf
verschiedenen Architekturen aufsetzen, vereinigen um die Kommunikationsanteil zu
reduzieren. Es ist jedoch sehr viel einfacher eine große Ressource aus individuellen
Ressourcen zu erstellen, als der umgekehrte Weg. Deshalb sollte man bei der
Implementierung damit beginnen kleine, leicht zu definierende Ressourcen zu erstellen
und nur die CRUD (Create, Read, Update, Delete POST, GET, PUT, DELETE)
Funktionalitäten für diese zur Verfügung stellen. Daraus ist es später dann einfach Use-
Case orientierte und kommunikationsaufwand-reduzierte Ressourcen zu erstellen.
Consider Connectedness
Eines der REST-Prinzipien ist die Verbundenheit über Hypermedia Links. So werden
APIs viel selbsterklärender, wenn Links in den Antworten vom Server zu finden sind.
Zumindest eine Referenz auf die Ressource selbst sollte enthalten sein, damit der
Client weiß wie die Daten geholt wurden bzw. wieder geholt werden können. Bei der
Erstellung einer Ressource über POST, ist zusätzlich der HTTP Location Header zu
verwenden um den Ressourcenlink zur Verfügung zu stellen. Werden ganze
„Collections“ in einer Response zurückgeliefert, die Nummeriert werden können, so
sind zumindest ‚first‘, ‚last‘, ‚next‘ und ‚prev‘ Links sehr hilfreich.
Haben wir aus zeitlichen Gründen weggelassen.
Idempotence
Aus RESTful Sicht bedeutet Idempotence, dass Client ein und denselben Serviceaufruf
machen können und immer wieder genau dasselbe Ergebnis erhalten. Viele identische
Anfragen haben also dieselbe Wirkung wie eine einzige Anfrage. Erhöht man zum
Beispiel einen bei jedem Request, so ist die Operation nicht idempotent. Obwohl
idempotente Operationen am Server immer zum selben Ergebnis führen, können sich
die Server-Antworten unterscheiden, wenn sich die Ressource zwischen den Anfragen
geändert hat.
Folgende http Methoden sind idempotent: PUT, DELETE, GET, HEAD, OPTIONS,
TRACE.
Safety
Safe bedeutet, dass der Aufruf der Methode den Zustand des Servers nicht verändert.
Solche Methoden werden nur dazu verwendet um Informationen zu bekommen und
sie sollen keinerlei Nebeneffekte haben (außer Logging und Caching). Das heißt, dass
Clients solche „Safe Request“ wiederholt durchführen können, ohne sich über
eventuelle Nebenwirkungen Gedanken machen zu müssen. Zu den „Save Methods“
zählen: HEAD, GET, OPTIONS und TRACE. „Save Methods“ sind per Definition auch
Idempotent, da sie am Server dasselbe Ergebnis erzielen und werden als Read-Only
Operationen implementiert.
11
Safety bedeutet wiederum nicht, dass der Server jedes Mal mit exakt derselben
Response antwortet.
HTTP Methoden (Verben)
Die 4 am häufigsten verwendeten HTTP Methoden sind POST, GET, PUT und DELETE und
passen somit zu den CRUD Operationen.
HTTP Verb URI: /customer URI: /customers/{id}
GET 200 (OK), Kundenliste.
Verwende Seiten(-Nummerierung),
Sortierung und Filterung für große Listen
200 (OK), Ein Kunde.
400 (Not Found). ID not found or invalid.
PUT 404 (Not Found), es sei denn man
will alle Ressourcen in der Collection
auf einmal ändern
200 (OK)
204 (No Content)
404 (Not Found). ID not found or invalid.
POST 201 (Created), 'Location' header mit
Link auf /customers/{id}
404 (Not Found).
DELETE 404 (Not Found), es sei denn man
will alle Ressourcen in der Collection
auf einmal löschen
200 (OK).
404 (Not Found). ID not found or invalid.
Sehr wichtig sind in diesem Zusammenhang die Status-Codes, die der Server dem Client
zurückliefert. Eine Übersicht über die möglichen Codes ist hier zu finden:
http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
Ressourcen Benennung
Im Wesentlichen ist eine RESTFul API einfach eine Sammlung von URIs, HTTP-Aufrufe auf
diese URIs und die zugehörigen JSON/XML Antworten die eine Repräsentation der
Ressourcen darstellen.
Jede Ressource besitzt mindestens eine URI, die sie identifiziert. Dabei sollen URIs eine
vorhersehbare, hierarchische Struktur haben, wobei die Hierarchie die Beziehungen zwischen
den Daten darstellen soll.
RESTful API werden für Konsumenten geschrieben, daher sollten der Name und die
Strukturierung der URI eine Bedeutung für den Benutzer haben und diesem Klarheit
verschaffen.
Nachfolgend als Beispiel ein Bestellsystem mit Kunden:
AKTION RESSOURCE URI
Neuen Kunden im System anlegen POST http://www.example.com/customers
12
Kunden mit der ID# 33245 lesen, ändern
oder löschen GET|PUT|DELETE http://www.example.com/customers/33245
Neues Produkt erstellen POST http://www.example.com/products
Produkt mit der ID# 66432 lesen, ändern
oder löschen GET|PUT|DELETE http://www.example.com/products/66432
Neue Bestellung für den Kunden mit der ID#
33245 erstellen POST http://www.example.com/customers/33245/orders
Alle Bestellungen des Kunden 33245 holen GET http://www.example.com/customers/33245/orders
Einzelposten zur Bestellung 8769 des
Kunden 33245 hinzufügen POST http://www.example.com/customers/33245/orders/8769/lineitems
Ersten Einzelposten der Bestellung 8769
des Kunden 33245 holen GET http://www.example.com/customers/33245/orders/8769/lineitems/1
Bestellung 8769 holen, ohne den Kunden zu
kennen GET http://www.example.com/orders/8769
Einzelposten zur Bestellung 8769
hinzufügen, ohne den Kunden zu kennen POST www.example.com/orders/8769/lineitems
… …
Anzumerken ist, dass wir für Collections immer die Mehrzahl (Pluralization) verwenden:
‚Customers‘, ‚Products‘, ‚Orders‘ & ‚lineitems‘. Um auf ein bestimmtes Element innerhalb einer
Collection zu verweisen verwenden wir die ID. Dadurch ergibt sich, dass wir immer nur 2 Basis-
URLs für jede Ressource benötigen:
Eine für die Erstellung der Ressource in einer Collection
POST http://www.example.com/customers
Eine fürs Lesen, Updaten & Löschen der Ressource über ihren Identifier
GET|PUT|DELETE http://www.example.com/customers/{id}
Doch es gibt auch Fälle in denen die Pluralization keinen Sinn macht: Singleton Ressourcen.
Diese gehören nicht zu einer Sammlung und es kann nur eine Ressource diesen Typs geben.
Ein Beispiel dafür ist eine Konfiguration:
GET|PUT|DELETE http://www.example.com/configuration
GET|PUT|DELETE http://www.example.com/customers/12345/configuration
In beiden Fällen fehlt sowohl die ID als auch die POST-Methode.
Anti-Patterns
Nun noch ein Beispiel, wie man es nicht machen sollte!
Oft verwenden Services die URI um das Service Interface und die geforderte Operation
im URL Übergabeparameter anzugeben. In folgenden Beispielen soll der Kunde 12345
geändert werden und das Datenformat JSON sein:
GET http://api.example.com/services?op=update_customer&id=12345&format=json
GET http://api.example.com/update_customer/12345
GET http://api.example.com/customers/12345/update
13
Obwohl das dritte Beispiel noch das Beste der drei ist, verletzt es noch immer die
Regel, dass ein Verb in der URL ist und GET nicht ‚Safe‘ verwendet wird. Hier noch
eine fast richtige Variante, die jedoch noch immer redundant ist und die API Benutzer
nur verwirrt:
PUT http://api.example.com/customers/12345/update
Zur Vollständigkeit noch die korrekte Vorgehensweise:
PUT http://api.example.com/customers/12345/
Returning Representations
Da es wie schon erwähnt erstrebenswert ist, mehrere verschiedene Repräsentations-
möglichkeiten für Ressourcen anzubieten (json, xml, wjson, wxml), muss man den Clients eine
Möglichkeit geben, diese explizit anzufragen. Es hat sich mehr oder weniger durchgesetzt dies
mit Hilfe von Format-Specifiern in der URI zu tun, die im Endeffekt wie Dateiendungen
aussehen. So führt die URI
GET http://www.example.com/customers.xml
dazu, dass der Client eine Kundenliste im XML-Format vom Server bekommt. Wird der Format-
Specifier nicht angegeben, so soll der Server das „Default-Format“ (meist JSON) wählen. So
führen
GET http://www.example.com/customers/12345
GET http://www.example.com/customers/12345.json
zur selben Server-Response.
Wird eine vom Client gefordertes Format nicht vom Service unterstützt, so soll ein HTTP 404
Error zurückgegeben werden.
Linking
Eines der Hauptprinzipien von REST ist, dass der ‚Appliaction State‘ via Hypertext
kommuniziert wird (Hypertext As The Engine of Application State – HATEOAS). Roy Fielding
geht in seinem Blog sogar soweit dass er sagt, dass die Verwendung von Hypertext der
wichtigste Teil eines REST Interface ist. Eine API sollte über Links auf verschiedene
Datenkomponenten verständlich, verwendbar und navigierbar sein ausgehend von einer
initialen URI. Auf einen Request nur Daten zurückzuliefern ist seiner Meinung nach nicht
gutzuheißen.
In der Praxis zeigt sich aber, dass die meisten Web Services eigentlich fast nur Daten und
wenige bis gar keine Links liefern. So werden Collections nicht so wie Fielding fordert als eine
Liste von Links geliefert, sondern meist als ein Array von Objekten. Hintergründe dafür sind
zum einen die Reduktion der „chattiness“ und zum Anderen erhöht die volle Verwendung von
HATEOAS die Komplexität und bürdet dem Client Service einiges an zusätzlichen Aufwand
auf, was wiederum die Produktivität der Entwicklung senkt.
14
Minimal Linking Recommendations
Es gibt jedoch eine Hand voll von Hyperlinking Praktiken, die die Service Verwendbarkeit,
Navigierbarkeit und Verständlichkeit erhöht und gleichzeitig die Auswirkungen auf die
Entwicklung kompakt halten, indem die Kopplung zwischen Client und Server gering gehalten
wird.
Erstellung
Die URI (= Link) der neu erstellten Ressource wird im Location Response Header
mitgegeben. Der Response Body bleibt entweder leer oder enthält die ID der neu
erstellten Ressource.
Client Request – “Create new User”
POST http://api.example.com/users
Server Response – “Created User successfully, see Location Header for Resource URI
Response Body is empty.”
Response Header:
HTTP/1.1 201 CREATED
Status: 201
Connection: close
Content-Type: application/json; charset=utf-8
Location: http://api.example.com/users/12346
Response Body:
Zurückliefern von Collections
Liefert ein Service eine Sammlung von Ressourcen-Repräsentationen zurück, so soll
jede Repräsentation mindestens einen ‚self link‘, also einen Link auf sich selbst in
seiner eigenen Link-Collection haben.
Gibt es noch weitere Links in der Collection (‚first‘, ‚last‘, ‚next‘, ‚previous‘), so sollen
diese in einer separate Links-Collection zurückgegeben werden.
{
"data":[
{ "user_id": "42",
"name": "Bob",
"links":[
{ "rel": "self",
"href": "http://api.example.com/users/42"
}
]
},
{ "user_id": "22",
"name": "Frank",
"links":[
{ "rel": "self",
"href": "http://api.example.com/users/22"
}
]
},
{ "user_id": "125",
"name": "Sally",
"links":[
{ "rel": "self",
"href": "http://api.example.com/users/125"
}
15
]
}
],
"links":[
{ "rel": "first",
"href": "http://api.example.com/users?offset=0&limit=3"
},
{ "rel": "last",
"href": "http://api.example.com/users?offset=55&limit=3"
},
{ "rel": "previous",
"href": "http://api.example.com/users?offset=3&limit=3"
},
{ "rel": "next",
"href": "http://api.example.com/users?offset=9&limit=3"
}
]
}
Aus den in 1.1 genannten Anforderungen ergeben sich für das WS1 folgende Funktionen
und Schnittstellen:
Gibt den aktuell authentifizierten Benutzer zurück [100%]
Request GET http://localhost:3000/users/me
Request-Params Benutzer-Cookie
Conditions Benutzer muss eingeloggt sein
Response Gibt das User-Objekt des aktuell eingeloggten Benutzers zurück
Benutzer mit Benutzername und Passwort registrieren [100%]
Request POST http://localhost:3000/auth/signup
Request-Params firstName, lastName, email, username, password
Conditions keine
Response Gibt das User-Objekt des neu erstellten Benutzers zurück
Administrator oder Angestellten registrieren [100%]
Request POST http://localhost:3000/auth/registerAdmin
Request-Params firstName, lastName, email, username, password, roles
Conditions Administrator muss dazu eingeloggt sein
Response Gibt das User-Objekt des neu erstellten Benutzers zurück
Benutzer mit Benutzername und Passwort anmelden [100%]
Request POST http://localhost:3000/auth/signin
Request-Params username, password
Conditions keine
Response Gibt das User-Objekt des neu erstellten Benutzers zurück
Aktuellen Benutzer abmelden [100%]
Request GET http://localhost:3000/auth/signout
Request-Params Benutzer-Cookie
Conditions Benutzer muss eingeloggt sein
Response Redirect to http://localhost/3000/
16
Initialisiere den Facebook OAuth Login Prozess [75%]
Request GET http://localhost:3000/auth/facebook
Request-Params .
Conditions .
Response .
Registriere eine Callback URI für den Facebook OAuth Prozess [75%]
Request GET http://localhost:3000/auth/facebook/callback
Request-Params .
Conditions .
Response .
Neuen Mietwagen erstellen [100%]
Request POST http://localhost:3000/vehicles
Request-Params .
Conditions .
Response Gibt das Vehicle-Objekt zurück.
Liste aller Mietwägen holen [100%]
Request GET http://localhost:3000/vehicles
Request-Params .
Conditions -
Response Liste mit allen Mietwagen Objekten
Mietwagen mit der {vehicleID} holen / bearbeiten / löschen [75%]
Request GET|PUT|DELETE http://localhost/vehicles/:vehicleID
Request-Params vehicleID, Benutzer-Cookie
Conditions Benutzer muss eingeloggt sein
Response (geändertes/gelöschtes) Vehicle-Objekt
Aktuelle Wechselkurse holen [100%]
Request GET http://localhost/currencies
Request-Params .
Conditions .
Response Objekt mit einer Währungs-Liste und einer Umrechnungsfaktor-Liste
Kommunikation und weitere Schnittstellen
Das WS1 antwortet auf Anfragen mit einem http-Response, welches bei Bedarf ein JSON-
Objekt im Body enthält um Daten an den Client zu senden.
Weiters besitzt das WS1 eine SOAP-Schnittstelle zum WS2, wobei das WS1 als SOAP-
Client agiert. Das WS2 bietet einen Währungsrechner dessen Schnittstellen über eine WSDL
spezifiziert werden und wird nachfolgend genauer beschrieben.
17
2.3 WS2 – Währungsrechner
Für das Webservice des Currency-Converters soll die Programmiersprache JAVA verwendet
werden.
Die Schnittstellen/Medthoden des Currency Converters sind nachfolgend aufgelistet.
String[] getAllCurrencies()
Double getCurrencyRate(String Währung)
Double getChange ( String Währung1,
String Währung2,
String BetragWährung1)
2.4 WS3 – Google Maps Webservice
Beim WS3 handelt es sich um das Google Maps Webservice, welches der Client benutzt, um
dem End-User die genaue Kartenposition der Abhol- und Rückgabe- Stationen anzuzeigen.
Google hat dieses Web Service äußerst ausführlich dokumentiert und viele anschauliche
Beispiele unter folgendem Link zur Verfügung gestellt:
https://developers.google.com/maps/documentation/webservices/
Eine typische Webdienstanfrage besitzt folgende Grundstruktur:
https://maps.googleapis.com/maps/api/service/output?parameters
Dabei gibt service den angeforderten Dienst und output das Antwortformat an, meist json
oder xml.
Da das WS3 unter dem oberhalb genannten Link sehr gut beschrieben ist, wollen wir hier
nicht weiter darauf eingehen.
18
3 Implementierung
Nachfolgend sind die Implementierungsdetails zu jedem Teil unseres Web Services zu finden.
Dazu gehört eine kurze Beschreibung der verwendeten Tools, Frameworks, Bibliotheken und
des jeweiligen Build- und Deployment- Vorgangs, sowie eine Erläuterung der Code und
Verzeichnisstruktur bzw. der Implementierungsdetails.
3.1 Applikation und WS1 – Autoverleih
In diesem Kapitel wird die Entwicklung, der Quellcode und die Funktionalitäten des Autoverleih
Web Services und der Front-End Applikation näher erläutert. Wie schon im Kapitel
„Architektur“ erwähnt, verwenden wir für das WS1 den MEAN Stack. Das heißt, dass das WS1
bei uns als Node.js/Express.js Web Server Applikation mit Datenhaltung in einer MongoDB
Datenbank umgesetzt ist.
Weiters bedeutet das, dass es sich bei
unserem Front-End um eine Angular.js Single
Page Applikation handelt und dass diese somit
beim ansurfen der Webseite direkt vom WS1
an den Browser des Benutzers übermittelt wird.
Somit ist der gesamte Code Applikations- &
WS1- Code (inkl. der Datenbank-Abfragen)
einheitlich in JavaScript gehalten.
Initiales Aufsetzen der Entwicklungsumgebung
Wir wollen uns nun ansehen wie ein MEAN.js Stack installiert und ein neues MEAN.js Projekt,
nach den Best Practices der Node.js Front-End und Back-End Entwicklung, angelegt wird. Da
das gesamte Projekt unter Windows 7 bzw. 8.1 umgesetzt wurde, gelten alle nachfolgenden
Anleitungen in erster Linie für Windows. Die Unterschiede zu MacOSX oder Linux sind jedoch
vernachlässigbar gering.
19
Wir werden nicht alle verwendeten Tools, Bibliotheken und Frameworks erläutern, da es
schlichtweg zu viele sind. Dennoch verlinken wir immer auf die offizielle Webseite des
jeweiligen Moduls, sodass nähere Informationen auf dieser nachgelesen werden können.
1. Node.JS (+ NPM) installieren (per Installer)
2. MongoDB installieren (per Installer)
3. Github installieren (per Installer)
4. Yeoman, Grunt & Bower installieren npm install -g yo bower grunt-cli
5. YEOMAN MEAN.JS Generator installieren npm install -g generator-meanjs
6. Projektverzeichnis anlegen und in dieses wechseln mkdir autoverleih
cd autoverleih
7. Neue MEAN.JS Applikation erstellen yo meanjs
Nun einige Daten zur Applikation (Name, Keywords,…) angeben.
8. Datenbank starten und Applikation initial testen mongod
grunt
Im Browser http://localhost:3000 eingeben
9. Github einrichten
.gitignore Datei mit Best Practices wird automatisch vom Yeoman-Generator angelegt.
In dieser Datei werden automatisch die Auto-generierten Verzeichnisse und die Node
Module ausgeklammert. Diese kommen nicht ins Repository und müssen auf jedem
Entwicklungs-PC erneut installiert werden (siehe Abschnitt „Aufsetzen der
Entwicklungsumgebung II“).
a) Github Repository lokal hinzufügen
b) Commit & Sync durchführen
20
Projekt – Dateistruktur
Nachdem wir das Projekt mit dem YEOMAN MEAN.JS Generator erstellt haben, bekommen
wir folgende Projektstruktur:
Die Dokumentation der einzelnen Dateien und
Verzeichnisse kann hier gefunden werden.
Dennoch erkläre ich an dieser Stelle kurz die
wichtigsten Dateien (blau markiert) und die 4
Verzeichnisse:
app
Enthält den Quellcode für die Server-
Applikation und ist nach dem MVC-
Pattern strukturiert.
o controllers
Enthält die Express Kontroller
(Backend Business Logik)
o models
Enthält die Mongoose Modelle (Backend Models)
o routes
Enthält die Server Routing Konfigurationsdateien für die Express Routen
o tests
Enthält die Mocha Tests für die Tests der Backend Business Logik
o views
Enthält die Backend Views (wie zB. error pages) die an den Client gesendet
werden. Da wir AngularJS verwenden, sind benötigen wir keine Server Templates.
config
Der config Ordner enthält alle Dateien, die für die Konfiguration der Applikation benötigt
werden:
o env
Enthält Konfigurationsdateien, die von config.js geladen werden.
o strategies
Enthält die Strategie-Konfigurationsdateien, die von passwort.js geladen werden
o config.js
21
Enthält den Konfigurations-Loader, der die passwende Konfiguration vom env-
Ordner lädt.
o express.js
Express Konfigurationsdatei zur Initialisierung und Konfiguration der Express
Applikation
o init.js
Das Haupt-Initialisierungsfile, welches Projektspezifische Konfigurationen enthält
o passport.js
Passwort Konfigurationsdatei zur Initialisierung und Konfiguration der Passwort
Authentifizierungs-Strategien vom strategies Ornder.
public
Enthält alle statischen Dateien der Applikation. Hier werden die Front-End Dateien (in
unserem Fall Angular.js) gespeichert.
o dist
Enthält die komprimierte Front-End Applikation (application.min.js und
application.min.css). Dieser Ordner wird automatisch durch die Grunt Tasks uglify,
cssmin und build erstellt. Die Komprimierung hat den Hintergrund, dass die an den
Client übertragenen Dateien natürlich möglichst klein sein sollen.
o modules
Enthält den Client Code in Form von Angular.js Modulen. Wichtig ist dabei zu
erwähnen, dass unsere Angular.js SPA in Form von horizontalen Modulen
gegliedert ist.
Was das bedeutet? Nun, jeder der Angular ein wenig kennt, weiß dass Module ein
Kernfeature von Angular sind – Im Endeffekt ist jede AngularJS Applikation nichts
anderes als ein Angular Modul. Essentiell ist nun, dass ein jedes Modul in Angular,
andere Sub-Module als Abhängigkeiten einbinden kann (Dependency Injection).
Das sieht dann in etwa wie folgt aus:
angular.module(‘FirstDependency’, []);
angular.module(‘SecondDependency’, []);
angular.module(‘MainModule’, [‘FirstDependency’, ‘SecondDependency’]);
Die ersten beiden definierten Module werden also ins “MainModule” als
Abhängigkeiten eingetragen und können in diesem verwendet werden. Auf diese
Art und Weise kann die Applikationslogik sehr gut strukturiert werden.
22
Das heißt, anstatt (wie oberhalb dargestellt) unsere Applikation horizontal zu
strukturieren, indem wir ein zentrales Modul haben und unter diesem alle anderen
Angular Komponenten zuordnen (diese können natürlich in Ordnern
zusammengefasst werden), verwenden wir je ein Modul pro Logikeinheit
(Funktionseinheit) und haben im selben Verzeichnis nur Angular Komponenten,
die von der Logik her zu diesem Modul passen.
Der Vorteil bei dieser Strukturierung ist die Übersichtlichkeit,
Verständlichkeit und die Möglichkeit schnell eine bestimmte
implementierte Funktionalität im Code zu finden. Bei der
horizontalen Strukturierung ist das vor allem bei großen
Projekten schwierig, wenn dann zB. im Controller-Ordner
mehr als 20 Controller abgelegt sind.
Innerhalb eines Moduls kann man die einzelnen
Komponenten (Controllers, Services, Filters, Views, …)
wiederum in eigene Verzeichnisse geben um den Überblick
zu bewahren (siehe Abbildung rechts).
o lib
Enthält alle Front-End Bibliotheken, wie Angular.js, jQuery, Bootstrap, usw.
o config.js
Angular.js Konfigurationsmodule
o application.js
Angular.js Einstiegsdatei. Übernimmt das Bootstrapping der Applikation und das
einbinden der richtigen Module.
23
node_modules
Enthält alle Node.js Module (Express, Bower, Grunt, Passport, Mocha, …) die für die
Erstellung der Applikation benötigt werden.
.GITIGNORE
Das Git Ignore File, welches Git sagt, welche Dateien und Verzeichnisse er ignorieren
und somit nicht zum Repository hinzufügen soll.
bower.json
Bower Definitionsdatei für die Konfiguration der Front-End Bibliotheken und
Frameworks, die man verwenden möchte (zB: Bootstrap, Angular.js, …)
gruntfile.js
Grunt Definitionsdatei in der die verschiedenen Grunt Tasks definiert werden, die der
Taskrunner ausführen soll (build, test, run, …).
package.json
NPM Definitionsdatei für die verschiedenen Backend Node.js Module, die man
verwenden möchte.
server.js
Main File und somit Einstiegspunkt unserer Node.js Applikation.
Aufsetzen der Entwicklungsumgebung II
Nachdem der Quellcode den Kollegen über Github zur Verfügung gestellt wird anbei die
Anleitung wie sie die Applikation lokal bei ihnen in Betrieb nehmen:
1. Schritte 1 bis 4 des vorherigen Kapitels durchführen
2. Github Repository herunterladen, entpacken und Shell im Verzeichnis starten
3. Node Module installieren
npm install
4. MongoDB starten und Applikation starten
mongod
grunt
Bootstrap und Konfiguration der Server App
Die server.js Datei im / Ordner ist der Einstiegspunkt unserer Server-Applikation und wird vom
Node.js Server als erstes gestartet. Diese ist mit weniger als 40 Zeilen recht übersichtlich
gehalten und wird nachfolgend erläutert.
24
Der Config Ordner ist der Speicherplatz für die Konfigurationsdateien, wobei die Dateien nach
der Reihe von server.js geladen werden.
Der Start ist bei der init.js Datei (Zeile 5). Diese macht nichts anderes als zu prüfen ob die
NODE_ENV-Variable gesetzt wurde bzw. wenn ja, ob es eine gültige Konfigurationsdatei für
dieses Environment im Ordner /config/env gibt. Ist das nicht der Fall, so wird ein Fehler
ausgegeben. Gültige Werte für die NODE_ENV Variable sind in unserem Fall ‚test‘,
‚development‘, ‚production‘ und ‚secure‘.
Wurde die NODE_ENV-Variable nicht gesetzt, so wird sie per Default auf ‚development‘
gesetzt.
Um die Applikation zb. im Test Environment laufen zu lassen muss die Applikation wie folgt
gestartet werden:
Linux: $ NODE_ENV={test}
$ grunt
Windows CMD: $ set NODE_ENV=test
$ echo %NODE_ENV%
$ grunt
Windows Powershell: $ $env:NODE_ENV = “test”
$ echo $Env:NODE_ENV
$ grunt
25
Danach folgt das Laden der config.js Datei (Zeile 6). Diese lädt dann die Dateien des /env
Ordners in Abhängigkeit von der NODE_ENV Variable. Einzig und allein die all.js Datei wird
immer geladen. Ansonsten wird unterschieden zwischen den Konfigurationen für
Development, Production, Test und Secure.
Darüber hinaus werden in der config.js noch die drei Hilfs-Funktionen getGlobbedFiles(),
getJavaScriptAssets(),getCSSAssets() mit denen Dateien in Abhängigkeit von Patterns geholt
werden können, definiert.
Danach werden in Zeile 7 & 8 die beiden externen Module ‚mongoose‘ und ‚chalk‘ geladen.
Auf Mongoose werden wir später noch näher eingehen. Chalk dient nur zum Zweck des
„Terminal String Stylings“. Dadurch können wir zB. Fehler in roter Schrift am Terminal
ausgeben oder wichtige Informationen mit einer entsprechenden Hintergrundfarbe
hervorheben.
Nun wird in Zeile 16 – 21 die Verbindung zur MongoDB Datenbank über eine Mongoose-
Connection aufgebaut und eine rote Fehlermeldung ausgegeben, falls die Datenbank nicht
erreichbar sein sollte.
Anschließend initialisieren wird in Zeile 24 die Express Applikation indem wir /config/express.js
laden und in der Zeile 27 „Passport“ indem wir /config/passport laden. Beide
Konfigurationsdateien werden später noch genauer erläutert.
Zu guter Letzt binden (bind()) wir die Express App noch an einem Port (Per Default bei
Development & Production auf 3000, bei Test auf 3001 und bei Secure auf 443. Alternativ
kann der Port mit der Environment-Variablen ‚PORT‘ angegeben werden (zB. 80) und horchen
(listen()) auf eingehende Verbindungen.
Sobald der Server einen eingehenden HTTP-Request bekommt, wird dieser von der Server-
Applikation verarbeitet (Routing, Express-Middleware) und anschließend mit einer HTTP-
Response dem Sender geantwortet. Wird zum Beispiel die Server-URL ohne weitere
Pfadangabe aufgerufen (‚/‘), so antwortet der Server mit dem gerenderten Template
„index.server.view.html‘. In dieses Template wird wiederum beim Rendern
‚layout.server.view.html‘ eingebunden, die unsere AngularJS Single-Page-Applikation enthält
(HTML + CSS/JavaScript Include-Anweisungen). Sobald dann der Client das DOM vollständig
geladen hat, wird dann die AngularJS – Applikation am Client gestartet.
Abhängigkeiten der Server App
Wie schon erwähnt verwenden wir in der Server App externe Module wie Mongoose, Chalk,
Consolidate, Express, Blob, Passport, usw. All diese externen Abhängigkeiten sind Node.JS
Module, die im Ordner /node_modules abgelegt sind und mittels NPM heruntergeladen wurden
(npm install moduleX --save). Das --save beim Installieren gibt an, dass das Modul auch in die
„dependencies“ Sektion der /package.json Datei eingetragen werden soll. –save-dev dagegen
gibt an, dass das Modul in die devDependencies eingetragen werden soll, was heißt, dass es
26
sich dabei um ein Modul handelt welches nur während der Entwicklung benötigt wird und dann
beim Deployment weggelassen werden sollte.
Durch die Package.json Datei hat man mehrere Vorteile. Zum einen gibt die Datei eine
Übersicht welche Module bereits installiert wurden (sofern man immer mit --save installiert
oder brav manuell einträgt) und zum anderen lassen sich mit ihrer Hilfe auch bequem mehrere
bzw. alle Module auf einmal updaten. Zusätzlich erlaubt es uns, dass wir das Verzeichnis
/node_modules, welches recht groß werden kann, in die .gitignore Datei eintragen und somit
nicht ins Repository hochladen müssen. Jeder der sich eine Kopie zieht muss einfach nur vor
der ersten Ausführung „npm install“ in die Konsole tippen und automatisch werden alle, im
package.json eingetragenen Module, installiert.
Wie das Package.json File genau aufgebaut ist, kann hier und hier sehr gut eingesehen
werden.
Bootstrap und Konfiguration der Client App
Die Client Applikation befindet sich in unserem Fall (fast) zur Gänze im Ordner /public. Die
einzige Außnahme bilden die Template Dateien im Ordner /app/views. Diese Template
Dateien sind HTML-Dateien und werden von der Server Applikation an den Client übertragen.
Da wir eine AngularJS Applikation haben, geschieht dies nur beim ersten Laden der Seite, im
Fehlerfall (Nicht vorhandene URL wird angesurft) und bei der Installation der App. Das Ganze
wird am Server mit der Hilfe von ‚Routes‘ gemacht, wie genau wird im Abschnitt „Automatische
Generierung“ erläutert. Auf jeden Fall wird in der gerenderten Template-Datei, die an den
Client gesendet wird, auf CSS und JavaScript-Dateien verwiesen, die sich am Server im /public
Verzeichnis befinden und zu unserer Angular-Applikation gehören.
Eine AngularJS App besteht aus verschiedenen Modulen, die bei uns vertikal strukturiert sind.
Doch es gibt ja nicht nur die eigenen Module, sondern auch Third-Party Module (Angular-
Bootstrap, Angular-Mocks, Angular-animate, Angular-ui-router, …) welche in die App
eingebunden werden müssen. Dies können wir mittels einer Dependency Injection (DI) der
Module ins AngularJS Main Application Module bewerkstelligen.
Um die Injection von internen als auch externen Angular Modulen zu vereinfachen, gibt es bei
uns eine Konfigurationsdatei namens config.js im Ordner public/.
In dieser Datei wird ein Objekt namens ApplicationConfiguration erstellt, welches folgende
Member aufweist:
applicationModuleName
String mit Namen des Angular Main-Moduls
applicationModuleVendorDependencies
Array mit externen Abhängigkeiten
registerModule(modulName, dependencies)
27
Funktion zum Erstellen eines neuen Moduls inkl. dessen Abhängigkeiten. Weiters wird
das neu erstellte Modul automatisch dem Angular Main-Modul als Abhängigkeit
hinzugefügt
Diese Konfigurationsdatei wird dann vom AngularJS-Start Module verwendet um die Angular
Applikation zu starten:
public/application.js
Diese Datei wird dazu verwendet um die AngularJS App zu starten. Dazu wird
1. Das Main Application Module mit den Daten des ApplicationConfiguration Objekts
angelegt
2. Der Hashbang in HTML5 Modus aktiviert & und ein Facebook Bug behoben
Mehr Infos dazu sind hier und hier zu finden.
3. Die Angular-App gestartet, sobald der DOM vollständig geladen wurde. Dazu wird der
Aufruf der Methode angular.bootstrap() in die Callback-Funktion von document.ready()
geschrieben.
Erst wenn die Angular-App mit bootstrap() gestartet wurde, werden die ganzen Angular-
spezifischen Elemente (Views, Models, Expressions, Services, Direktiven) ausgewertet und
der DOM entsprechend manipuliert. Wenn die bootstrap()-Methode fertig ist, so ist auch schon
die Webseite im Browser sichtbar.
Abhängigkeiten der Client App
Auch die Client Applikation hat Abhängigkeiten. Hier sind es jedoch in erster Linie JavaScript
bzw. CSS Bibliotheken und Frameworks, die gemanagt werden müssen. Dazu verwenden wir
das NodeJS Modul Bower, welches ein Paketmanager ist. Bower verwendet wiederum gleich
wie NPM (package.json) eine Manifest Datei in der Pakete definiert werden können. Diese
heißt bei Bower bower.json und kann initial mit „bower init“ erstellt werden. Wie die Datei
aufgebaut ist kann hier gefunden werden.
Es gilt wiederum dasselbe wie bei NodeJS Modulen: Die Option --save bzw. --save-dev beim
Installieren führt dazu, dass das Paket auch gleich ins Dependency bzw. devDependency
Array der bower.json Datei hinzugefügt werden (bower install <package> --save).
Da Bower Abhängigkeiten automatisch ins Verzeichnis „bower_components/“ speichert,
haben wir zusätzlich noch die Konfigurationsdatei „.bowerrc“, die Bower anweist die
Abhängigkeiten in ein anderes Verzeichnis {"directory": "public/lib"} abzulegen.
Automatische Generierung
Unsere MEAN.js Applikation und der MEAN.js YEOMAN Generator sind so konzipiert, dass
sie uns bereits einiges an nerviger wiederholender Arbeit abnehmen und dadurch auch immer
wieder auftretende Fehler vermieden werden. Hier die Liste mit automatisierten Tasks in
unserer Applikation, die uns das Leben erleichtern:
28
Front-End
HTML Includes
Die vertikale Struktur unserer Front-End App ermöglicht es JavaScript und CSS
Dateien der einzelnen Module automatisch in die Applikations-HTML-Datei zu
inkludieren. Dies wird bewerkstelligt indem folgende Zeilen ins Node-Template
„layout.server.view“ eingefügt wurden:
{% for cssFile in cssFiles %}
<link rel="stylesheet" href="{{cssFile}}">
{% endfor %}
...
{% for jsFile in jsFiles %}
<script type="text/javascript" src="{{jsFile}}"></script>
{% endfor %}
Somit braucht man einfach nur mehr Module erstellen und diese werden automatisch
in die Angular-App eingebunden sobald unsere Template Engine „Swig“ das Template
rendert. Die Registrierung und Konfiguration der Template Engine selbst, wird in der
config/express.js durchgeführt:
// Set swig as the template engine: config.templateEngine = ‘swig’
app.engine('server.view.html', consolidate[config.templateEngine]);
// Set views path and view engine
app.set('view engine', 'server.view.html');
app.set('views', './app/views');
Mit “app.engine” wird dabei angegeben, dass Express die Template Engine ‚Swig‘ für
Dateien mit der Dateiendung ‚server.view.html‘ verwenden soll.
Mit „app.set(name, value)“ können wir die Einstellungen der Express Applikation
anpassen. So verändert zum Beispiel app.set(‚title‘, ‚Seitentitel‘); den Namen der
Applikation auf ‚Seitentitel‘. Mit ‚view engine‘ setzen wir mit die Standard Engine
Dateiendung auf ‚server.view.html‘, wenn diese weggelassen wurde. Mit ‚views‘ geben
wir an in welchem Verzeichnis die Views der App abgelegt sind.
Back-End
Mongoose Modelle
Wir verwenden als Datenbank zwar MongoDB, jedoch tun wir dies nicht direkt, sondern
mit Hilfe von MongooseJS, welches einen Schema & Usability Wrapper für MongoDB
in NodeJS darstellt. Das heißt Mongoose wurde einerseits dafür geschaffen um uns
Arbeit abzunehmen und andererseits um Schemata verwenden zu können. Bei einem
Schema handelt es sich um ein Objekt, dass die Struktur eines Dokuments definiert,
welches in einer MongoDB Collection gespeichert wird. Das Schema ermöglicht die
Definition von Datentypen und Validierungen für jedes einzelne Datenobjekt.
Neben dem Schema kennt Mongoose noch zwei andere Typen:
Eine ‚Connection‘ ist dabei einfach ein Wrapper um die Datenbankverbindung.
29
Ein ‚Model‘ ist ein Objekt, welches den einfachen Zugriff auf eine Collection ermöglicht.
So kann mit dem ‚Model‘ die Collection abgefragt werden, bzw. jedes in der Collection
zu speichernde Dokument vor dem Speichern mit dem Schema validiert werden. Eine
Instanz eines ‚Model‘ wird in Mongoose als ‚Document‘ bezeichnet. (Mehr Infos über
Mongoose: hier)
Alle Mongoose Modelle unserer Applikation werden in App/models abgelegt. Diese
Modelle werden beim Laden der App automatisch registriert und können im Server-
Code einfach mit mongoose.model(‚modelName‘) aufgerufen und verwendet werden.
Bewerkstelligt wird das indem die Modelle per require() in der ‚config/express.js‘
eingebunden werden und das Express-Module wiederum in die Application-Main
‚server.js‘ eingebunden wird:
// Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
Express Routen
‚Routes‘ sind URIs, mit denen der Client beim Server HTTP-Requests durchführt. Das
heißt also, dass Routing sich darauf bezieht, wie sich die Server Applikation verhält,
wenn der Client einen Request mit einer bestimmten HTTP-Methode (GET, POST, …)
auf eine bestimmte URI (Pfad) durchführt.
Also handelt es sich bei Routen um genau das, was wir für die Implementierung eines
RESTFul Services benötigen. Wir geben Routen wie folgt an:
// This is used to sign up a user using username and password.
app.route('/auth/signup').post(users.signup);
Dabei ist ‘/auth/signup’ der Route-Pfad und ‚users.signup‘ die Callback-Funktion,
welche aufgerufen wird, sobald ein HTTP-POST Request mit der URI /auth/signup an
den Server gelangt.
Diese Routes werden im Verzeichnis App/routes abgelegt und werden automatisch mit
der Express-Applikation registriert. Damit ist eine Route sofort nach dem anlegen
verfügbar. Das Einbinden erfolgt dabei gleich wie bei den Mongoose Modellen in der
Konfigurationsdatei ‚config/express.js‘.
30
Sequenzdiagramm
Das nachfolgende Sequenzdiagramm soll noch einmal den Ablauf der User – Browser- WS1
Kommunikation veranschaulichen.
31
NoSQL Datenbank Design
Da wir es mit einer dokumentenbasierten Datenbank und nicht mit SQL (genauer gesagt mit
einem RDBMS) zu tun haben, müssen wir diesen Umstand natürlich auch beim Design der
Datenbank beachten.
Während eine relationale Datenbank Daten in Tabellen und Zeilen strukturiert, verwendet
MongoDB Collections von JSON-ähnlichen Dokumenten(BSON Dokumente), welche
wiederum aus Key-Value-Paaren bestehen. Eine Collection ist also eine Sammlung von
zusammengehörenden bzw. verwandten Dokumenten, die einen Satz von gleichen
Eigenschaften aufweisen (Ähnlich zu Tabellen in RDBMS).
Im Gegensatz zu Tabellenfeldern erlauben JSON-Keys nicht nur die Verwendung von
herkömmlichen Datentypen wie Number, String, Boolean, sondern auch Arrays und
Verschachtelung von komplexeren Objekten. Das bedeutet wiederum, dass wir keinen objekt-
relationalen Mapper zwischen Applikation und Datenbank benötigen und die Datenobjekte der
Applikation einfach persistieren können.
Queries
Eine MongoDB Abfrage (db.collection.find) bezieht sich immer auf eine spezifische Document-
Collection, wobei in der Abfrage definiert wird mit Hilfe welcher Kriterien die Dokumente
identifiziert werden. Optional enthält die Abfrage auch noch eine Projection, mit der die
Datenmenge (also die Key-Value-Paare) innerhalb der Dokumente, die MongoDB zurückgibt,
limitiert werden kann und einen Cursor-Modifier, mit denen Limits, Sortierung, … angegeben
werden kann. Hier zwei Beispiele:
Links das Beispiel ohne Projektion und rechts mit ihr.
32
Mit Projections kann man entweder gezielt Felder
inkludieren
db.records.find( { "user_id": { $lt: 42 } }, { "name": 1, "email": 1 } )
oder
exkludieren
db.records.find( { "user_id": { $lt: 42 } }, { "history": 0 } )
aber nicht beides gleichzeitig (Ausnahme beim _id-Feld). Das _id Feld wird per Default immer
zurückgeliefert, wenn es nicht explizit exkludiert wird.
Bei Feldern, die Arrays enthalten müssen die Projektionsoperatoren $elemMatch, $slice und
$ verwendet werden.
Datenmodifizierung
Das Erstellen, ändern oder löschen von Daten bezieht sich in MongoDB auf eine einzige
Collection. Hier ein Beispiel für die Erstellung (.insert) eines neuen Dokuments in der „user“
Collection:
Dies war nur ein kleiner Einblick in die Welt von MongoDB. Mehr Informationen (gut
aufbereitet) sind hier zu finden: http://docs.mongodb.org/manual/
Da JSON-Dokumente menschen-lesbar und sehr leichtgewichtig sind, können wir beim Design
gleich valide JSON-Objekte formen. Diese teilen sich bei uns in
Office
User
Vehicle
RentingContract
auf und leiten sich direkt aus den Anforderungen von Kapitel 2.1 ab:
33
“user” Document
{
"_id" : ObjectId("528ba7691738025d11aa135a"),
"fistName" : "Hermann",
"lastName" : "Wagner",
"displayName" : "hw90",
"email" : "[email protected]",
"username" : "hw90",
"password" : "ASDFH123SADF1586A5Z27ADCB56",
"salt" : "sadfkzwerxcv",
"roles" : ["admin", "user"],
"provider" : "facebook",
"updated" : "09122014",
"created" : "01122014",
"resetPasswordToken" : "",
"resetPasswordExpires" : "01102015",
}
“vehicle” Document
{
"_id" : ObjectId("528ba7691738025d11aab772"),
"manufacturer" : "Porsche",
"model" : "550 Spyder",
"booked" : "false",
"buildYear" : "2010",
"mileage" : "78012",
"horsepower" : "150",
"pricePerDay" : "50",
"licencePlate" : "W-ABC1234"
}
„rentingContract“ Document
{
"_id" : ObjectId("528ba7691738025d11aabea21"),
"pickUpOffice" : "<ObjectID123124>",
"returnOffice" : "<ObjectID123176>",
"renter" : "<ObjectID1124354>",
"pickUpDate" : ISODate("2014-12-01"),
"pickUpTime" : "10:24",
"returnDate" : ISODate("2014-12-05"),
"returnTime" : "10:24",
"vehicle" : "<ObjectID1124444>"
}
34
Authentisierung und Autorisierung
Passieren beim Design oder der Kodierung der Authentisierung und
Autorisierung einer Web Applikation Fehler, so haben diese meist
schwerwiegende Auswirkungen sowohl für die Registrierten Benutzer als auch
für die Betreiber der Webanwendung. Denn werden Benutzerdaten gestohlen
oder manipuliert, so führt das nicht nur zu einer mehr als schlechten Publicity und den Verlust
von Kunden beim Betreiber, sondern es kann auch zu Identitätsdiebstahl führen, bei
Benutzern, die dieselben Credentials auf anderen Webseiten verwenden.
Um solche Fehler zu verhindern und schnell eine sichere und zuverlässige Authentisierung
und Autorisierung zu bieten gibt es daher für Node.js das Modul Passport. Dieses ist eine
modular aufgebaute Authentisierungs ‚Middleware‘ für NodeJS Applikationen, die ohne
weiteres ganz einfach in Express-basierten Applikationen integriert werden kann. Passport
bietet dabei verschiedene Passwortstrategien, wie zum Beispiel Authentifizierung über
Benutzername + Passwort ( ‚local‘) oder aber auch Single sign-on über OpenID oder OAuth.
Dadurch ist es auch möglich sich über Facebook, Twitter, Google, LinkedIn, Github, usw. in
die App einzuloggen. Für den Web Services Login bietet Passport auch die Möglichkeit von
Token-basierten Credentials (Tokens sind im Prinzip dasselbe wie Cookies, wobei Cookies nur im
Browser verfügbar sind und sehr oft für andere Zwecke verwendet werden, die nicht mehr REST-
konform sind. Auch CSRF Angriffe sind bei Cookies möglich, da der Browser Cookies bei jedem
Request mitsendet. Auf jeden Fall werden sowohl Tokens als auch Cookies als „mehr RESTful“ als
Sessions angesehen, da der Server keine Informationen speichern muss. Eine aufschlussreiche
Diskussion zu diesem Thema ist hier zu finden).
Um das zu bewerkstelligen verwendet Passport sogenannte „Passport Strategien“, mit denen
die Module für die einzelnen Provider konfiguriert werden können. Diese Strategien sind im
Ordner /config/strategies zu finden und werden vom YEOMAN MEAN.JS Generator schon
automatisch für die wichtigsten Provider (facebook, google, twitter, linkedin, twitter & local)
angelegt. Außerdem werden diese wieder automatisch in die Express App eingebunden. Das
wird im /config/passport.js File erledigt, welches per require() in die server.js – Main
eingebunden und gleich ausgeführt wird:
// Initialize strategies
config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) {
require(path.resolve(strategy))();
});
Administrator in MongoDB Datenbank anlegen
User Web Service bietet nur einem Administrator die Möglichkeit einen weiteren Administrator
anzulegen. Somit haben wir ein „Henne-Ei-Problem“, da es uns über das WS nicht möglich ist
einen Admin anzulegen ohne sich bereits als Admin eingeloggt zu haben.
35
Lösung
Der erste Administrator muss per „Installation“ angelegt werden. Dafür dient die Route: /install.
Das heißt, beim Deployment der App auf einem neuen Server mit einer neuen Datenbank
muss zuerst die Seite mit /install angesurft werden und ein Administrator angelegt werden (zB.
localhost:3000/install).
Diese Möglichkeit ist natürlich sehr gefährlich, da jeder beliebige sich so einen Administrator
Account anlegen könnte. Deshalb müssen nach dem Anlegen des initialen Admins sofort
folgende Dateien gelöscht werden:
/app/views/install.server.view.html
/app/routes/deleteme.server.routes.js
/app/controllers/install.server.controller.js
Dabei muss unbedingt die deleteme.server.routes.js gelöscht werden. Da löschen der beiden
anderen Dateien reicht nicht aus, da dadurch nur die GUI entfernt wird.
Der neu angelegte Administrator kann nun weitere Administratoren und Employees erstellen.
Aufbau der Client AngularJS Applikation
Sehen wir uns nun den Aufbau der Client Applikation, also im Endeffekt den Ordner /public,
einmal näher an. Im Kapitel „Projekt-Dateistruktur“ wurde bereits auf den prinzipiellen Aufbau
des /public Ordner eingegangen und erläutert, dass wir unser Architektur vertikal ausgelegt
haben. Hier noch einmal eine kurze Wiederholung:
public
Enthält alle statischen Dateien der Applikation. Hier werden die Front-End Dateien (in
unserem Fall Angular.js) gespeichert.
o dist
Enthält die komprimierte Front-End Applikation (application.min.js und
application.min.css). Dieser Ordner wird automatisch durch die Grunt Tasks uglify,
cssmin und build erstellt. Die Komprimierung hat den Hintergrund, dass die an den
Client übertragenen Dateien natürlich möglichst klein sein sollen. Zusätzlich
befindet sich in diesem Ordner auch noch die Datei application.js, die den nicht-
komprimierten JavaScript Code des Front-Ends gesammelt enthält und beim grunt
build automatisch erstellt wird.
o modules
Enthält den Client Code in Form von Angular.js Modulen. Wichtig ist dabei zu
erwähnen, dass unsere Angular.js SPA in Form von horizontalen Modulen
gegliedert ist.
36
o lib
Enthält alle Front-End Bibliotheken, wie Angular.js, jQuery, Bootstrap, usw.
o config.js
Angular.js Konfigurationsmodule
o application.js
Angular.js Einstiegsdatei. Übernimmt das Bootstrapping der Applikation und das
einbinden der richtigen Module.
Daraus geht hervor, dass für die Entwicklung vor allem das Verzeichnis /public/modules
wichtig ist, da es ja fast den gesamten Front-End Code enthält. Daher sehen wir uns diese
nun genauer an.
Die Autoverleih Applikation hat folgende drei Module:
core – Enthält die Startseite, den Seitenheader und alle Seiten, die mit Autoverleih
spezifischen Dingen zu tun haben (Auto anlegen, Auto ändern, …)
users – Enthält alle Seiten, die mit benutzerspezifischen Dingen zu tun haben (Login,
Registrieren, Passwort ändern, Benutzerdaten ändern, …)
Dabei enthält jedes der Module folgende Verzeichnisse:
config – Enthält zum einen die Datei *.client.config.js mit verschiedenen
Einstellungen für das Modul und zum anderen die Datei *.client.routes.js, die sich
ums Client-seitige Routing der Single-Page-Application kümmert. Wird zum Beispiel
die Route /signin aufgerufen, so wird das View:
modules/users/views/authentication/signin.client.view.html geladen.
controllers – Enthält die Controller des MVC-Models
css – Enthält die Stylesheets für die Templates/Views
img – Enthält Abbildungen, die in den Templates/Views dargestellt werden
services – Enthält den Backend-Code
tests – Enthält Modultests für die Controller und Services
views – Enthält die Templates aus denen die Views generiert werden. Diese Views
stellen das GUI dar, welches der Benutzer zu Gesicht bekommt
37
und zusätzlich jeweils eine Datei *.client.module.js. Diese macht nichts anderes als das
jeweilige Module im Hauptmodul zu registrieren (Dependency Injection).
Kommunikation zwischen WS1 und WS2
Die Kommunikation zwischen WS1 und WS2 läuft für die Applikation transparent ab. Für die
Applikation stellt das WS1 die Währungsumrechnung zur Verfügung. Im Hintergrund holt sich
das WS1 jedoch die Wechselkurse und die Umrechnungsergebnisse selbst vom WS2 über
SOAP. Dazu muss am WS1 ein SOAP Client implementiert werden, der die in der WSDL
beschriebenen Schnittstellen des WS2 verwendet.
Den SOAP Client für unser NodeJS WS1 haben wir mit Hilfe des NPM Moduls soap
implementiert, welches sowohl SOAP Client als auch Server Funktionalitäten für NodeJS zur
Verfügung stellt. (Alternative: https://github.com/jmoyers/soapjs)
http://localhost:8080/autoverleih_ws2/CurrencyConverter?WSDL
38
3.2 WS2 – Währungsrechner
Für das Webservice des Currency-Converters wurde die Technologie JAVA gewählt.
Entwickelt wurde es in der Netbeans IDE.
Die Schnittstellen/Medthoden des Currency Converters sind nachfolgend aufgelistet.
Erstellt wird ein Webservice unter Netbeans folgendermaßen:
1. Java Web Application Project erstellen
2. Rechte Maustaste New Webservice
3. Danach über den Button "Add Operation" die Operationen hinzufügen (s. Abbildung
oben)
(Name, return Type, Parameters, Exceptions)
4. Daraufhin wird von Netbeans im Quelltext die Funktion mit allen notwendigen
Parametern und Kommentaren erstellt.
5. In diesen fügt man jetzt seine Programmlogik ein.
6. Anschließend klickt man mit der rechten Maustaste beim Webservice auf "Generate
and Copy WSDL" und Netbeans generiert aus diesem Webservice das zugehörige
WSDL-File.
Testen kann man das Webservice, in dem man auf "Run Project" und anschließend auf "Test
Webservice" klickt. Netbeans erzeugt dann automatisch eine einfache HTML-Seite, wo man
die Funktionen mit Parameterübergabe testen kann.
39
3.2.1 getAllCurrencies
gibt eine Liste (String Array) aller verfügbaren Währungen zurück.
Dabei wird beim Funktionsaufruf das Dokument der europäischen Zentralbank geladen und
mittels XPath anhand folgenden Ausdrucks "//Cube[@currency]" nach allen verfügbaren
Währungen durchsucht, also allen Nodes, die "Cube" heißen und das Attribut "currency"
besitzen.
XPath steht für XML Path Language, und ist eine Abfragesprache, um Teile eines XML-
Dokumentes zu adressieren bzw. auszuwerten.
Die dadurch erhaltenen Währungen werden anschließend in eine String-Liste gepackt und
zurückgegeben.
Parameter
Keine
3.2.2 getCurrencyRate
gibt den aktuellen Umrechungskurs der mitgegebenen Währung zurück.
Auch hier wird das Dokument der EZB geladen und mittels XPath durchsucht. Dabei wird
folgende Expression verwendet:" //Cube[@currency='Währung']/@rate". Wobei Währung
als Parameter mitgegeben wird. Das Ergebnis dieser Suche ist eindeutig, da nur nach einem
Node mit dem Namen "Cube" und den zwei Attributen "Currency" und "Rate" gesucht wird,
wobei eben Currency als Parameter mitgegeben wird. Das "/@rate" bewirkt, dass der Wert
vom Attribut Rate nur ermittelt wird.
Dieser wird dann als double geparsed und zurückgegeben.
Parameter
String Währung
3.2.3 getChange
rechnet das Wechselgeld zwischen zwei Währungen aus. Dabei wird auch die Funktion
"getCurrencyRate" verwendet.
Sie besteht aus 4 IF Funktionen.
Die erste überprüft ob Währung 1 gleich Währung 2 ist. Wenn ja, gibt sie den mitgegebenen
Wert einfach wieder zurück.
Die zweite und dritte überprüfen ob in EUR oder von EUR umgewandelt werden soll. Je
nachdem wird die mitgegebene Menge durch den Wechselkurs der Fremdwährung
multipliziert oder dividiert.
40
Die letzte IF-Anweisung dient zur sogenannten Cross-Rates Berechnung. D.h. dass von
einer Fremdwährung in eine andere umgewandelt werden kann über den Euro-Kurs.
Parameter
String Währung 1, String Währung 2, Double Wert der Währung 1
41
3.3 WS3 – Google Maps Webservice
WS3 wurde nicht von uns implementiert, sondern nur in der Client Applikation verwendet. Dazu
wurde einerseit die „Google Maps JavaScript API Version 3“ und andererseits das AngularJS
Modul angular-google-maps verwendet, welches bereits Angular-Direktiven für die
Verwendung des Maps Webservices zur Verfügung stellt und somit die Verwendung des Web
Services eigentlich nur mehr zu einer Konfigurationsaufgabe wird.
Details zur Verwendung von Google Maps Webservices sind hier zu finden:
https://developers.google.com/maps/documentation/webservices/
http://angular-ui.github.io/angular-google-maps/#!/