Übersicht
Dieser Artikel soll zeigen, dass es recht einfach ist einen Rest Service aufzusetzen, über den dann die Daten für die Typescript Webseite herangezogen werden können. Die Umsetzung umfasst die Anbindung von Typescript an den Web-Service und die Implementierung einer Java Rest Schnittstelle. Der Web-Service liefert die Daten, gemäß dem vorherigen Blog.
Der Start bildet die Anbindung des Services in Typescript.
Für künftige Serverabfragen wird der HttpClient benötigt. Er wird per Dependency Injection dem Action-Service übergeben.

Damit der HttpClient verwendet werden kann muss er noch als Provider in der app.module.ts hinterlegt werden:

Der Zugriff selbst auf die Komponente vom HttpClient erfolgt über den Konstruktor-Parameter (hier) im ActionService.

Diesmal soll unsere Button-Liste mit Daten vom Service beliefert werden. Ein mit dem HttpClient gesendete Anfrage liefert zunächst ein Observable-Handler Objekt zurück. Wir benötigen hier aber noch eine Konvertierung zwischen dem, was wir erhalten vom Server und dem was wir brauchen für unser Frontend. Diesen Zwischenschritt bearbeite ich hier mit einem Promise. Die Klasse app.component.ts erwartet ein Promise mit dem Ergebnistyp NextAction.
In diesem Beispiel habe ich eine Serverschnittstelle definiert, wie sie von meinem künftigen Service angesprochen werden soll. Mittels template-String werden die variablen PfadVariablen in die statischen Teile der URL eingepflegt. Die Service Schnittstelle erwartet, dass dies ein GET Aufruf ist. Würde man als post, put oder sonstiger http-Methode versuchen die URL aufzurufen, würde dies zu einem Fehler führen. In dem Beispiel werte ich ein positives Ergebnis vom Service-Endpunkt mit then-Handler aus. Das gelieferte Datenmodell sieht wie folgt aus:

Kurze Erklärung
Title ist der Text der durch die Aktion ausgegeben werden soll.
Type verbirgt den Button-Text. In diesem Beispiel soll er vom Action-Service spezifiziert werden.
HasNext ziegt, ob weitere Actions zur Verfügung stehen.
ActionId ist eine UUID, die identifiziert zu welcher Abfrage die asynchrone Abfrage erfolgt ist, um zu entscheiden, ob ein Ergebnis akzeptiert und angezeigt werden darf.
Rand-Info: UUID ist importiert:

Dagegen erwartet app.component.ts vom Service den Typ NextAction:

Wie man sieht: relativ ähnlich, nur dass caption der Text der Schaltfläche ist und Title per onClick EventHandler definiert ist.

Der Converter bestimmt, was bei Klick „onClick“ passieren soll und gibt vor, welchen Text die Schaltfläche aufweisen soll.

Die Werte für type stammen alle vom Service. Noch wissen wir nicht, was ein Klick-Ereignis anzeigen wird. Um die Struktur für das Service-Ergebnis zu definieren wurde JSON.stringify(..) verwendet. Via console.log(..) wurden die Werte angezeigt:

Auf der Webseite (im HTML) wurden alle Checkboxen entfernt, denn nun erfolgen alle Abfragen immer asynchron. Alles andere bleibt beim Alten.

In der Klasse app.component.ts ist das Feld für die Aktionen hinterlegt als ActionModel-Array im Feld mainActions. Das heißt auch dort ist noch eine Konvertierung nötig.
onRunRequest ist der Click-Handler für den einzigen Button im Frontend. Zur Erinnerung
Mit einem Klick wird jedesmal eine neue Abfrage-Runde gestartet, die Aktion-Liste wird geleert, bzw. durch ein leeres Array ersetzt und es wird also immer eine neue aktive ActionId erstellt.

Inhalt
Weiterhin verwenden wir, wie im letzten Blog den Service, um die Daten Schritt für Schritt zu erhalten. Zur Erinnerung: Es ist in diesem Beispiel gewünscht, jede Aktion nach einander abzurufen.
Per then-Handler erhalten wir nun vom app.service.ts NextAction als Ergebnis, oder einen Fehler per catch-Handler. Im then-Handler wird zunächst geprüft, ob die NextAction noch zur aktiven Abfrage gehört über die ActionId. Ist das nicht der Fall wird eine Meldung ausgegeben. Der neue Wert wird der in this.mainActions gepusht (hinzugefügt), jedoch nur mit den Informationen, die die Schaltfläche benötigt „Caption“ und „onClick“-Handler, den der ActionService vorgibt.

Wie in Blog 1 auch, wird nun geprüft, ob ein weiteres NextAction möglich ist und es wird abgerufen. Hierbei wird nun die neue Größe der Liste als Parameter und die aktive ActionId an action.service.ts übergeben. Das Ganze wiederholt sich asynchron solange, bis der Server sagt, es gibt kein nächstes Objekt mehr.
Java SpringBootApplication mit Rest-Service (Rest-Controller)
Mein Service soll eine SpringBoot-Anwendung sein. Das lässt sich am einfachsten mit Maven realisieren. Ein neues Maven-Projekt, ohne Architype ausgewählt und erstellt. Dies sind die Schritte
- Maven-Projekt erstellen
- Pom.xml um SpringBoot, log4J und Apache Commons 3 ergänzen
- Einen ExceptionHandler erstellen
- Einen RestController erstellen
- Einen ServiceHandler erstellen
- Modelle für den Datenaustausch erstellen
- Die ServiceApp mit Konfiguration erstellen
- Die Business-Logik entwickeln
Das Java-Maven-SpringBoot-Projekt:

