Das jüngste Projekt der Renuo, der City Messenger, wurde im Oktober 2019 im App Store und im Play Store aufgeschaltet. Seither hatten wir genügend Zeit, um Kunden-Feedback zu sammeln und eigene neue Ideen weiter zu verfolgen. Dies hat zu einer rundum erneuerten Version geführt, welche demnächst erhältlich sein wird.
In dieser neuen Version wurden allerdings einige Änderungen eingeführt, welche nicht mehr oder nur teilweise rückkompatibel zur ersten Variante der Mobile App waren. Dies führte unweigerlich dazu, dass wir erst einen Schritt zurück machen mussten, bevor neue Features dazukommen konnten.
Das Problem
Die erste Version legte das Codefundament oder den «Grundstein der App» relativ statisch fest. Alle Funktionalitäten wurden so entwickelt, dass sie gut mit der ersten Version der App funktionieren.
Als dann die zweite Version der App in Entwicklung kam, stellte sich rasch die Frage, wie man mit der alten Version umgehen sollte. Denn anders als bei einer Website – die man immerhin teilweise selbst steuern kann – haben bei Apps die Benutzenden die Kontrolle darüber, ob sie die App aktualisieren oder nicht. Um zu verstehen, wieso dies ein Problem darstellt, ist es wichtig zu wissen, wie die Architektur einer modernen App (typischerweise) aussieht.
Die Architektur
Damit der City Messenger funktionieren kann, braucht es – vereinfacht gesehen – drei Komponenten:
Die Mobile App oder im Fachjargon das Frontend (genau genommen ein ReactNative Frontend) ist das, was die Kunden auf ihr Smartphone laden und sehen, wenn sie es öffnen. Die App ist zuständig dafür, Chat-Verläufe darzustellen, Eingaben der User zu akzeptieren und bildet somit die oberste Schicht zum Endbenutzer.
Der Server oder das sogenannte Backend (genauer Ruby on Rails Backend) ist dafür zuständig, Nachrichten zu empfangen und an den richtigen Empfänger weiterzuleiten. Er verwaltet die Benutzerdaten, und verarbeitet/validiert alle Eingaben, die von den Benutzenden über die App getätigt werden. Somit bildet der Server die zweite, wesentlich grössere Schicht.
Die Datenbank ist zuständig für die Speicherung aller User, Shops und der Chat-Verläufe. Sie legt die Daten so an, dass sie vom Server gelesen und weiterverarbeitet werden können. Somit ist sie das Grundgerüst des Ganzen und bildet die dritte Schicht, auf der alles basiert.
Schematisch gesehen sieht die Architektur so aus:
Dabei ist jede Schicht von den anderen losgelöst. Dennoch müssen sie untereinander mit einem vordefinierten Verhalten kommunizieren.
Als Vergleich nehmen wir die Schweizer Post als Verwalterin von Paketen («Backend»), die Sendungen an Privatpersonen («App») zustellt. Dies ist ein standardisierter Prozess: Der Empfänger verfügt über eine Adresse in einem gewissen Format, welches die Post lesen kann. Ist die Adresse korrekt, wird der Empfänger eines Tages ein Paket im Paketfach vorfinden. Dies allerdings nur, wenn bis dahin alle Prozesse so abgelaufen sind, wie geplant. Gibt es irgendwo eine Störung, erfolgt die Zustellung entweder verspätet oder gar nicht. Diesen Vorgang stellen wir vereinfacht so dar:
Übertragen wir nun das Schema auf die City Messenger Architektur, sieht man durchaus eine gewisse Ähnlichkeit:
Die Scherung der einzelnen Versionen
Wenn wir nun nochmals auf die Aussage im ersten Abschnitt zurückgreifen, dass die Benutzenden die Kontrolle über die Updates haben, stellt man ein Problem in dieser Architektur fest: Wenn nun aufgrund der neuen App Version ein neuer «Verhandlungsprozess» definiert wird, mit dem die App und der Server kommunizieren, setzt das voraus, dass sich beide verstehen, dass beide den selben Prozess anwenden. Wenn nun aber die App noch den veralteten Prozess anwendet – da der User die App nicht aktualisiert hat und wir als Entwickler keine Kontrolle darüber haben – muss der Server je nach Strategie unterschiedlich reagieren. Entweder er akzeptiert den Kommunikationsversuch der App nicht mehr, bis sie aktualisiert wird, oder er unterstützt ihn bis auf Weiteres und fährt sozusagen zweigleisig.
Um dies auf das Post-Beispiel zu übertragen: Wenn beispielsweise die Post («Backend») alle Postleitzahlen in eine neue Kombination aus alphanumerischen Zeichen ändert (8400 wird zu A12D9) und ein Sender («App») nun versucht, eine Sendung mit dem alten Postleitzahlensystem aufzugeben, hat die Post auch zwei Möglichkeiten zu reagieren: Entweder sie ist kulant und akzeptiert die Sendung trotz falscher Adresse und stellt sie trotzdem korrekt zu. Oder aber sie lehnt die Sendung ab. Beide Strategien haben Vor- und Nachteile.
Die «Kulanzstrategie» / Backwards-Compatibility
Der grösste Vorteil dieser Strategie – sofern alle App-Versionen vom Endgerät unterstützt werden – liegt wahrscheinlich dabei, dass alle Kunden weiterhin bedient sind, auch wenn sie auf ein Update der App verzichtet haben. Wenn man eine grosse User-Basis hat, macht diese Strategie also sicherlich Sinn. Man möchte kaum Nutzer verlieren, nur weil ein Segment nicht mehr bedient werden kann.
Allerdings zieht diese Strategie auch einige Nachteile mit sich. Der gewichtigste ist der Unterhalt von mehreren Versionen, welche sich gegenseitig vertragen müssen. Dies ist mit einem ordentlichen Mehraufwand verbunden, da man neue Versionen vorsichtig entwerfen muss, damit vorherige Funktionen nicht versehentlich abgeändert werden.
Kommt dazu, dass Kunden, wenn sie schon nicht auf die zweite Version updaten, womöglich auch nicht auf die dritte und vierte aktualisieren werden. So müssen Bug-Fixes immer mehrfach eingebaut werden, um auch ältere Versionen zu berücksichtigen.
Die «Ablehnungsstrategie» / Version Enforcement
Der Vorteil dieser Strategie liegt darin, dass in der Regel alles die selbe Struktur hat. Die Funktionalität muss demnach nur einmal entwickelt werden. Für eine neue Version kann sie einfach geändert werden, ohne dass man gewissen Kunden «die Tür vor der Nase zuschlägt». Der Entwicklungsaufwand ist dementsprechend kleiner.
Allerdings gibt es auch bei dieser Strategie einen Haken: Wenn die Kunden die App öffnen, müssen sie als erstes ein Update durchführen, bevor sie die App nutzen können. Dies wird als schwache UX (User-Experience) gedeutet und kann zu User-Verlust führen.
Falls die User die App jedoch tatsächlich updaten, führen die neuen Features insbesondere bei Chatapplikationen zu einer Verbesserten UX. Und je mehr Personen eine neue Chatfunktionalität nutzen, desto attraktiver wird sie.
Über seine Benutzenden Bescheid zu wissen, hilft!
Für welche Strategie man sich nun entscheidet, muss von den Endkunden abhängen. Es lohnt sich also frühzeitig einen Mechanismus einzubauen, mit welchem man das Updateverhalten der User verfolgen und auch steuern kann. Im Beispiel vom City Messenger hatte sich ergeben, dass die meisten Kunden die App regelmässig aktualisierten oder automatische Updates aktiviert hatten. So waren bereits 10 Tage nach dem Release (23. Februar 2020) die meisten Installationen bereits aktualisiert worden. Auch stieg die Aktivierungsrate stetig, also die Anzahl Kunden, die nach dem Update die App auch öffneten oder eine Neuinstallation durchführten.
Im Falle vom City Messenger haben wir uns deshalb für die Ablehnungsstrategie entschieden. Dadurch ergaben sich Vorteile, welche überwiegend waren:
User profitieren schneller von Neu-Entwicklungen, da alle sie nutzen können.
Das Entwicklungstempo ist schneller, da wir uns nicht um Rückwärtskompatibilität kümmern müssen. So liegt die Verbesserungen der Apps stets im Fokus.
Wir verhindern, dass zwei unterschiedliche Datenversionen entstehen, was zu Verlust von Kunden führen könnte.
Fazit
Das Update-Tracking nachträglich einbauen zu lassen, ist meistens kompliziert und erfordert viel Zeit. (Für den City Messenger brauchte es 35 Arbeitsstunden zusätzlich.) Deshalb ist es sinnvoll, sich bereits vor einem App-Release Gedanken zu machen, wie man mit seinem Produkt fortfahren möchte. Vor allem wenn künftig weitere Komponenten auf die Daten zugreifen sollen, wird es schnell kompliziert.
Da sich jede Kundschaft anders verhält, ist es auch ratsam, schon bei den ersten Patch Releases das Update-Verhalten zu studieren, damit bei einem grossen neuen Release keine Überraschungen auftreten.