41
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

SEMINARARBEIT - Lean Startup Security · Das Back-End braucht das Front-End nicht mehr dynamisch generieren (zb. PHP, JSP, …), was zu einer Entkoppelung von Back- und Front- End

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/#!/