Ergänzt wurden die Dateien und Klassen;
- Resources/application.properties mit server.port=8089
- Resources/log4j.properties
- Java/webapp/index.html – Einfache Webseite, Inhalt
- Java/webapp/WEB-INF
- Das package java/de.devrain.demo.service mit dem Inhalt
- ServiceApp (MainClass)
- ServiceConfig – für den Rest Service
- ActionServiceRestController – empfängt Anfragen und sendet Antworten
- ActionServiceExceptionHandler – für die zu sendenden Antworten vom RestService, wenn irgendein unerwarteter Fehler der gesamten Service-Applikation auftritt
Zunächst zur pom.xml. Sie gehört zu Maven und beinhaltet sämtliche modul-spezifischen Angaben.
In die pom wurden die notwendigen Abhängigkeiten aufgenommen:

Sowie Abhängigkeiten:

Sowie folgende Plugins:

Um einen Java-RestService zu starten bedarf es der Main-Class.
Hier ist es die SpringBootApplication. Spring ist Annotation-basiert.
Annotationen werden in anderen Programmiersprachen unterschiedlich betitelt. In Typescript heißen sie Decorator in C# sind es Attributes und in Java heißen sie Annotations. Aber sie werden gleichermaßen verwendet. Sie markieren ein Symbol, wie eine Klasse, Methode oder Feld.
Eine Technologie wie Spring analysiert diese Markierungen und konfiguriert entsprechende Tools, um spezifizierte Aufgaben zu erfüllen, wie zum Beispiel einen Rest-Service.
Die MainClass für eine SpringBootApplication muss in einem Package liegen. Die MainClass wird mit der Annotation @SpringBootApplication markiert und erhält den Einstiegspunkt der Anwendung:

Damit ist die SpringBootApplication lauffähig, aber es gibt noch keinen Service, der Web-Anfragen verarbeiten kann. Das wird in einer Klasse erledigt, die per @RestController -Annotation markiert ist. Eine SpringBoot-Applikation, kann beliebig viele RestController aufweisen, die unterschiedliche Aufgaben erfüllen. So lassen sich Zuständigkeiten sauber von einander trennen.
Für dieses Beispiel ist nur ein einiziger Service nötig. Daher kann man ihn als Micro-Service bezeichnen.
Ein Micro-Service kann im Bedarfsfall auf einen Rechner mit besseren System-Ressourcen umziehen, ohne den übrigen Betrieb der Anwendung zu behindern (Skalierbarkeit).
Die Bereitstellung eines Rechners mit mehr System-Resourcen geht zumeist einher mit höheren Bereitstellungskosten. Durch die Verwendung einer Micro-Service-Architektur lassen sich einzelne nur benötigte Services skalisieren, so dass keine höheren Kosten für alle Services zu erwarten sind. Die Anwendung bleibt kostengünstiger. Zudem verringert es die Ausfallzeiten der gesamten Applikation. Teile der Anwendung (einzelne Services) können separat neu gestartet werden, was die Startzeiten allgemein reduziert.
Unser Rest-Service soll für einen bestimmten Port arbeiten, nämlich Port 8089. Daher wird dieser Wert in der Ressourcen-Datei application.properties als Schlüssel server.port hinterlegt.
Der Rest-Service soll von der Klasse MainClass/SpringBootApplication identifiziert werden und nur die Aktion für die Webseite antworten, die zum Index passt.


Die Service-Methode “getNextAtIndex” empfängt einen Web-Abfrage, wenn es sich um eine GET-Abfrage handelt und an den korrekten Pfad gesendet wird. Dabei wird der Hauptpfad (Root) vom @RestController aus der Angabe im @RequestMapping verwendet, sowie der Pfad in der Annotation @GetMapping des Service-Endpunktes. Angeführt vom Host, wo der Service läuft und mit der Portangabe auf der der Dienst/Service gestartet wurde. Wir arbeiten lokal also ist dies der localhost (oder IP 127.0.0.1) geführt vom Web-Protokoll http.
Somit ergibt sich die URL:
http://localhost:8089/action/v1/{locale}/next/{index}/id{actionId}
Erläuterung
protokoll+host+port = http://localhost:8089
root-url = /action/v1
request-url = /{locale}/next/{index}/id{actionId}
Die Werte in den geschweiften Klammern sind Pfadvariablen. Diese enthalten einen Namen und können bei Bedarf eine Regex-Maske aufnehmen. Hier sind zurzeit keine weiteren Spezifikationen definiert.
Ist eine URL Eingabe eine Zahl und wird im Methodenparameter zu der entsprechenden Pfadvariable eine Zahl, wie eine Ganzzahl erwartet, so ist es möglich den Typ des Methodenparameters entsprechend als int (also Ganzzahl / Integer) anzugeben. Würde ein Aufrufer an der erwarteten Stelle keine Ganzzahl eingeben, so erhielte er eine Fehlermeldung.
Der Serviceendpunkt in der Web-Service-API liefert die bekannte Action als ActionEdo.
Edo steht für Exchange Data Object (Datenaustauschobjekt). Die Endung „Edo“ wurde ergänzt, um für den Entwickler deutlich zu machen, dass Änderungen an dem Datenmodel Auswirkungen auf andere (externe) Systeme haben werden, was wiederum zu Fehlern führen würde. Edos werden für gewöhnlich nicht oft geändert, um die Prozessbehinderung sehr gering zu halten.
In diesem Beispiel haben wir nur das Antwort Edo „ActionEdo“:

