Skip to content

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:

  1. Erstellen einer funktionalen Paritäts-Spec vor jeglichen Code-Änderungen
  2. Generieren von Tests, die überprüfen, ob das Verhalten vor und nach der Migration übereinstimmt
  3. Bereitstellen eines phasenweisen Migrationsplans mit Rollback bei jedem Schritt
  4. 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_abc123

Dies 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_abc123

Was 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:

  1. Alle 23 Routengruppen verhalten sich vor und nach der Migration identisch (Paritäts-Test-Suite)
  2. Antwort-Schemas werden mit Fastifys integrierter AJV-Integration validiert
  3. Authentifizierungs-Middleware zu Fastify-Lifecycle-Hooks (onRequest) migriert
  4. Fehlerantworten behalten dieselbe JSON-Struktur wie die Express-Implementierung
  5. WebSocket-Funktionalität erhalten (Migration von socket.io zu @fastify/websocket)
  6. Keine Regression bei der Antwortzeit — Fastify P99-Latenz ≤ Express P99 × 1,05
  7. Alle bestehenden Integrationstests bestehen gegen den Fastify-Server
  8. 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_abc123

SpecForge 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_abc123

Diese 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_abc123

Wie 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 done

Schritt 7 — Die Migration durchführen

Mit jeder Phase:

  1. Die Änderungen implementieren
  2. Paritäts-Tests gegen beide Server ausführen
  3. Funktionale Parität mit validate validieren
  4. 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_abc123

Schritt 8 — Performance-Validierung

Prompt:

Audit the code at /Users/me/my-app/src for project proj_abc123

Prü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_abc123

Dies 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 implementation

Rollback-Plan

Der Strangler-Fig-Ansatz von SpecForge bedeutet, dass ein Rollback immer möglich ist:

PhaseRollback-Aktion
Während der MigrationReverse Proxy zurück auf 100% Express umschalten
Nach dem Wechsel (< 48 Std.)Express läuft noch — Proxy umschalten
Nach Entfernung von ExpressAus 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

MigrationHauptproblemEmpfohlener Ansatz
Django → FastAPIAsync-Muster, ORM-UnterschiedeStrangler Fig
Mongoose → PrismaSchema-Migration, Query-APIModul für Modul
React-Klassen → HooksVerhaltensparität, LebenszyklusKomponente für Komponente
PostgreSQL → CockroachDBSQL-Dialekt-UnterschiedeRead-Replica zuerst
REST → GraphQLClient-seitige Breaking ChangeAdditiv (REST behalten)
Node 18 → Node 22API-VeralterungenZuerst detect_deprecations