Wie entwickle ich eine Mobile App ohne Server? - Ein Plädoyer für Service Simulatoren

Einführung
„Wie entwickle ich eine Mobile App ohne Server?“ Diese Frage wurde glücklicherweise früh in einem Kundenprojekt gestellt, bei dem es im „Internet of Things“ Kontext vornehmentlich darum ging, neuartige Business Cases im Landwirtschaftssektor zu evaluieren. Mittels des direkten Feedbacks durch die Endnutzer sollte in erster Instanz darüber entschieden werden, ob eine Weiterverfolgung des Marktes strategisch sinnvoll ist. Dementsprechend war es von entscheidender Bedeutung, die Entwicklung der Mobile App und der im Hintergrund laufenden Serveranwendung zu parallelisieren, um somit innerhalb kürzester Entwicklungszeit die diversen Use Cases zu validieren und Change Requests zeitnah zu berücksichtigen.
In diesem Blogbeitrag möchte ich anhand konkreter Code-Beispielen zeigen, wie mithilfe des Service Simulators Wiremock schnell und einfach ein Backendservice gemockt wird, um die clientseitige Entwicklung zu beschleunigen. Hierfür habe ich sowohl eine Android App als auch ein serverseitiges Gegenstück mittels Spring Boot entwickelt. Die ausführbaren Code-Beispiele sind zusammen mit der Wiremock Konfiguration und einer Installationsanleitung auf Github zu finden.
Problemstellung: Abhängigkeit zum Server

Darstellung der Abhängigkeit zwischen Client und Server
Die Abhängigkeit zum Server erschwert die parallele Entwicklung des Mobile Clients und der Serveranwendung. In der Praxis findet man sich hierbei oft folgenden Problemsituationen konfrontiert
- „Hidden Complexity“: Die Backend-Kollegen arbeiten am Feature XY, welches sich unerwartet in die Länge zieht und folglich die Mobile App Entwicklung entschleunigt.
- Aufwändige Serverinstallation: Für die lokale Installation des Servers müssen ebenfalls Umsysteme wie z.B. Datenbanken oder ERP Systeme installiert und konfiguriert werden.
- „Configuration Hell“: Je nach Komplexität des serverseitigen Technologie-Stacks sorgen Konfigurationsänderungen für unnötigen Aufwand für den Mobile-Entwickler.
Lösung: Service Simulation mit Wiremock
Abhilfe schafft hier das Service Simulations-Tool Wiremock. Hierzu ein Auszug aus der Produktseite
„WireMock is a flexible library for stubbing and mocking web services. Unlike general purpose mocking tools it works by creating an actual HTTP server that your code under test can connect to as it would a real web service.“ Wiremock.org
Wiremock bietet unter anderem folgende Features an
- HTTP response stubbing: In der einfachsten Form wird für eine definierte Route eine vorgefertigte statische Antwort retourniert – ein sogenannter Service Stub. Die Konfiguration kann Angaben zur URL und dem Request Header bzw. Request Body enthalten.
- Request verification: Da standardmäßig alle getätigten Requests von Wiremock (In-Memory) gespeichert werden, ist es mithilfe der Verify-Funktion möglich, posthum zu bestimmen, ob bestimmte Requests getätigt wurden.
- Proxy/Interception: Wiremock fungiert hier als „Man-In-The-Middle“, d.h. Requests können wahlweise an den tatsächlichen Server weitergeleitet oder über eine Stubbing-Konfiguration beantwortet werden. Die Antworten des tatsächlichen Endsystems leitet Wiremock transparent weiter an die Client-Anwendung.
- „Record and playback of stubs and fault injection“: Erleichtert das Arbeiten im „Offline-Modus“, da Wiremock Anfragen an das Live-System sendet und die empfangene Antworten vom tatsächlichen Server aufzeichnet. Später können diese als Stub Konfigurationen wiederverwendet werden.
- Standalone Prozess und JUnit Integration: Wiremock bietet sowohl eine „Standalone“ Ausführungsvariante (Java Anwendung) als auch eine Java API in Form einer JUnit Test Rule an.
„Grau ist alle Theorie“ – Ein Code-Beispiel
In diesem Beispiel zeige ich, was notwendig ist, um mithilfe von Wiremock eine Task-Verwaltungs-App entkoppelt vom Server zu entwickeln.