Dagegen halte ich nun das Model, wie es in Typescript verwendet wird:

Man sieht, dass es genauso aussehen muss. Allerdings unterscheiden sich die Typen der Enumeration. Prinzipiell sind alle Werte, die übertragen werden zwischen Service und Konsument Strings, also Texte. Aber bei Typengleichheit kann auch dieser verwendet werden. In dem Edo werden Json Annotationen verwendet, die für das Format (den MediaType des Bodys) wichtig sind.
Daten zwischen Service und Konsument sind primär binär, dennoch können die Daten auch als Texte übertragen werden. Vorteil ist, dass der Inhalt der verschickten Daten lesbar bleibt. Bei einer solchen Übertragung ist allerdings im Produktivsystem eine verschlüsselte Kommunikation entscheidend, wenn es um sensible Personendaten geht. Diesen Aspekt habe ich allerdings ausgelassen in diesem Beispiel.

Der markierte Bereich zeigt den Aufruf über die Businesslogik. Diese Zwischenschicht ist nicht nötig, aber sie trennt den Logikteil der Web-Abfrage vom technischen Teil. Der RestController verarbeitet nur den empfangenen und gesendeten Informationsanteil.
Die Businesslogik wurde in dem Beispiel per Dependency Injection, durch Spring, dem RestController (Service) beigegeben. Spring verwendet dazu ein Interface zur Businesslogik:


Es gibt wie in Blog 1 noch keinen Datenbankzugang. Unsere Datenbank ist vorerst noch eine Liste. Doch die Liste ist nun etwas komplexer. Es ist beabsichtigt die Ausgabetexte in verschiedenen Sprachen an den Konsumenten zurückzuliefern. Das sollte nämlich nicht die Aufgabe des Konsumenten sein. Wir hinterlegen für jede Aktion einen mehrsprachfähigen Text.
Es gibt eine Hilfsmethode, die eine Liste von Einträgen in Form einer Liste in ein Wörterbuch übersetzt. In Java nennt sich dies Map und in C# Dictionary.

Hier befindet sich nun die Businesslogik, sowie die Datenschicht der Anwendung. Sie unterscheidet sich nicht sonderbar von der Logik aus Blog 1.
In einer Datenquelle wird per Index die Aktion gesucht und erweitert um den Boolean-Wert, der angibt, ob es eine weitere Aktion gibt.
Action ist das Datenmodel, wie es verwendet werden soll in einer späteren Datenbank:

ContinousAction ist das Businessmodel, wie ist für die Businesslogik verwendet werden soll:

Nachdem die ContinousAction ermittelt wurde, wird sie an den RestController zurückgeliefert. Dieser gibt noch die aktuelle ActionId bei und mappt dann in das Edo Modell zurück und sendet die Antwort an den Konsumenten, mit dem Status Code ACCEPTED, bzw. 202. Um zu definieren, dass unser Datenübertragungsformat Json Utf-8 ist, muss dies in produces hinterlegt sein. Sowie die Annotation @ResponseBody

Der Mapper ist ein Helfer, um die internen Daten in die Edo Form zu bringen.

Zuletzt wird noch der ExceptionHandler aktiv, wenn zu einem Index keine Aktion existiert:

Damit ist der Kreislauf vollständig. Die Webseite ruft nun vom Rest-Service die Aktionen nacheinander ab und zeigt diese asynchron an.


Zusammenfassung
- Typescript stellt eine Anfrage mit Sprache, Index und ActionId an den WebService.
- Der Webservice fragt den Datensatz per Index und Sprache vom ServiceHandler ab und liefert die angefragte Aktion an den Konsumenten (hier: Typescript) zurück.
- Typescript verarbeitet die Antwort und zeigt die Aktionen als Schaltflächen im Frontend an.

Schwierigkeiten & Lösung
Unbekannte Anfragequellen
Als der Web-Service fertig war, konnten keine Anfrage an ihn akzeptiert werden, da diese nicht konfiguriert war. Es handelte sich um den CORS Fehler
No ‘Access-Control-Allow-Origin’ header is present
Die Lösung musste im WebService erfolgen.

An der Config-Klasse wurde ein bestimmtes Interface implementiert, so dass sämtliche Abfragequellen erlaubt sind /**