Kontext
Eine öffentliche GraphQL-Read-API auf einem Stack, der aus dem Support gefallen war. Die Resolver-Schicht hatte sich um frühere Framework- Idiome verhärtet; das Hinzufügen eines Felds war zur Koordinationsübung geworden. Fünfzig+ Konsumenten, Web, native iOS, native Android, Partnerintegrationen und ein internes Marketplace-Produkt, keiner koordinierte Releases mit uns.
Das Team wollte das Framework upgraden. Jeder In-place-Plan, den ich skizzierte, endete in einem Freeze, einem Wartungsfenster oder beidem. Der vorherige Versuch (achtzehn Monate früher, vor meiner Zeit) war aus demselben Grund stehengeblieben.
Die zwei Optionen, die ich abwog
| Option | Risikoprofil | Kosten |
|---|---|---|
| In-place-Upgrade mit Feature-Flags | Hoch, jeder Konsument ist einen Rollback davon entfernt zu brechen; Framework-Upgrade und Resolver-Form-Änderungen sind gekoppelt | Niedriger in der Plumbing; sehr hoch in der Koordination |
| Strangler-Fig: v2 in neuem Repo bauen, parallel laufen lassen, per Consumer-Kohorte hinter Toggles cutovern | Niedrig in jedem Schritt, v1 bedient weiter Traffic; Cutover per Toggle reversibel | Hoch in der Plumbing, DataLoader, Context, Validierung von Hand neu implementieren |
Die In-place-Option hatte zwei nicht-offensichtliche Risiken. Erstens: das Framework-Upgrade und das konsumentensichtbare Verhalten waren gekoppelt. Den Framework-Switch zu flippen, formte die Resolver-Outputs in subtilen Weisen um (Serialisierungsreihenfolge, Error-Envelopes, Default-Field-Handling). Jeder Konsument wettete implizit darauf, dass v2s Outputs zu v1s passten, ohne Möglichkeit, das vor dem Flip zu verifizieren. Zweitens: jedes vorherige In-place-Upgrade in der Team-Historie war stehengeblieben, weil jemand einen Freeze brauchte, den niemand gewähren konnte.
Die Entscheidung
v2 in einem separaten Repository auf einem anderen Framework
(graphql-yoga + hono) als Schwester-Service zu v1 bauen. Kein
gemeinsamer Code mit v1. Parallel laufen lassen, so lange wie nötig
zur Validierung. Per Consumer-Kohorte hinter Feature-Toggles
cutovern, mit Diff-Metriken als Gate.
Ich kam dahin, indem ich die Risikofrage umkehrte. Statt "wie machen wir ein In-place-Upgrade sicher?" fragte ich "was müsste wahr sein, damit ein Parallel-Build falsch wäre?" Die ehrliche Antwort war nur die Plumbing-Kosten. Die konnte ich tragen.
Was ich aufgab
Jeden DataLoader, jede Context-Bindung, jede Runtime-Validierung neu zu implementieren, von Hand, in einem neuen Codebase, während v1 weiter Traffic bediente. Sechs Wochen Resolver-für-Resolver-Portierung, bevor irgendetwas davon produktionsgeformt war. Ein Build-Budget, das sich ein Quartal lang nicht in sichtbaren Feature-Fortschritt übersetzte.
Die saubererausschauende Alternative, Typen oder Clients zwischen v1 und v2 zu teilen, um "die Migration einfacher zu machen", ist die Strangler-Fig-Variante, die scheitert. Jedes Mal, wenn ich es gesehen habe, leaken die Designeinschränkungen des alten Codes in den neuen. Ich erzwang die Disziplin von Null-geteiltem-Code ab Tag eins.
Was eintrat
Das Framework-Upgrade und die Consumer-Migration entkoppelten sauber.
v2 konnte in Dutzenden Weisen falsch sein, ohne dass ein Konsument es
merkte, weil v1 weiter Traffic bediente. Sobald die
Shadow-Diff-Pipeline im dritten Monat
landete, waren die Divergenzen, die sie zutage förderte, die Art, die
kein Paritätstest gefangen hätte: die adTargeting-JSON-String-Key-
Reihenfolge-Eigenheit, die price-info-Feldform-Varianz, eine Handvoll
locale-spezifischer Edge-Cases.
Die Plumbing-Kosten waren real. Phase 1 produzierte drei Wochen lang keine sichtbare Feature-Arbeit. Die Art von Entscheidung, die ein weniger erfahrenes Team innerhalb des ersten Monats zurückgerollt hätte. Ich würde sie wieder verteidigen.
Was ich anders machen würde
Die Diff-Pipeline würde in Woche eins landen, nicht in Woche zehn. Das Strangler-Fig-Framing war korrekt, aber die Confidence-Engine (der v1↔v2-Diff) gehört vor die Resolver, nicht danach. Sechs Wochen Resolver-Bauen ohne irgendetwas produktionsgeformtes zur Validierung bedeuteten, dass die Paritätstests mehr Gewicht trugen, als sie verdient hatten. Den Diff von Tag eins gegen Shadow-geformte Staging-Daten zu zeigen hätte die schlimmsten Divergenzen zwei Monate früher zutage gefördert.