Stell dir vor, du bist Trainer einer Volleyballmannschaft im Breitensport. Nach langem Hin und Her hast du es endlich geschafft, dass deine Mannschaft sich nicht mehr über WhatsApp für Training und Spiel anmeldet. Aber die Zusagen kommen trotzdem wieder erst 25 Minuten vor Abfahrt in KADR an.
Heißt für dich eigentlich: Vorbereitungszeit = /dev/null. Und irgendwie musst du jetzt eine brauchbare Aufstellung zusammenbekommen, die das Feld nicht offen lässt wie ein Scheunentor. Also greifst du dir das iPad (das sonst eigentlich nur für YouTube benutzt wird, obwohl du immer behauptest, dass du damit wirklich arbeitest) und willst deine gemeldeten Spieler irgendwie in die Taktik-App des findigen Entwicklers deines Vertrauens (ja, ich bin es) schieben.
Aber wie funktioniert dieser Datenaustausch zwischen den Apps eigentlich richtig, ohne dass alles explodiert?
Die Lösung: In KADR sind ja schon alle Anmeldungen, also klickst du auf den Button im Termin "An Volleyzones senden" und im Hintergrund passiert Magie. Und in diese Magie möchte ich euch jetzt einweihen. Falls ihr schon Mitglied im Bund der Datenmagier seid, lest trotzdem weiter, ich will eure Zeit und Aufmerksamkeit und gerne auch Meinung.
Wer vor dem gleichen Problem steht vor dem auch ich stand: Ich will euch Wissen geben.
Der Standard-Reflex (und warum er doof ist)
Beide Apps sind mobil, beide hängen ohnehin an Firebase. Der erste Reflex ist also: Pack den Kader einfach in die
Push-Notification und schick ihn per FCM rüber. Ein Funktionsaufruf, fertig.
Doof. Und zwar aus Gründen, die genau dann zuschlagen, wenn du sie am wenigsten gebrauchen kannst:
- FCM ist best-effort : keine garantierte Zustellung. Push kommt nicht an, Daten weg.
- Das Payload-Limit liegt bei ~4 KB. Dein Kader passt vielleicht, das nächste Feature nicht mehr.
- Pushes werden zusammengefasst, verworfen, umsortiert; Tokens rotieren.
Du würdest deinen kompletten Datentransport auf einen Kanal bauen, der konstruktionsbedingt unzuverlässig ist. Eine Türklingel als Paketzustellung.
(Deep-Links und Realtime-Sync hab ich auch durchgespielt: Deep-Links brauchen beide Apps installiert und geöffnet,
Realtime-Sync stirbt, sobald das iPad offline ist. Aber der FCM-Payload ist der verführerischste Fehler, weil er am einfachsten aussieht.)
Also: "Zustellung an den Briefkasten erfolgt"

Die Idee ist simpel. Der Push ist nur noch die Türklingel. Das Paket liegt woanders, an einem Ort, der nicht wegläuft, wenn die Notification verpufft.
- Sender (KADR): erzeugt ein versioniertes Event
KaderTransferred(terminId,spieler[],timestamp) und legt
es in eine Inbox-Tabelle. - Inbox: eine dedizierte Tabelle. Events bleiben liegen, bis sie abgeholt werden. Source of Truth ist die Tabelle, nicht der Push.
- Empfänger (Volleyzones): zeigt wartende Kader an, der Trainer übernimmt und bestätigt mit
POST /inbox/{eventId}/ack.
FCM ist damit optional. iPad offline? Notification weggewischt? Egal ein Reload zieht den Kader direkt aus der Inbox.
Der Push beschleunigt nur noch, er trägt keine Verantwortung mehr.
To be honest: Wirklich sauber ist das (noch) nicht
Um auch irgendwo einen Lehrauftrag zu erfüllen:
Meine Inbox-Tabelle liegt in einer Postgres-Instanz, die sich KADR und Volleyzones teilen. Im Klartext: Die beiden
Backends sind am Datenlayer gekoppelt. Wenn ich sie irgendwann sauber trennen will, also eine eigene DB pro Service, das volle Lehrbuch-Programm, dann geht das so erstmal nicht.
Sauber wäre: Volleyzones holt sich die Transfers über einen API-Call beim KADR-Backend ab. Klare Grenze, keine geteilte Datenbank.
Hab ich nicht gebaut. Bewusst. Weil die geteilte Tabelle in fünf Minuten stand und für ein Sideproject am Küchentisch alles andere Overkill wäre.
Warum mich das trotzdem ruhig schlafen lässt
Weil ich nicht gegen eine Implementierung gebaut habe, sondern gegen einen Kontrakt.
Volleyzones ruft sinngemäß getPendingTransfers() und bekommt KaderTransferred-Events nach festem Schema zurück. Was hinter dieser Funktion passiert, weiß der Aufrufer nicht und muss es nicht wissen. Heute liest sie aus der geteilten Tabelle. Morgen macht sie einen HTTP-Call zum KADR-Backend, übermorgen antwortet einfach Claude oder Gemini.
Der Kontrakt - Event-Name, Felder, Ack-Endpoint - bleibt identisch. (Und weil die Events versioniert sind, brechen auch neue Felder die alten Consumer nicht.)
Das ist der ganze Trick. Die Kopplung ist heute real, da mach ich mir nichts vor. Aber das Entkoppeln ist von einem Rewrite auf einen Tausch des Funktions-Bodys geschrumpft. Volleyzones merkt davon: nichts.
Und genau deshalb durfte die erste Version banal sein. Gutes Design heißt nicht, sofort die saubere Lösung zu bauen, es heißt, die Grenze an die richtige Stelle zu legen, damit aus der schmutzigen Lösung später schmerzfrei eine saubere wird.
Donnerstag, 18:45
Drei Abmeldungen, halbes Brett, das iPad mault wegen WLAN. Aber der Kader steht in Volleyzones egal ob die Notification kam oder nicht.
Die Aufstellung sitzt, bevor der erste Ball aufgepumpt ist.
Die Architektur dahinter? Eine Tabelle und ein Kontrakt. Einfache Lösung für ein einfaches Problem mit eingebautem Notausgang für den Tag, an dem es kein einfaches Problem mehr ist.