Martin Zöller
Im Laufe unseres ersten Jahres in Bangkok hat sich eine Gewohnheit entwickelt: Zum Abendessen schauen wir oft eine Folge von Gordon Ramsays „Kitchen Nightmares“. Ramsays raue Art und kreative Beleidigungen lockern die Atmosphäre nach einem langen Arbeitstag, auch wenn das, was er manchmal in den Küchen der Restaurants findet, durchaus den Appetit verderben kann.
Jede Folge ist relativ ähnlich aufgebaut: Gordon probiert das Essen, spricht mit den Besitzern und Mitarbeitern und schaut sich einen Abend lang an, wie das Restaurant funktioniert. Dann dreht er den Laden auf links: Oft wird die Speisekarte überholt, die alte Inneneinrichtung ersetzt und vor allem viel an der Kommunikation und Organisation zwischen den Akteuren gearbeitet.
Der Höhepunkt jeder Folge ist der Relaunch: Gordon beobachtet in voller Chef-Montur, was das Team gelernt hat und wie es seine Änderungen umsetzt. Und an dem Punkt kann einem der Mann echt leidtun. Manchmal muss er mitansehen, wie sich fast nichts bessert: Gerichte gehen roh oder zerkocht raus, Köche und Kellner schreien sich gegenseitig an, und selbst die einfachsten Hygiene-Vorschriften muss der britische Hüne auch nach einer Woche Zusammenarbeit immer noch in die Köpfe der Köche prügeln.
Ganz ehrlich? Was ich mir in den letzten Jahren im Bereich Mobile-APIs angesehen habe, ist oft erschreckend nah an dem, was Gordon Ramsay in diesen Momenten durchlebt: Als jemand, der sowohl viel Backend-Erfahrung hat gesammelt, als auch Jahre seines Lebens in Apples Ökosystem und mit Flutter verbracht hat, tut es jedes Mal weh, zu sehen, wie Teams nicht miteinander arbeiten, sondern gegeneinander. Besonders dann, wenn es um die Integration einer Backend-API in eine Mobile-App geht, finde ich oft zwei Lager vor, statt eines gemeinsamen Teams.
Und obwohl ich noch nicht angefangen habe, Leute als „Donut“ zu beschimpfen – was aus meinem deutschen Mundwerk auch nur halb so lustig klingen würde wie mit diesem wundervollen britischen Akzent –, habe ich zumindest eine Liste mit neun echten Problemen zusammengestellt, die ich in APIs für Mobile-Apps immer wieder sehe – natürlich zusammen mit Erklärungen, warum das echte Probleme sind, und Wegen, wie man sie lösen kann:
„Unsere API ändert sich nicht.“ – Bis sie es tut. In vielen Unternehmen ist die Website oft der erste Client – die Mobile-App folgt meist erst später. Die Folge ist, dass die API, die die Mobile-App mit Daten füttert, für die Web-App optimiert ist, und eben nicht für die Mobile-App.
Das ist ein Problem, denn: Web-Apps lassen sich ebenso schnell aktualisieren wie Backend-Systeme. Mit einem einzigen Knopfdruck aktualisiert sich die Web-App zusammen mit dem Backend, das die API bereitstellt. Jeder Nutzer sieht sofort die neue Version. Was vor zehn Minuten noch die aktuelle Version war, ist inzwischen spurlos verschwunden.
Bei Mobile-Apps funktioniert das nicht. Smartphone-Besitzer entscheiden selbst, ob und wann sie deine App aktualisiert. Aktualisiert sich also dein Backend und die App nicht, kann die App ggf. nicht mehr mit dem Backend sprechen.
Die Lösung? API-Versionierung – das heißt: das kleine Wörtchen „v1“, an irgendeiner Stelle der API – meistens im Pfad oder in einem HTTP-Header. Das sorgt dafür, dass sich das Backend unabhängig aktualisieren kann – unter der Bedingung, dass gewisse Änderungen gleichbedeutend mit einer neuen API-Version (v2, v1.1, …) sind – und zwar solche Änderungen, mit denen deine Mobile-App nicht ohne ein eigenes Update umgehen kann.
Jetzt fragst du dich wahrscheinlich: Mit welchen Änderungen kann eine App umgehen und mit welchen nicht? Das ist die perfekte Überleitung zum nächsten Punkt.
Ein Beispiel: Deine App zeigt eine Liste von Rezepten an, die von deiner API geliefert werden. Simpel. Wahrscheinlich handelt es sich bei deiner API um eine JSON-API und vielleicht ist deine Intuition, diese Liste einfach als Array an deine Mobile-App zu liefern.
Das ist in Ordnung, solange du 20 Rezepte hast, die du zurückgibst. Vielleicht ist es auch bei 200 Rezepten noch in Ordnung. Aber was machst du bei 2.000? Vielleicht ist es dann sinnvoll, die Rezepte nicht als ewig lange Liste, sondern aufgeteilt in Seiten (Pages) zurückzugeben. Das erfordert die Rückgabe zusätzlicher Informationen wie z.B. die Gesamtzahl der Rezepte, die aktuelle Seite und so weiter.
Wie machst du das jetzt, wenn du nur eine Liste hast, die deine API liefert? Du musst ein Objekt bauen, das sowohl die Liste als auch die zusätzlichen Informationen enthält. Das ist ein klassischer „Breaking Change“ und ich kenne keine einzelne Mobile-App, die damit von Haus aus umgehen kann.
Die Lösung ist total einfach: Von Anfang an immer ein Objekt zurückzugeben, das deine Rezeptliste als Feld enthält. Zalando empfiehlt das beispielsweise in seinen API-Guidelines. Obwohl diese Maßnahme so einfach ist, erlebe ich immer wieder, dass sie nicht befolgt wird.
In manchen Unternehmen sitzen sowohl Backend-Entwickler, als auch App-Leute im gleichen Team. In anderen Unternehmen sitzen beide Parteien nicht einmal in der gleichen Postleitzahl.
Die Folge? Keine Seite versteht die andere. Die Backend-Leute sehen die Welt mit ihrer Backend-Brille. Die App-Leute wollen einfach nur Daten empfangen und anzeigen. Zeit für das neue Feature hat eigentlich sowieso niemand, weil gerade ohnehin wieder zehn Initiativen gleichzeitig fertig werden müssen und schon wieder drei Leute in Elternzeit sind.
Also wird die API für das neue Feature gebaut, wenn eben Zeit ist, und Anforderungen und Umsetzungen fliegen über den bildlichen Zaun wie Charlie Harpers Hinterlassenschaften in einer Plastiktüte. (Two and a Half Men? Irgendwer? Nein?)
Das führt zu Missverständnissen, Feedbackschleifen, Frust in Retrospektiven und natürlich auch zu Folgefehlern im Betrieb.
Die Lösung ist auch hier recht simpel: Nehmt euch gezielt Zeit für neue Features und sprecht miteinander. Geht die Anforderungen zusammen durch und entwerft die entsprechenden Endpunkte ebenfalls gemeinsam. Einigt euch auf Pfade und Datenobjekte, die für beide Seiten passen, und haltet diese fest (Contract). Das kostet initial mehr Zeit, Organisation und Kommunikation, spart aber Feedbackschleifen und ärgerliche Feuerlösch-Sessions im Live-Betrieb.
Ein Begriff, den du dir dazu merken kannst: Backends for Frontends.
Zum Thema Feuerwehr: Der mit Abstand häufigste Grund für lange Abende im Büro und Post-Mortems in meiner Karriere ist das Entfernen von Feldern aus API-Antworten. Das Problem lässt sich in drei sehr kurzen Sätzen zusammenfassen:
„App erwartet Feld. Backend entfernt Feld. App funktioniert nicht mehr.“
Eine kleine Abwandlung davon ist, ein bislang immer gesetztes Feld in ein Feld zu ändern, das manchmal fehlt. Beides sind Breaking Changes (siehe oben). Ein kurzes Beispiel:
„Alle Kunden haben immer eine Postleitzahl.“ Monate später ändert sich irgendwo im Produkt der Onboarding-Flow für Neukunden, sodass die Postleitzahl nicht mehr im ersten Schritt angegeben werden muss. Plötzlich gibt es Kunden ohne Postleitzahl. Dementsprechend fehlt sie bei manchen Kunden in der API-Antwort. Apps können damit nicht umgehen.
Die Lösung? API-Versionierung (siehe Punkt 1) sowie enge Absprachen zwischen Backend-Entwicklern und App-Leuten. Manche Leute sind der Auffassung, dass die eigentliche Lösung darin besteht, niemals Felder zu entfernen oder optional zu machen, wenn sie einmal verpflichtend und „immer da“ waren. In der Praxis ist das schwierig und vielleicht auch über Jahre hinweg nicht förderlich, wenn API-Objekte immer größer werden und irgendwann niemand mehr weiß, welches Feld aktuell ist und welches schon vor Jahren hätte entfernt werden können.
Das Schöne an der Softwareentwicklung ist, dass fast nichts von dem, was wir tun, wirklich neu ist. Kevlin Henney hat auf der GOTO 2024 in Kopenhagen in einem tollen Vortrag deutlich gemacht, wie wenig Innovation in Programmiersprachen stattfindet und wie viel älter viele Konzepte sind, als wir denken.
Das bedeutet: Was auch immer du gerade baust – geh davon aus, dass (fast) alle technischen Details schon einmal gelöst wurden und dass es für die allermeisten Probleme einen Standard gibt.
Klingt logisch, oder?
Trotzdem sehe ich immer wieder Fehlerbehandlungen der Marke „Eigenbau“ – von internen APIs bis hin zu großen öffentlichen APIs bekannter Anbieter. Wenn dein Backend mit Status 200 (OK) antwortet, möchte keine App der Welt im Payload der Antwort nach Hinweisen auf Fehler suchen.
Warum? Ein einfacher Grund: Wahrscheinlich nutzt die App Standard-Tooling für die Anbindung der API, z. B. URLSession aus Apples Foundation. Dieses Standard-Tooling beinhaltet eine Standard-Fehlerbehandlung, angelehnt an Standard-Fehlercodes aus dem REST-Standard.
Merkst du was? Richtig: Standards reduzieren (meistens) Komplexität. Ich habe oft gehört: „Bei uns funktioniert das nicht.“ Was ich bislang sehr selten gehört habe, ist eine gute Antwort auf die wiederholte Frage: „Warum?“
Ein wichtiger Grundsatz für jede Mobile App ist: Je weniger Logik in der App stattfindet, umso besser. Alles, was das Backend übernehmen kann, sollte es übernehmen. So muss eine Änderung von Business-Logik im Unternehmen nicht über Monate ausgerollt werden, bis sie selbst den letzten Nutzer erreicht hat (siehe Punkt 1).
Dennoch sehe ich häufig Code in Apps, der lediglich dazu dient, die Modelle, die aus der API kommen, in Repräsentationen umzuwandeln (Mapping), die sich zur Anzeige auf dem Bildschirm eignen. Das erfordert Domänenwissen und Business-Logik in der App – und die können sich eben ändern.
Die Lösung ist einmal mehr ein Backend für das Frontend: Die Modelle, die das Backend liefert, sind für die App geeignet, auch wenn sie sich dadurch stark von ihrer Datenbank-Repräsentation unterscheiden. Zwar hat das zur Folge, dass mehr Mapping-Logik für unterschiedliche Clients im Backend landet – doch diese Logik, gebündelt an einer Stelle, ist ein großer Gewinn gegenüber verteilter Logik in mehreren Clients (Android, iOS und Web).
Software steht und fällt mit Kommunikation. Ein Entwickler, dem Informationen fehlen oder der seinen Gegenüber nicht versteht, kann nicht effektiv arbeiten – egal, wie gut sein Code oder sein technisches Verständnis ist.
Fast alle größeren Probleme, die ich in meiner bisherigen Laufbahn lösen musste, entstanden durch unzureichende Kommunikation. Fast alle Lösungen, die ich gebaut habe, waren nur so gut wie die Absprachen und Konzepte, die vorher getroffen und entworfen wurden.
Diese Konzepte müssen kein Schuss ins Blaue sein: Regelmäßiger Austausch zwischen App- und Backend-Leuten kostet Zeit und füllt einen ohnehin vollen Kalender weiter auf. Aber ein stärkeres Band zwischen beiden „Parteien“ ermöglicht eine effektivere Zusammenarbeit, mehr gegenseitiges Verständnis und Respekt und letztlich bessere Lösungen: Mobile-App-APIs lassen sich leicht einbinden. Backend-Leute kennen die Pain Points der App-Leute. Gemeinsame Rollouts sind besser koordiniert.
Klingt toll, oder? Aus meiner Erfahrung kann ich sagen, dass es das auch ist. So macht es auch einfach mehr Spaß.
Nicht jede App verarbeitet sensible Daten – ein Beispiel: Meine App iana liest die Apple Music Library ihrer Nutzer und generiert daraus interaktiv Playlists oder shuffelt nach ganzen Alben (ganz wie früher – wer kennt es noch?). Die Informationen, die sie verarbeitet, sind nicht personenbezogen und ihre Kommunikation nach außen ist minimal.
Das gilt allerdings nur für wenige Apps. Wenn deine App eine API anspricht, die von deinem Unternehmen entwickelt wird, können das auch Clients tun, die nicht aus deiner App kommen – zum Beispiel Angreifer, die versuchen, sensible Daten aus deinem Backend abzugreifen, indem sie sich als „deine App“ ausgeben.
Ein einfacher Weg, eine solche API zusätzlich abzusichern, ist App Attest / DeviceCheck. Wenn jemand weiß, dass eine Anfrage aus einer legitimen Kopie deiner App kommt, dann sind das Apple und Google.
Während es möglich ist, das Backend für diesen Mechanismus selbst zu implementieren, rate ich all meinen Kunden, App Check von Firebase zu nutzen: Es ist sehr schnell aufgesetzt und hat den gleichen Effekt wie eine hauseigene Implementierung.
Wenn deine App und ihre API die ersten acht Punkte dieser Liste bereits erfüllt, dann bist du auf einem sehr guten Weg. Was immer bleibt, ist ein Restrisiko: Fehler passieren und sind menschlich. Ist eine fehlerhafte App-Version einmal vollständig ausgerollt, gibt es keinen Weg zurück mehr.
Für diese Fälle ist ein Force-Update-Mechanismus absolut unerlässlich: Hier wird die App-Version gegen eine Mindestversion geprüft (die z. B. dein Backend liefert), und wenn die installierte Version älter ist, wird dem Nutzer eine Aufforderung angezeigt, die App zu aktualisieren. Er oder sie hat keine Möglichkeit mehr, die installierte Version zu nutzen (deswegen „Force“ Update).
Das Problem? Ein Force-Update funktioniert nur für Versionen deiner App, die den Mechanismus schon kennen. Wenn du ihn zum ersten Mal brauchst und dann einbaust, ist das zu spät – es sei denn, du bist ein Time Lord.
Ich empfehle jedem Unternehmen mit eigener App, einen solchen Mechanismus so früh wie möglich einzubauen: Auf der einen Seite ist eine Implementierung recht einfach. Auf der anderen Seite sollte ein solcher Mechanismus auch mindestens seit ein paar Monaten ausgerollt sein, bevor man sich auf ihn verlassen und von ihm Gebrauch machen kann.
Wenn deine App also keine Möglichkeit hat, alte oder fehlerhafte Versionen zum Update zu zwingen, ist jetzt der zweitbeste Zeitpunkt, diese Möglichkeit einzubauen – der beste Zeitpunkt war übrigens gestern.
Mobile-Apps an dein Backend anzubinden ist eine komplexere Aufgabe als die Anbindung einer Web-App. Vielen Unternehmen fehlt schlichtweg die Erfahrung – sie wissen nicht, was sie nicht wissen. Die gute Nachricht: Du bist jetzt bestens gerüstet!
Keiner dieser neun Punkte ist schwer umzusetzen. Dennoch sehe ich jede einzelne dieser „Sünden“ regelmäßig. Hier noch einmal eine kurze Checkliste mit neun Fragen aus den neun Punkten, an der sich dein Team und du orientieren könnt:
Kannst du all diese Fragen mit „Ja“ beantworten, ist deine Mobile-App-API besser als 90 % aller Apps, die ich bislang gesehen habe. Hast du offene Baustellen, Fragen oder brauchst Unterstützung bei der Umsetzung? Dann melde dich bei mir.