Skip to content

Latest commit

 

History

History
157 lines (121 loc) · 13.6 KB

File metadata and controls

157 lines (121 loc) · 13.6 KB

Migration Plan: rxpress Library Extraction

Always ensure this document remains up-to-date with progress

Goals

  • Extract the reusable RxJS-driven web server stack from packages/examples/server into packages/rxpress.
  • Ship a TypeScript-friendly package that external apps can consume via npm i rxpress.
  • Leave packages/examples/server as a thin example/host that depends on the published library.

Current Status Snapshot

  • packages/rxpress/lib already mirrors some server services but is incomplete (mixed .ts sources, no build pipeline, no exported entry point).
  • packages/examples/server owns the production-ready implementations (EventService, Logger, ConfigService, RPC route/event definitions, CRON wiring).
  • Workspace tooling uses Nx + npm workspaces; there is no dedicated publish flow for rxpress.

Phase 1 – Inventory & Hardening

  1. Audit server features: catalogue route/event/cron helpers, metrics, logging, KV store expectations, and config contracts still living only in packages/examples/server.
  2. Align TypeScript configs: create packages/rxpress/tsconfig.json (emit to dist/, ES2022 target, declaration output) and add build/test scripts.
  3. Drop ad-hoc TS in lib/: relocate sources to src/ to match TS build output, keeping .d.ts generation in mind.

