5. Migration Strategy
5.1 Coexistence Model
The migration from Prisma Client to Kysely follows a dual-path
coexistence model. Both query engines operate simultaneously within
backend-main during the migration period. Each endpoint is routed to
either the Prisma path or the Kysely path at the service layer. The
routing is determined by migration status, not by runtime configuration
or feature flags.
5.1.1 Coexistence Architecture
During coexistence, the @pnats/schema-manager package continues to
provide PrismaService and database configuration. A new Kysely
service (or provider) is introduced alongside PrismaService in the
NestJS dependency injection container. Tenant module services that have
been migrated inject the Kysely provider; services that have not been
migrated continue to inject PrismaService.
| Aspect | Prisma Path (during coexistence) | Kysely Path (during coexistence) |
|---|---|---|
| Query generation | TenantPrismaClient via PrismaService | Kysely instance via Kysely provider |
| Connection pool | Per-tenant Prisma Engine internal pool | Shared pg.Pool |
| Schema scoping | Implicit via client configuration | Explicit via withSchema(tenantId) |
| Type safety | Prisma-generated types | Kysely database interface types |
| Rollback mechanism | Original Prisma service code remains in place | Revert injection to PrismaService |
5.1.2 Connection Budget
During coexistence, total database connections are the sum of the
pg.Pool maximum and the aggregate Prisma Engine connections for
non-migrated endpoints. The migration strategy MUST monitor connection
utilization and adjust pool sizing if the combined connection count
approaches PgBouncer or PostgreSQL limits.
5.2 Phase 1 — High-Read Endpoints
Phase 1 targets endpoints characterized by high read frequency and measurable Prisma overhead. These endpoints provide the clearest latency improvement signal and the lowest risk surface for initial migration.
5.2.1 Candidate Selection Criteria
| Criterion | Rationale |
|---|---|
| High request frequency | Maximizes aggregate latency savings |
| Read-only or read-dominant | Avoids write-path complexity in initial migration |
Relation hydration via include | Benefits from explicit join control |
| Existing benchmark data | Validates improvement against measured baseline |
| Stable API contract | Reduces risk of concurrent API and query-layer changes |
5.2.2 Initial Candidate Endpoints
The company list endpoint (GET /api/v1/company) is the first
migration candidate based on the existing benchmark evidence. Additional
Phase 1 candidates SHOULD be selected from modules with similar
characteristics:
| Module | Endpoint Pattern | Rationale |
|---|---|---|
| Company | List with relation hydration | Benchmark evidence: 46.9% latency reduction |
| Positions | List with submission and contact joins | High-frequency list view with nested relations |
| Candidates | List with submission and status joins | Read-heavy with complex relation graph |
| Submissions | List with candidate and position joins | Frequently accessed with deep nesting |
5.3 Phase 2 — Complex Query Endpoints
Phase 2 extends migration to endpoints with complex query composition: dynamic filters, search, sorting formulas, and multi-predicate logic.
5.3.1 Complexity Factors
| Factor | Description |
|---|---|
| Dynamic filter composition | Filters constructed from filter and filterLogic request parameters |
| Full-text search | PostgreSQL tsvector search queries currently using Prisma's search parameter |
| Sort formulas | Custom sort expressions including sortFormula and multi-column ordering |
| Table-scoped views | Queries parameterized by tableId for user-configured table layouts |
| Conditional relation loading | Relation subsets that vary based on request context |
5.3.2 Migration Approach
Complex query endpoints MUST be migrated by reconstructing the full query composition logic in Kysely's query API. Each endpoint's filter builder, search integration, and sort logic MUST be reimplemented to produce SQL equivalent to the Prisma path for all supported parameter combinations.
5.4 Phase 3 — Write Operations
Phase 3 migrates create, update, and delete operations from Prisma Client to Kysely.
5.4.1 Write-Path Considerations
| Consideration | Description |
|---|---|
| Transaction semantics | Multi-step writes that use Prisma's $transaction MUST be replicated using Kysely's transaction |
| Nested writes | Prisma's nested create and connectOrCreate MUST be decomposed into explicit insert sequences |
| Optimistic concurrency | Any version-based concurrency checks MUST be preserved in the Kysely implementation |
| Side effects | Post-write hooks or event emissions tied to Prisma middleware MUST be replicated |
| Return values | Prisma's automatic return of created/updated records MUST be matched via RETURNING clauses |
5.4.2 Transaction Boundaries
Kysely's transaction method provides explicit transaction control.
Write operations that currently use Prisma's interactive transactions
MUST be migrated to Kysely transactions with equivalent isolation
semantics. The transaction scope MUST remain within the same tenant
schema, scoped via withSchema().
5.5 Validation Gates
Each endpoint MUST pass through validation gates before its Prisma path is deactivated. Validation gates enforce Invariant 3 (Feature Parity) and Invariant 4 (Zero Data Regression).
5.5.1 Gate Definitions
| Gate | Requirement |
|---|---|
| Unit test parity | All existing unit tests MUST pass against the Kysely implementation without modification |
| Integration test parity | All existing integration tests MUST pass against the Kysely implementation without modification |
| Response equivalence | A dual-execution mode MUST compare Prisma and Kysely responses for a defined sample of production inputs |
| Performance validation | P50 and P95 response times MUST not regress compared to the Prisma baseline for the same endpoint |
| Schema coverage | All supported query parameters (filters, sorts, search, pagination) MUST produce correct results |
5.5.2 Dual-Execution Validation
During validation, an endpoint MAY operate in dual-execution mode where both the Prisma and Kysely paths execute for the same request. The Prisma result serves the response while the Kysely result is compared asynchronously. Discrepancies are logged for investigation.
Dual-execution mode MUST be time-bounded and MUST NOT be used in production for endpoints with write side effects.
5.6 Rollback Mechanics
Rollback capability (Invariant 5) is maintained through code-level retention of the Prisma implementation.
5.6.1 Rollback Approach
| Aspect | Description |
|---|---|
| Code retention | Prisma service methods MUST NOT be deleted until the Kysely path passes all validation gates |
| Injection switching | Rollback is achieved by reverting the NestJS provider injection from the Kysely provider to PrismaService |
| Data compatibility | No schema changes are introduced by the query-layer migration; rollback does not require data migration |
| Connection pool cleanup | If Prisma clients are deactivated, rollback reactivates the PrismaManager client cache for affected tenants |
5.6.2 Rollback Scope
Rollback operates at the endpoint level. Reverting one endpoint to its Prisma path does not affect other endpoints already validated on the Kysely path. This granularity allows surgical rollback without a full system-wide revert.