Rezept: Tech-Stack-Migration
Dieses Rezept führt durch die Planung und Durchführung einer Technologie-Migration mit SpecForge. Wir verwenden die Migration einer Node.js-API von Express 4 zu Fastify als Beispiel — eine häufige Migration, die Routing, Middleware, Plugins und Fehlerbehandlung berührt.
Derselbe Workflow gilt für jede Tech-Migration: Framework-Upgrades, Datenbank-Engine-Wechsel, ORM-Ersetzungen oder Sprach-Upgrades.
Vor dem Start
Migrationen sind risikoreich, weil sie viele Dateien berühren und das Verhalten auf subtile Weise brechen können. SpecForge reduziert dieses Risiko durch:
- Erstellen einer funktionalen Paritäts-Spec vor jeglichen Code-Änderungen
- Generieren von Tests, die überprüfen, ob das Verhalten vor und nach der Migration übereinstimmt
- Bereitstellen eines phasenweisen Migrationsplans mit Rollback bei jedem Schritt
- Erkennen von Abweichungen während der Migration
Schritt 1 — Quell-Codebasis analysieren
Prompt:
Reverse engineer my codebase at /Users/me/my-app/src for project proj_abc123Dies erstellt Specs aus der aktuellen Implementierung — dokumentiert, was die App tatsächlich tut. Diese Specs werden zur funktionalen Paritäts-Basislinie.
Schritt 2 — Migrationsumfang analysieren
Prompt:
Plan a migration from Express to Fastify for project proj_abc123Was zu erwarten ist:
## Migration Analysis: Express 4 → Fastify 5
### Scope
- 23 route files
- 8 middleware (auth, logging, rate-limit, cors, compression, error, validation, cache)
- 4 error handlers
- 2 WebSocket integrations (socket.io → @fastify/websocket)
- 12 test files that mock express internals
### Equivalence Map
Express → Fastify
─────────────────────────────────────────
app.use(middleware) → fastify.addHook / fastify.register(plugin)
app.get('/path', handler) → fastify.get('/path', { schema }, handler)
req.body → request.body (same)
res.json(data) → reply.send(data)
res.status(201).json(data) → reply.code(201).send(data)
next(err) → throw err (or return reply.send(err))
express-validator → @fastify/ajv-compiler + JSON Schema
multer → @fastify/multipart
cors (package) → @fastify/cors
helmet → @fastify/helmet
express-rate-limit → @fastify/rate-limit
socket.io → @fastify/websocket + ws directly
### Risk Areas
- High: socket.io migration (no direct equivalent — behavior change)
- High: Middleware order (Fastify lifecycle differs from Express)
- Medium: Error handling (Fastify uses setErrorHandler, not error middleware)
- Low: Route parameters (compatible syntax)
### Estimated effort: 8–14 days
### Recommended approach: Strangler fig (route-by-route)Schritt 3 — Die Migrations-Spec erstellen
Prompt:
Create a spec for migrating from Express to Fastify in project proj_abc123.
Approach: strangler fig — migrate route groups one at a time behind a reverse proxy.Abnahmekriterien, die die Spec enthalten wird:
- Alle 23 Routengruppen verhalten sich vor und nach der Migration identisch (Paritäts-Test-Suite)
- Antwort-Schemas werden mit Fastifys integrierter AJV-Integration validiert
- Authentifizierungs-Middleware zu Fastify-Lifecycle-Hooks (onRequest) migriert
- Fehlerantworten behalten dieselbe JSON-Struktur wie die Express-Implementierung
- WebSocket-Funktionalität erhalten (Migration von socket.io zu @fastify/websocket)
- Keine Regression bei der Antwortzeit — Fastify P99-Latenz ≤ Express P99 × 1,05
- Alle bestehenden Integrationstests bestehen gegen den Fastify-Server
- Migration ohne Ausfallzeit — Express und Fastify laufen während der Übergangsphase gleichzeitig
Schritt 4 — Den Migrationsplan belasten
Prompt:
Challenge the migration spec for project proj_abc123SpecForge wird Randfälle prüfen:
- Was passiert mit laufenden Anfragen beim Express → Fastify-Wechsel?
- Wie werden Session-Cookies während der Übergangsphase behandelt?
- Was, wenn ein Feature-Flag fehlschlägt und einige Benutzer Express, andere Fastify treffen?
- Gibt es Express-spezifische Middleware ohne Fastify-Äquivalent?
Schritt 5 — Funktionale Paritäts-Tests generieren
Prompt:
Generate tests for the migration spec in project proj_abc123Diese Tests laufen gegen beide Server — Express und Fastify — und stellen identisches Verhalten sicher:
typescript
// parity.test.ts
describe.each([
['Express', expressApp],
['Fastify', fastifyApp],
])('%s — POST /api/orders', (_, app) => {
it('returns 201 with order ID on valid payload', async () => {
const res = await request(app)
.post('/api/orders')
.send({ productId: 'prod_1', quantity: 2 })
expect(res.status).toBe(201)
expect(res.body).toMatchObject({ orderId: expect.any(String) })
})
it('returns 422 on missing productId', async () => {
const res = await request(app)
.post('/api/orders')
.send({ quantity: 2 })
expect(res.status).toBe(422)
})
})Schritt 6 — Den Ausführungsplan generieren
Prompt:
Generate an execution plan for the migration spec in project proj_abc123Wie ein Strangler-Fig-Plan aussieht:
markdown
## Phase 1: Foundation
- [ ] Install Fastify + plugins (fastify-cors, fastify-helmet, etc.)
- [ ] Set up Fastify server alongside Express (different port)
- [ ] Configure reverse proxy (nginx/Caddy) with feature flag routing
- [ ] Write parity test harness that runs against both servers
## Phase 2: Migrate routes (per group — can parallelize groups)
- [ ] Group A: /api/auth routes (low risk, no WebSocket)
- [ ] Group B: /api/users routes (medium risk, validation changes)
- [ ] Group C: /api/orders routes (high risk, payment integration)
- [ ] Group D: /api/admin routes (low risk, internal only)
## Phase 3: Migrate middleware
- [ ] Auth hook (Express middleware → Fastify onRequest)
- [ ] Error handler (next(err) → setErrorHandler)
- [ ] Rate limiting (@fastify/rate-limit)
- [ ] CORS + Helmet (@fastify/cors, @fastify/helmet)
## Phase 4: WebSocket migration
- [ ] Replace socket.io with @fastify/websocket + ws
- [ ] Migrate client-side socket.io → native WebSocket
- [ ] Parity test: WebSocket message flow
## Phase 5: Cutover
- [ ] Switch reverse proxy to 100% Fastify
- [ ] Keep Express running for 48h (rollback window)
- [ ] Remove Express after 48h with no incidents
- [ ] Update PLAN.md and mark migration spec as doneSchritt 7 — Die Migration durchführen
Mit jeder Phase:
- Die Änderungen implementieren
- Paritäts-Tests gegen beide Server ausführen
- Funktionale Parität mit
validatevalidieren - Abweichungen zwischen der Migrations-Spec und der Implementierung erkennen
Prompt nach jeder Phase:
Validate the migration spec against the code at /Users/me/my-app/src for project proj_abc123Schritt 8 — Performance-Validierung
Prompt:
Audit the code at /Users/me/my-app/src for project proj_abc123Prüfen, dass die Fastify-Implementierung mindestens genauso gut wie die Express-Basislinie bei Architektur-Compliance und Code-Qualität abschneidet.
Für Latenz-Validierung das Last-Test-Tool ausführen (k6, Artillery, wrk) gegen beide Server und P99-Latenz vergleichen — das Spec-Kriterium besagt Fastify P99 ≤ Express P99 × 1,05.
Schritt 9 — Architekturentscheidungen dokumentieren
Prompt:
Generate ADRs for the migration spec in project proj_abc123Dies erzeugt:
- ADR-001: Warum Strangler Fig statt Big-Bang-Migration
- ADR-002: @fastify/websocket statt socket.io im Fastify-Kontext
- ADR-003: AJV JSON Schema statt express-validator für Request-Validierung
Schritt 10 — Erkenntnisse festhalten
Prompt:
Capture learning: when migrating from Express to Fastify, middleware order matters more —
Fastify lifecycle hooks (onRequest, preHandler, onSend) must be planned before migration starts,
not discovered during implementationRollback-Plan
Der Strangler-Fig-Ansatz von SpecForge bedeutet, dass ein Rollback immer möglich ist:
| Phase | Rollback-Aktion |
|---|---|
| Während der Migration | Reverse Proxy zurück auf 100% Express umschalten |
| Nach dem Wechsel (< 48 Std.) | Express läuft noch — Proxy umschalten |
| Nach Entfernung von Express | Aus Git-Tag pre-migration wiederherstellen |
Immer den letzten stabilen Express-Commit taggen, bevor begonnen wird: git tag pre-fastify-migration.
Dieses Rezept auf andere Migrationen anwenden
| Migration | Hauptproblem | Empfohlener Ansatz |
|---|---|---|
| Django → FastAPI | Async-Muster, ORM-Unterschiede | Strangler Fig |
| Mongoose → Prisma | Schema-Migration, Query-API | Modul für Modul |
| React-Klassen → Hooks | Verhaltensparität, Lebenszyklus | Komponente für Komponente |
| PostgreSQL → CockroachDB | SQL-Dialekt-Unterschiede | Read-Replica zuerst |
| REST → GraphQL | Client-seitige Breaking Change | Additiv (REST behalten) |
| Node 18 → Node 22 | API-Veralterungen | Zuerst detect_deprecations |