Phase 1 Findings

  • packages/examples/server/src/main.ts orchestrates express server setup, dynamic loading of routes/events, and uses ConfigService.__rootDir plus glob to discover handlers.
  • Logging relies on Logger wrapping EventService (app::log topic) with env-driven levels; no equivalent concrete logger bundled in rxpress.
  • KVService provides optional file persistence and seeds keys relative to ConfigService.__rootDir; rxpress exposes only KVBase interface without storage implementation.
  • Events (src/events/*.app-log.js) demonstrate log sinks and expect logger/trigger context; cron wiring currently exists only in packages/rxpress (library) and is unused by server example.
  • Metrics, process handlers, and OTEL setup live in rxpress/lib/services/metrics.service.ts but server main.ts still configures emits/topics manually; need unified bootstrap.
  • Type declarations in packages/examples/server/src/types/rpc.ts depend on concrete Logger and KVService; library variant already abstracts these via interfaces.

Phase 2 – Core Library Port

  1. Port services/types: move ConfigService, EventService, route orchestrator, metrics, cron, and typing utilities from server into rxpress/src, merging with any partial implementations already there.
  2. Design pluggable infrastructure: define minimal logger/KV/process handler interfaces so consumers can attach adapters (pino/console/morgan, Redis/in-memory, etc.); keep rxpress agnostic to concrete implementations and avoid bundling defaults.
  3. Rebuild orchestrator API: expose init/start/stop/load entry points in src/index.ts, ensure dynamic loader paths work outside the monorepo (avoid ConfigService.__rootDir assumptions).

Phase 2 Progress

  • Switched rxpress build to NodeNext ESM output with explicit .js extensions and runtime guards.
  • Declared package dependencies (express, glob, cron, RxJS, OTEL) under packages/rxpress/package.json to satisfy TypeScript and publish requirements.
  • Added ts-node based smoke test and tsconfig tuned for src/ sources; npm run build --workspace rxpress now succeeds.
  • Logger and KV adapters stay consumer-supplied; rxpress exposes interfaces plus new config hooks for rootDir/envFiles.

Phase 3 – Packaging & Verification

  1. Wire build tooling: add npm run build to compile to dist, update package.json (main, exports, types, clean description, semver). Include files: ["dist"] and remove TypeScript sources from publish payload.
  2. Add tests/examples: port existing smoke tests, add integration tests that spin up express app via library; document fixtures under __tests__.
  3. Update docs: author README usage guide and migration notes; ensure AGENTS.md references new workflow if needed.
  4. Refactor packages/examples/server: replace local service imports with rxpress exports; keep only project-specific routes/events/config.
  5. Provide wrapper bootstrap: update server startup scripts to call rxpress.init()/start() instead of bespoke logic; confirm environment loading still works via new API.

Phase 3 Progress

  • Cleaned package.json metadata, set version 0.1.0, and limited publish artifacts to dist + README.

  • Added README quick start documenting adapter expectations.

  • Documented helper adapters under src/helpers and wired README examples to real implementations.

  • Added ts-node driven integration test with stub adapters; skips gracefully when sandbox blocks listening sockets.

  • Added CHANGELOG and publishing checklist to document release flow.

  • Added cron integration smoke test covering scheduler + event pipeline.

  • Automated semantic-release workflow with changelog, npm publish, and git tagging.

  • README helper example covered by automated integration test.

  • Observability dashboard plots “Total rxpress requests” via Prometheus (other panels pending).

Phase 3 – Upcoming Focus

  • Add automated test that compiles the README helper example to guard against regressions.
  • Document how to run the npm run release workflow (semantic-release) in CONTRIBUTING/README.

Phase 4 – Consumer Migration

  1. Retire duplicated config service: removed local ConfigService after exposing root resolution via library helpers.

Phase 4 Progress

  • Server example now calls rxpress.init/start, registers routes/events through the library, and relies on shared logger/KV adapters.
  • Local ConfigService removed; server uses shared helper configuration.

Phase 5 – Publish Readiness

  1. End-to-end validation: run npm run build --workspace rxpress, npm pack to inspect tarball, and test consuming it from a sample app (npm init -y && npm install ../rxpress-*.tgz).
  2. Lint & format automation: wired ESLint + Prettier into CI workflow and added Husky/lint-staged pre-commit checks.
  3. Version & release: establish release checklist, bump version, add CHANGELOG entry, and prepare npm publish workflow.
  4. Post-publish integration: update server workspace to depend on published semver (instead of relative path) and verify npm install rxpress works in a clean environment.
  5. Observability stack: added Docker Compose (OTel Collector + Grafana) and default server telemetry configuration. Sample Grafana dashboard auto-provisioned via docker-compose.

Phase 6 – gRPC Handler Support (polyglot-ready)

  1. Design bridge service: create GrpcBridgeService under packages/rxpress/src/services/grpc.service.ts that loads handler_bridge.proto, hosts Invoker + ControlPlane, and exposes init, invokeRoute, and invokeEvent APIs. Move the proto into the library (e.g., packages/rxpress/src/grpc/proto/handler_bridge.proto) and ensure it ships in the bundle.
  2. Extend configuration types: update RxpressConfig, RPCConfig, and EventConfig to support { kind: 'grpc'; handlerName: string; target?: string; timeoutMs?: number; metadata?: Record<string,string>; } alongside existing local handlers. Provide grpc root config (proto path, default target, optional local handler directories) in packages/rxpress/src/types/index.ts.
  3. Integrate with routes/events: modify RouteService and EventService so entries marked kind: 'grpc' forward requests via GrpcBridgeService, carry run IDs/span context in InvokeRequest.meta, and translate InvokeResponse payloads/errors back into current HTTP/event semantics.
  4. Context bridging: implement ControlPlane handling that maps remote log, emit, and kv operations onto Rxpress’ logger, EventService.emit, and KV adapters (including run-scoped KV keys). Add cleanup to release run scopes when the stream closes.
  5. Local handler bootstrap: add optional grpc.localHandlers config that loads TypeScript handlers (mirroring grpc_example/orchestrator/handlers) so existing projects can adopt gRPC without remote processes. Ensure future remote handlers can reuse the same proto without code changes.
  6. Testing matrix: create integration tests in packages/rxpress/__tests__/ covering (a) HTTP route invoking a gRPC handler, (b) event subscription invoking a gRPC handler, and (c) run-scope propagation across the boundary. Tests should assert log, emit, and kv round-tripping via ControlPlane.
  7. Documentation updates: document gRPC usage in packages/rxpress/docs/ (new grpc.md, references in routing/events guides, README quick links) including polyglot handler guidance, configuration examples, and local vs remote handler deployment notes.
  8. Future remote support notes: documented the remaining gRPC roadmap and stood up first-class health checks plus file-based discovery refresh. Follow-on items now focus on:
    • Extending discovery beyond static files (e.g., DNS/service registry adapters, dynamic scale-out).
    • Streaming RPC support (allow long-lived bidi streams for real-time workflows).
    • Operational tooling (metrics on bridge throughput/errors, admin endpoints to list active handler connections).

gRPC Next steps

  • Implement service discovery/health checks so the bridge can target multiple remote handler hosts.
  • Add mTLS to secure traffic between the Node.js orchestrator and remote language runtimes.
  • Extend your handlers with streaming RPCs when you need long-lived bidirectional workflows.

Considerations & Risks

  • Keep logger/KV integration fully adapter-based; document required interface signatures so teams can slot in console, pino, Redis, memory, or other tooling without coupling.
  • Ensure dynamic glob loaders resolve correctly once compiled to dist/ (might require switching to import.meta.url relative paths).
  • Decide how much telemetry/metrics functionality is core vs optional to keep install size reasonable.
  • Document Node.js version requirement (Node 20+) and any peer dependencies (e.g., express, rxjs).
  • Plan for backwards compatibility if external consumers expect existing server behavior; provide migration notes or wrappers.

Phase 6 – Clustered Runtime

Cluster Rollout Plan

  • Config & Initialization services always see { enabled: boolean; workers: number; hashSelectors: [...] }.
    • Update packages/rxpress/src/types to expose the new config, and add validation so values ≤0 fall back to CPU count.
  • ClusterService (Primary Role)
    • Create ClusterService.start(config) invoked from rxpress.createServer when worker count > 1.
    • Primary responsibilities:
      • Fork N workers, track PIDs, restart on exit if restartOnExit !== false.
      • Stand up a lightweight TCP server (sticky server) bound to the configured WS port. On connection, inspect headers for x-forwarded-for first, else use socket.remoteAddress, hash to worker index, and hand off the socket via worker.send({ type: 'sticky-connection' }, socket).
      • Forward OS signals (SIGINT/SIGTERM) to workers, await “shutdown-complete” acknowledgements, then close dispatcher and exit.
      • Emit topology/trace events (SYS::CLUSTER::*) for observability.
  • Worker Bootstrap Path
    • Workers run existing Express HTTP server as-is; when notified (process.on('message')) of a sticky upgrade, recreate the upgrade by calling a new WSSService.attachSocket({ request, socket, head }).
    • Ensure workers register WSSService.configureClusterBridge({ workerId, sendToPrimary }) so broadcasts can loop through the primary.
  • Broadcast Fan-out & Trace Preservation
    • Keep SYS::WSS::BROADCAST event handler per worker; modifies WSSService.publish to:
      • Dispatch locally.
      • Send serialized envelopes to primary when origin is worker.
    • Primary listens on WSSService.onPublish (registered within ClusterService) to rebroadcast envelopes to all other workers, preserving traceContext and skipping the origin worker.
  • Lifecycle & Health
    • Workers send READY/SHUTDOWN messages; primary maintains a map to support graceful stop and restarts.
    • Incorporate restart throttling (e.g., max restarts in 1 min) to avoid thrash.
  • Stateless HTTP Handling
    • HTTP requests continue to use Node’s server.listen per worker (SO_REUSEPORT) since stateless behavior is acceptable; sticky dispatcher only handles WS upgrades to maintain session affinity.
  • Documentation & Tests
    • Update PLAN.md Phase 6 checklist as milestones complete.
    • Add docs: new cluster.md in docs/, README section, and example server configuration snippet.
    • Write integration test (skippable if clustering unsupported) that starts 2-worker cluster, broadcasts from worker A, asserts worker B receives via bridge.
    • Adjust existing tests to account for new config defaults.
  • Add cluster configuration block (worker count, dispatcher strategy, graceful shutdown).
  • Introduce startCluster/stopCluster orchestration within rxpress.createServer/rxpress.stop (primary vs worker roles).
  • Implement primary net dispatcher for sticky sockets (or SO_REUSEPORT fallback).
  • Replace direct WebSocket writes with RxJS broadcast subject.
  • Route broadcasts through EventService.emit to retain span/run context.
  • Fan-out payloads from primary to workers and handle worker-side delivery.
  • Ensure graceful shutdown (signal forwarding, acknowledgements).
  • Document clustering usage and update example server to showcase it.