Kleinteiliges aus der Wolke: Bereitstellen von Geschäftslogik via REST und dem RAD Server
Wenn Sie als Entwickler schon länger im Geschäft sind, dann werden Sie es bereits bemerkt haben: Die Art und Weise der Softwareentwicklung befindet sich wieder einmal vor einem größeren Umbruch. Statt komplexer und umfassender Desktop-Applikationen sind heute schlanke Apps angesagt, welche idealerweise auf allen Zielplattformen lauffähig sind. Dazu lagert man die Geschäftslogik in spezialisierte Dienste aus. Statt auf eine unflexible monolithische Anwendungsstruktur setzt man auf eine Verteilung der Aufgaben. Dieser Ansatz wird treffenderweise als Microservices bezeichnet. Einen effektiven Weg, um Businesslogik für alle Arten von Clients über REST bereitzustellen, ist die Verwendung des RAD Servers. Programmiert wird in diesem Fall in RAD Studio, einer komfortablen, integrierten Entwicklungsumgebung.
Veränderungen in der IT gehören zwar schon immer dazu. Dennoch beschreibt das Intro eine sich seit längerem vollziehende Anpassung der Art und Weise, wie Architekturen von Softwaresystemen heute aufgebaut sind. Oft wird auch von einem Paradigmenwechsel gesprochen. Unseres Erachtens ist diese Aussage aufgrund des Ausmaßes der Änderungen auch durchaus berechtigt. In diesem Artikel wird die Entwicklung der Anwendungsarchitekturen anhand wichtiger Meilensteine konzeptionell nachvollzogen. Am Ende steht die Beschreibung der heute üblichen verteilten Architektur von Softwaresystemen, welche auf eine Aufteilung der Geschäftslogik auf kleine verteilte und über das Internet erreichbare Einheiten setzt. Neben der Entwicklung der Software für den Client rückt damit auch die Bereitstellung der Services in den Fokus des Entwicklers. Ein interessanter und gleichermaßen effektiver Lösungsansatz auf der Basis des RAD Servers und der integrierten Entwicklungsumgebung RAD Studio wird ebenso vorgestellt.
Softwarearchitekturen im Wandel
Den Wandel in den Softwarearchitekturen haben wir im Intro bereits angedeutet. Dabei kann man die wesentlichen Schritte anhand der folgenden Meilensteine kompakt beschreiben. Anwendungssysteme, welche gewissermaßen keine explizite Trennung in unterschiedliche Teilsysteme aufweisen, bezeichnet man als monolithische IT-Systeme. Die typischen Funktionen eines solchen Anwendungssystems – also Datenhaltung, Anwendungslogik und Benutzeroberfläche – sind in diesem Fall eng gekoppelt und weitreichend zu einem komplexen Gesamtsystem miteinander verschmolzen (s. Abb. 1).
Mit einer solchen Architektur kann man i. d. R. nicht mehr die heutigen Anforderungen an ein flexibles Softwaresystem erfüllen. Das gesamte System stellt sich als ein großer und kaum zu durchschauender Block dar. Ein solches IT-System führt zu folgenden Nachteilen:
- Hohe Komplexität: Das Softwaresystem ist komplex. Die fehlende Aufteilung in Einzelmodule führt dazu, dass diese nicht reduziert wird.
- Schlechte Wartbarkeit: Die Wartung und Fehlerbereinigung stellen sich als sehr schwerfällig dar. Es gelingt nicht, einzelne Teile der Anwendung gegen neuere Technologien auszutauschen, ohne dass dies direkte Einflüsse auf andere Bestandteile der Software hat. Anpassungen an einer Stelle im Quellcode lösen gewissermaßen eine Kette von notwendigen Folgeanpassungen aus.
- Schwierige Wiederverwendung: Die Wiederverwendung einzelner Softwarekomponenten ist bei einem solchen komplizierten monolithischen Gesamtsystem ausgeschlossen. Entwickler können eine einmal erarbeitete Lösung für eine ähnliche Problemstellung nicht wiederverwenden, sondern müssen jedes Mal von vorn beginnen.
- Inflexibilität: Eine Erweiterbarkeit und eine Skalierbarkeit sind stark eingeschränkt.
- Systemgebunden: Nutzer wollen heute auf unterschiedlichsten Geräten, vom Smartphone bis zum Desktop und den verschiedensten Betriebssystemen ihrer Wahl arbeiten. Die Bindung an eine Systemumgebung auf Clientseite schränkt diese Wahlmöglichkeit stark ein.
Monolithische IT-Systeme sind heute nicht mehr Stand der Technik. Oft sind diese automatisch auf dem Wege der Implementierung irgendwie entstanden. Dennoch sind noch viele solcher Systeme im Einsatz. Sie werden als Altsysteme bzw. Legacy-Systeme bezeichnet. Ihre Modernisierung und Anpassung ist eine der schwierigsten und komplexesten Aufgaben der heutigen Softwareentwicklung. Trotzdem muss es im Sinne des Investitionsschutzes das Ziel sein, bei der Migration dieser Systeme eine möglichst große Quellcodebasis zu erhalten und erfolgreich zu transformieren. Besser ist eine Aufteilung der Gesamtapplikation in Schichten, üblicherweise für die Datenhaltung, die Verarbeitung und die Präsentation (Abb. 2).
Der Einsatz von Web-Applikationen und die Verwendung von Apps für mobile Geräte zwingen gewissermaßen zu einer Aufteilung der Aufgaben des Systems. Aufgrund der Beschränkung in den Ressourcen müssen große Teile der Datenbereitstellung und Businesslogik an Dienste ausgelagert werden.
Kommen wir zu Microservices. Eine eindeutige Definition gibt es nicht. Wird ein Anwendungssystem nach diesen Prinzipien aufgebaut, dann geht man von einer sehr breiten und tiefen Modularisierung über alle Komponenten des Softwaresystems aus. Die einzelnen Module werden einzeln und unabhängig voneinander in Produktion gebracht und weiterentwickelt. Die Services können dabei flexibel durch unterschiedlichste Anwendungssysteme, welche nicht nur auf das eigene Unternehmen beschränkt sind, benutzt werden. Die Integration erfolgt üblicherweise über RESTful http, d. h. über eine einheitliche Schnittstelle. Die eigene Anwendung wird damit gewissermaßen zum Service, welcher sich wiederum aus der Nutzung anderer Dienste zusammensetzt (Abb. 3).
Für die Nutzung eines Service spielt dessen technische Umsetzung keine Rolle. Unterschiedliche Betriebssysteme und die Bereitstellung auf verschiedenen Technologien sind nicht hinderlich. Einzelne Services können ausgetauscht werden oder werden unabhängig vom Gesamtsystem aktualisiert. Sofern man die Services von Drittanbietern nutzt, gibt man gewissermaßen die gesamte Verantwortung für den Betrieb und die weitere Entwicklung weiter. Sofern sich die Schnittstelle nicht ändert, haben Anpassungen am Dienst keine Auswirkungen auf die nutzende Software. Ist man nicht mehr mit den Leistungen des Dienstes zufrieden, kann man diesen gegen einen anderen tauschen. Anpassungen sind nur an den Stellen im Quelltext notwendig, welche mit dem Dienst kommunizieren.
REST als Basis der Kommunikation
Werden Client und Server über Netzwerke (Internet) miteinander vermittelt, dann basiert die Nutzung des entfernten API meist auf der Basis von REST. REST hat sich dafür als Standard etabliert und steht als Abkürzung für REpresentational State Transfer. Schnittstellen (API), welche auf REST setzen, werden im Entwickler-Jargon auch gern als "RESTful" bezeichnet. Derartige Implementierungen setzen wiederum auf standardisierte Verfahren, wie HTTP/S, URI, JSON oder XML. REST basiert auf folgenden wichtigen Prinzipien [1]:
- Client-Server-Modell: Die Kommunikation erfolgt auf der Basis eines Client-Server-Modells. Das Ziel ist eine flexible und generische Nutzung der Services über Plattformgrenzen hinweg.
- Zustandslosigkeit: Die Kommunikation zwischen Client und Server ist stets zustandslos. Jede Anfrage vom Client an den Server muss vollständig sein. Der Server kann auf keine vorherige Anfrage zurückgreifen. Die Vorteile sind eine hohe Zuverlässigkeit und eine einfache Skalierbarkeit. Als Nachteil ergibt sich eine höhere Netzbelastung, da jede Anfrage von einem vollständigen Datensatz begleitet werden muss.
- Caching: Clients können Antworten des Servers speichern. Damit können Netzausfälle überbrückt oder ein zeitweiser Offline-Betrieb ermöglicht werden. Diese zwischengespeicherten Daten können dann bei einer erneuten Anfrage alternativ statt einer neuen Antwort vom Server verwendet werden. Informationen müssen als cacheable oder non-cacheable gekennzeichnet werden.
- Einheitlichkeit: Restkonforme Services (RESTful API) nutzen eine einheitliche Schnittstelle, welche vom bereitgestellten Dienst entkoppelt ist. Damit entsteht die Möglichkeit einer universellen Verwendung.
- Mehrschichtig: Das Gesamtsystem besteht aus einzelnen Schichten. Komponenten kennen nur verknüpfte andere Komponenten. Es sinkt die Systemkomplexität und es wird einfacher, Erweiterungen durchzuführen.
Ein RESTful-API wird i. d. R. per http bzw. https umgesetzt. Die Services werden per URI und den üblichen http-Methoden GET, Post, PUT usw. genutzt, d. h. der Client sendet eine Anfrage (Request) an den Server und erhält eine Antwort (Response). Ein Request besteht aus vier Bestandteilen (Abb. 4).
Die Bestandteile sind: Der Endpunkt (endpoint), die Methode (method), der Header (header) und die Daten (data). Der Endpunkt setzt sich aus dem root-endpoint, dem path und möglichen Parametern zusammen. Beispielsweise ist https://api.github.com der root-Endpoint von GitHub und /users/veikkoef/repos der path zu den Repositories der Autoren. Zusammen ergibt dieses https://api.github.com/users/veikkoef/repos. Es können noch Parameter nach dem folgenden Muster folgen: ?query1=value1&query2=value2. Bei den http-Methoden hat man die Wahl zwischen GET, POST, PUT/ PATCH und DELETE. GET steht für Read-Operationen. Da GET-Requests nur lesend auf den Server zugreifen, können sie beliebig oft abgeschickt werden. GET ist die standardmäßige Methode. POST wird für Create-Operationen, also das Erzeugen eines Datensatzes eingesetzt. Es können beispielsweise durch einen POST-Aufruf Felder in einer Datenbank verändert oder Prozesse auf dem Server gestartet werden. PUT/ PATCH verwendet man für Update-Operationen und DELETE für das Löschen von Daten. Der Header dient zur Bereitstellung von Informationen für den Client und den Server. Dieser kann für viele Zwecke verwendet werden, zum Beispiel zur Authentifizierung und Bereitstellung von Informationen über den nachfolgenden Inhalt. Abschließend und als letztes Element einer Anfrage folgen die Daten (body), welche man an den Server übermitteln möchte. Der body-Bereich ist nur relevant für die Operationen POST, PUT, PATCH oder DELETE.
Als Entwickler ist man aus zwei Perspektiven beteiligt. Clientseitig werden die bereitgestellten Services genutzt. Das Spektrum der unterstützenden Technologien ist hier nicht eingeschränkt, d. h. vom Desktop-Client über die Web-Applikation bis hin zur App für die mobilen Geräte sind alle Systeme denkbar. Nachfolgend betrachtet dieser Artikel jedoch die Möglichkeiten der Bereitstellung von Businesslogik als Service in Form eines universell über http-RESTful nutzbares API. Das Erstellen eines API hat mehrere Facetten:
- Inhaltlich geht es um die Umsetzung der Fachlogik, d. h. in welcher Form die Daten verarbeitet werden, u. a. ist zum Beispiel eine Datenbank über das API anzusprechen.
- API-Design: Hier muss man die Architektur des API festlegen. Es geht also darum, welche Aktionen der Server bei einer GET- bzw. PUT-Anforderung ausführen soll.
- Technische Umsetzung: Welche Programmiersprache und welches Framework kommen zum Einsatz. Hier hat man die Qual der Wal, d. h. es gibt unterschiedliche technische Herangehensweisen.
Im folgenden Abschnitt wird ein Lösungsansatz aufgezeigt, welcher eine effektive Möglichkeit darstellt, die Businesslogik in Form eines universellen API bereitzustellen. Zum Einsatz kommt der RAD Server aus dem Hause Embarcadero [2].
RAD Server als Backend und Middleware
Betrachtet man sich die Architektur eines Anwendungssystems auf der Basis von RAD Server, so ergibt sich die Struktur gemäß Abb. 5.
Die Server-Applikation fungiert hier flexibel und wahlfrei als Backend oder Middleware und bietet folgende Features:
- Flexibel nutzbaren REST-Endpunkt: Clientseitig werden die Services als universelle REST/ JSON-API bereitgestellt und können damit nahezu von allen Arten von Clients (Desktop, Mobile, Web) genutzt werden. Eine integrierte Zugriffsteuerung erlaubt es, Berechtigungen für die API-Nutzung über Benutzerauthentifizierung festzulegen. Mit der integrierten API-Analyse bekommt man einen Überblick über die Nutzung der Services.
- Middleware: Serverseitig bietet der RAD Server vielfältige Möglichkeiten, u. a. die Integration von Unternehmensdatenbanken, die Verwaltung von IoT-Hardware und die Integration von Cloud- und BaaS-Diensten wie Google, Amazon, Facebook und Kinvey.
- Anwendungsdienste: Der RAD Server bietet ein Set an Anwendungsdiensten wie Benutzerverzeichnis- und Authentifizierungsdienste, Push-Benachrichtigungen, Indoor-/ Outdoor-Positionsermittlung und JSON-Datenspeicher.
Ein besonderes Leistungsmerkmal des RAD Servers ist die nahtlose Bereitstellung der Dienste über die integrierte Entwicklungsumgebung RAD Studio. Damit ist es auf einfachste Weise möglich, Geschäftslogik als Restful API Service zu hosten. Ebenso kann vorhandener Delphi- oder C++-Quellcode mit vertretbarem Aufwand zu einem universell nutzbaren Backend portiert werden. Der Aufwand für notwendige Migrationen von Legacy-Applikationen (s. o.) kann durch umfassende Wiederverwendung merklich reduziert werden.
Ein Backend mit RAD Server und RAD Studio
Um ein Backend auf der Basis des RAD Servers zu erstellen, benötigen Sie eine Testversion von RAD Studio der Enterprise Edition. Diese können Sie von herunterladen [4]. Wir setzen voraus, dass RAD Studio Enterprise installiert ist. Im Folgenden werden die wesentlichen Schritte zum eigenen Service durchlaufen:
- Stellen Sie sicher, dass InterBase auf Ihrem System läuft. Rufen Sie dazu den InterBase Server Manager über das Start-Kommando auf (Abb. 6).
- Starten des RAD Servers (Developer Instance) mit dem Befehl EMSDevServer. Dieses erfolgt über die Kommandozeile. Beim ersten Start müssen Sie den Assistenten zur Einrichtung des EMSDevServers durchlaufen. Im Dialogfeld ergänzen Sie den Eintrag unter Serverinstanz wie in Abb. 7 ersichtlich.
Die weiteren Schritte des Assistenten können Sie ohne Anpassungen durchlaufen und den Entwicklungsserver auf diese Weise konfigurieren. Nach Abschluss erscheint ein Dialogfeld, welches es erlaubt, den Server zu starten/ zu stoppen, die Serveradresse im Browser aufzurufen oder die Konsole zu aktivieren (Abb. 8).
- Klicken Sie auf den Button Browser öffnen und es wird der Standard-Internetbrowser des Systems geöffnet. Der Entwicklungsserver ist unter localhost auf Port 8080 zu erreichen. Adressieren Sie den Endpunkt localhost:8080/version, um eine Standardtextausgabe des Servers zu produzieren (Abb. 9). Damit ist eindeutig, dass der Server arbeitet. Jetzt wollen wir eine eigene Ressource als RESTful-Service bereitstellen.
- Starten Sie RAD Studio und legen Sie ein neues Projekt an. Wählen Sie als Projekttyp RAD Server EMS Package (Abb. 10).
Durchlaufen Sie den Assistenten, wie Sie es in den folgenden Abbildungen sehen. Legen Sie ein Package mit Ressource an (Abb. 11). Innerhalb der Ressource werden dann die http-Methoden GET, POST usw. implementiert.
- Zur Auswahl stehen als Ressource ein Datenmodul oder eine Unit (Abb. 12). Über eine Unit kann zum Beispiel Businesslogik hinterlegt werden. Hier ist es auch möglich, Quellcode aus vorhandenen Delphi-/ C++-Projekten zu adressieren. Vergeben Sie einen Namen für die Ressource. Dieser Name ist dann später Bestandteil des Endpunkts (s. o.).
- Im folgenden Schritt müssen die gewünschten Methoden zum Endpunkt definiert werden, zum Beispiel eine GET-Methode um Daten abzurufen (Abb. 13).
- Klicken Sie im vorherigen Dialogfeld auf Fertig stellen und RAD Studio generiert den notwendigen Quellcode.
Das sehen wir uns jetzt noch etwas genauer an. Die Projektstruktur zeigt, dass die Ressource (hier mit dem Namen MyService) als Unit implementiert wird (Abb. 14).
Interessant ist der Blick in den Quellcode zur Definition unserer RESTful API MyService. Den gesamten Code hat RAD Studio generiert:
unit service; // EMS Resource Unit interface uses System.SysUtils, System.Classes, System.JSON, EMS.Services, EMS.ResourceAPI, EMS.ResourceTypes; type [ResourceName('MyService')] {$METHODINFO ON} TMyServiceResource = class published procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); end; {$METHODINFO OFF} implementation procedure TMyServiceResource.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); begin // Sample code AResponse.Body.SetValue(TJSONString.Create('MyService'), True) end; procedure Register; begin RegisterResource(TypeInfo(TMyServiceResource)); end; initialization Register; end.
Letztendlich entscheidend ist die Definition der Procedure Get (TMyServiceResource.Get), in welcher die Implementierung der http-Methode GET für die API stattfindet. Wir geben als Antwort hier lediglich einen JSON-Datenstrom mit einer einzigen Zeichenkette (MyService) zurück. An dieser Stelle müsste in Produktionsprojekten der Code hinterlegt werden, welcher bei Empfang einer GET-Message vom Client ausgeführt werden soll.
Dass die Ressource via REST von "außen" erreichbar ist, kann man wie folgt schnell testen. Starten Sie das aktive Projekt in RAD Studio und die Ressource wird über den Entwicklungsserver bereitgestellt. Wechseln Sie zum Internetbrowser und rufen Sie nun den Endpunkt mit localhost:8080/MyService auf (Abb. 15).
Andere http-Methoden (POST, Delete usw.) sind auf gleiche Art und Weise zu implementieren. Es genügt als Einstiegspunkt jeweils eine eigene Prozedur (procedure), in deren Rumpf der zu verarbeitende Code hinterlegt wird. Über vorgefertigte Bibliotheken ist es auf sehr einfache Art und Weise möglich, die zu sendenden Daten als allgemein üblichen JSON-Datenstrom zu übermitteln.
Die Nutzung des Developmentservers auf dem lokalen Rechner ist natürlich nur während der Entwicklung möglich. Für eine Produktionsumgebung sollten Sie den EMS-Server und den EMS-Konsolenserver auf einem Webserver einrichten. Aktuell werden Microsoft IIS Server (Windows) und Apache Server (Windows, Linux) unterstützt [5].
Wir haben also gesehen, dass es auf diese Weise möglich ist, schnell und effektiv Anwendungslogik als generisch nutzbaren Service in Form eines API auf einem Server zur Verfügung zu stellen.
Fazit
Die Architektur von modernen und umfassenden Softwareapplikationen verschiebt sich zunehmend in Richtung Microservices. Dabei wird die Businesslogik über http RESTful Services als flexible nutzbare Dienste bereitgestellt. Clientseitig erreicht man damit eine maximale Entkopplung von der Technologie, die Komplexität des Systems kann man durch die Aufteilung in kleinste voneinander unabhängige Einheiten reduzieren. Für die Bereitstellung stehen unterschiedlichste Technologien zur Verfügung. Eine besonders effektive Möglichkeit ist das Hosting der Businesslogik mittels des RAD Servers. Die nahtlose Integration in die integrierte Entwicklungsumgebung RAD Studio gestaltet den Prozess der API-Bereitstellung einfach, kosteneffizient und ermöglicht es, vorhandenen Delphi- oder C++-Quellcode weiter zu nutzen und damit die Anwendungsmigration zur vereinfachen.