Darstellung der App-Menüstruktur
Implementierung des HTTP Clients
Serverseitig existiert eine RESTful API zur Datenhaltung und Verwaltung der Tasks. Um in der Android App einen REST Client zu implementieren, verwende ich die bekannte Bibliothek Retrofit.
Retrofit ermöglicht es deklarativ eine typsichere HTTP Clientimplementierung zu generieren und somit den Quellcode auf ein absolutes Minimum – hier ein simples Java Interface – zu reduzieren. Unter der Haube wird dies mit der im JDK 1.3 eingeführten Reflection API (Dynamic Proxy) realisiert. Nachfolgend wird der implementierte Service Client dargestellt. Import Statements wurden zugunsten der Lesbarkeit entfernt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public interface TaskService { @GET("/tasks") void getTasks(Callback<List<Task>> tasks); @POST("/tasks") void createTask(@Body Task task, Callback<Response> response); @GET("/tasks/{id}") void getTaskById(@Path("id") String id, Callback<Response> response); @GET("/fancy/task") void getFancyTask(Callback<Task> fancyTaskCallback); } |
Dem aufmerksamen Leser fällt sicherlich eine Besonderheit in den Methodensignaturen auf… richtig, ein reaktives Programmiermuster! Callback-Objekte kapseln Logik, welche automatisch ausgeführt wird, sobald das Ergebnis verfügbar ist. Diese Technik sorgt dafür, dass der Service-Aufrufer nicht automatisch blockiert wird, sobald der Service nicht sofort das gewünschte Ergebnis liefert. In unserem Beispiel ist der Service-Aufrufer eine Activity, welche bei herkömmlichen, nicht reaktiven, Implementierungen das User Interface während Service-Aufrufen einfrieren lässt und somit unmittelbar die User Experience verschlechtert.
Die Verwendung des Service-Clients wird im folgendem Listing illustriert.
1 2 3 4 5 6 7 8 9 10 11 12 |
taskService.getTasks(new Callback<List<Task>>() { @Override public void success(List<Task> tasks, Response response) { CroutonUtil.showSuccess(MainActivity.this, "Ergebnis erhalten..."); taskListFragment.updateUi(tasks); } @Override public void failure(RetrofitError error) { CroutonUtil.showError(MainActivity.this); } }); |
Die Idee ist hier simpel: Entweder ist ein Service-Aufruf erfolgreich, dann wird mit der Crouton Bibliothek ein Erfolgs-Popup angezeigt oder es ist ein Fehler aufgetreten, dann wird entsprechend ein Fehler-Popup angezeigt.
Wiremock – Modus Operandi
Als nächstes möchte ich zeigen, wie man nun mithilfe von Wiremock die oben beschriebene REST API simulieren kann. Zur Konfiguration der Standalone Ausführungsvariante werden gewöhnliche JSON Dateien verwendet. Sicherlich ist es sinnvoll, diese Konfigurationen als Teil des Projektes mit zu versionieren. Die Standalone Version erzeugt zur Laufzeit dann die entsprechenden HTTP Endpunkte, welche sofort aufrufbar sind, da Wiremock automatisch einen integrierten Web Server startet. Natürlich kann der zu verwendende HTTP Port über eine Konfiguration angegeben werden, um Port-Kollisionen zu vermeiden.
Der erste Service Stub – Gib mir alle Tasks…
In diesem Beispiel soll aus dem „TaskService“ die Operation „getTasks()“ simuliert werden. Auf der HTTP-Ebene entspricht dies einem HTTP GET Request auf die Ressource „/tasks“. Nachstehend die dazugehörige Wiremock-Konfiguration.
1 2 3 4 5 6 7 8 9 10 |
{ "request": { "method": "GET", "url": "/tasks" }, "response": { "status": 200, "bodyFileName": "task_list_response.json" } } |
Idealerweise sollte die Konfiguration der Stub-Route vom eigentlichen Stub (Dummy-Daten) getrennt werden. Dies erhöht die Lesbarkeit der Konfiguration und der Stub kann wiederverwendet werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[ { "id" : 1, "title" : "Blog-Post schreiben", "content" : "Wiremock - ein nützliches Tool..." }, { "id" : 2, "title" : "Einkaufen", "content" : "Milch, Zucker..." }, { "id" : 3, "title" : "Schlafen gehen", "content" : "zzzzh" } ] |
Grundsätzlich besteht eine Stub Konfiguration aus zwei Elementen: Request und Response. In dem Request-Teil wird festgelegt, welche HTTP Methode und URI verwendet werden soll. Das Response-Element legt fest, welcher HTTP Status Code und Response Body zurückgegeben werden soll.
Task suchen mittels Query Parameter
Um nun einen Task anhand der „Id“ zu suchen, ist es notwendig, einen Query Parameter übergeben zu können. Die Suche nach der Task „42“, also ein HTTP GET Request auf „/tasks?q=42“, kann in Wiremock wie folgt abgebildet werden:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "request": { "method": "GET", "urlPath": "/tasks", "queryParameters": { "q": { "contains": "42" } } }, "response": { "body": "Scala lernen", "status": 200 } } |
Das Element „queryParameters“ enthält den Parameternamen und den erwarteten Wert. Zum Vergleich der Query Parameter stehen die bekannten String-Operationen „contains“ und „equals“ zur Verfügung.
Task erstellen – POST Requests simulieren
Selbstverständlich unterstützt Wiremock zusätzlich zu dem bisher gezeigten HTTP Verb GET auch PUT, DELETE und POST. Das nächste Listing zeigt, wie POST Operationen simuliert werden können. Fachlich soll hiermit das Erstellen einer neuen Task abgebildet werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "request": { "method":"POST", "url":"/tasks", "bodyPatterns": [ { "equalToJson":"{ \"name\": \"Geburtstag organisieren\" }", "jsonCompareMode":"LENIENT" } ] }, "response": { "body":"created", "status": 201 } } |
Das „bodyPatterns“ Element beinhaltet den erwarteten Request Body – hier den Tasknamen – und eine Angabe, wie „streng“ der Wiremock-interne JSON Validator den Request Body validieren soll. Mögliche Varianten sind zum Beispiel
- Strict: Das übergebene JSON muss die korrekte Reihenfolge der Attribute beachten und darf keine Zusatzattribute besitzen.
- Lenient (übersetzt milde): Die Reihenfolge der Attribute ist unerheblich und es dürfen zusätzliche Attribute vorhanden sein.
Weitere Stubbing Konfigurationsmöglichkeiten sind auf der offiziellen Produktseite im Abschnitt „Stubbing“ zu finden.
Integration von 3rd Party Services

Wiremock als Proxy Server
Zu guter Letzt zeige ich, wie man bestimmte Requests an ein tatsächliches Backend sendet und Wiremock beispielsweise für die Simulation nicht implementierter Services einsetzen kann. Die Serveranwendung, eine simple REST Ressource, habe ich mithilfe von Spring-Boot entwickelt. Wie eingangs erwähnt ist diese zusammen mit der Android App und der gezeigten Wiremock Konfigurationen auf Github zu finden.
Möchte man nun bestimmte Services nicht mehr mocken, dann gilt es, im Unterschied zu den bisherigen Stubbing Konfigurationen, einen kleinen Unterschied im Response Element zu beachten:
1 2 3 4 5 6 7 8 9 |
{ "request": { "method": "GET", "urlPattern": "/fancy/.*" }, "response": { "proxyBaseUrl" : "http://localhost:8022" } } |
Das Response Element retourniert nicht mehr wie bisher einen Stub, sondern verweist im Attribut „proxyBaseUrl“ auf das Live-System. Mit anderen Worten: Requests unterhalb des Pfades „/fancy/*“ werden auf die hinterlegte URL umgelenkt. Beispielsweise würde daher ein HTTP GET Request auf „/fancy/scala“ mit „http://localhost:8022/fancy/scala“ aufgelöst werden. Die HTTP Response des Live-Systems leitet Wiremock dann weiter an die Client-Anwendung.
Fazit
In diesem Blog-Post habe ich gezeigt, wie mithilfe von Wiremock die Client- und Serverentwicklung entkoppelt werden kann. Die Einarbeitungszeit und der Konfigurationsaufwand sind dank der leicht verständlichen Wiremock Dokumentation und den zahlreichen Beispielen kurz und überschaubar.
Natürlich existieren neben den aufgezeigten Vorteilen der Service Simulation, wie so häufig, auch Nachteile bzw. Trade-Offs, die man ebenso betrachten sollte. Aufgrund der beschriebenen Entkopplung ergibt sich naturgemäß ein höheres Integrationsrisiko der Client- und Serveranwendung, da die Integration letztlich in die Zukunft verschoben wird. Daher ist es umso wichtiger, die API-Dokumentation des Services aktuell zu halten. Besser noch: Die Dokumentation sollte direkt ausführbar sein! In dem eingangs erwähnten Projekt war genau diese lebendige API-Dokumentation ein Garant für den erfolgreichen Abschluss.
Aktuelle Beiträge






Artikel kommentieren