ProficientNowTechRFCs
Product RFCs/DB Ops/RFC DBOPS 0002

6. Design Rationale

RFC-DBOPS-0002                                                    Section 6
Category: Standards Track                               Design Rationale

6.1 Why This Section Exists

This section documents alternatives considered during the design of the query-layer migration and explains why each was rejected. Each alternative is evaluated against the invariants defined in Section 2.3.


6.2 Rejected Alternatives

6.2.1 Remain on Prisma Client

Description: Keep Prisma Client as the runtime query generator for all tenant SQL operations and address performance concerns through Prisma-level optimizations (query batching, selective includes, raw SQL for hot paths).

Why It Was Attractive:

  • Avoids migration effort and coexistence complexity entirely.
  • Preserves the existing developer workflow and Prisma-generated types.
  • Prisma's ecosystem continues to evolve with potential performance improvements in future versions.

Why It Was Rejected:

  • The Prisma Engine binary interposes between the application and the database, adding serialization and processing overhead that is inherent to the architecture and cannot be eliminated through query-level optimization.
  • Benchmark evidence demonstrates a 46.9% latency reduction on a representative read endpoint when the Prisma Engine is bypassed.
  • Prisma Client does not support per-query schema scoping, requiring continued reliance on search_path mutation for multi-tenant isolation.
  • The per-tenant client caching model creates linear connection growth that cannot be bounded without architectural change.
  • Escaping to $queryRaw for performance-sensitive paths forfeits Prisma's type safety, negating the primary benefit of the ORM.

Conclusion: Remaining on Prisma Client preserves the root causes of query-layer overhead and multi-tenant schema risk. Optimization within Prisma's constraints cannot achieve the overhead reduction or schema safety that a query builder migration provides.


6.2.2 Drizzle ORM

Description: Adopt Drizzle ORM as the replacement query builder instead of Kysely. Drizzle provides a TypeScript-first SQL query builder with schema definition, relational queries, and migration tooling.

Why It Was Attractive:

  • Drizzle provides an ORM-like relational query API that reduces the manual effort of relation hydration compared to Kysely.
  • Drizzle's schema definition can serve as both query type source and migration definition, potentially consolidating tooling.
  • Growing community adoption and active development.

Why It Was Rejected:

  • Drizzle's withSchema() support for PostgreSQL schema-per-tenant patterns is less mature than Kysely's. Schema scoping behavior has had documented issues in multi-schema environments.
  • Drizzle's relational query API, while convenient, adds an abstraction layer with its own query generation logic, reintroducing a category of opacity similar to Prisma's include directive.
  • Adopting Drizzle's schema definition would require maintaining a second schema source alongside the existing Prisma schema, creating drift risk. Using Drizzle without its schema definition reduces it to a query builder with a less proven multi-tenant story than Kysely.
  • The platform already has a working Kysely prototype with benchmark evidence. Drizzle would require a new prototype and validation cycle.
  • Violates the risk profile implied by Invariant 2 (Multi-Tenant Isolation) given the less mature schema scoping.

Conclusion: Drizzle ORM introduces schema-scoping maturity risk and abstraction opacity that Kysely avoids. The existing Kysely prototype and benchmark evidence provide a validated starting point that Drizzle does not offer.


6.2.3 Raw SQL via pg Driver

Description: Replace Prisma Client with raw SQL strings executed directly through the pg driver, without a query builder abstraction.

Why It Was Attractive:

  • Eliminates all query builder overhead; SQL strings are sent directly to the database with zero transformation.
  • Provides complete control over every aspect of the generated SQL.
  • Simpler dependency footprint with no query builder library.

Why It Was Rejected:

  • Raw SQL strings have no compile-time type safety for column references, parameter types, or result shapes.
  • SQL injection risk increases when query composition involves string concatenation for dynamic filters, sorts, and pagination.
  • Maintenance burden scales with endpoint count; each query is an opaque string rather than a composable, typed expression.
  • Refactoring is error-prone because column and table name references are embedded in strings rather than typed identifiers.
  • Violates Invariant 1 (Type Safety).

Conclusion: Raw SQL sacrifices type safety and maintainability for marginal performance gains over a typed query builder. The risk of runtime errors and SQL injection outweighs the benefit of eliminating the query builder layer.


6.2.4 TypeORM

Description: Adopt TypeORM as the replacement query builder. TypeORM is a mature TypeScript ORM with decorator-based entity definitions, a query builder API, and migration tooling.

Why It Was Attractive:

  • Mature ecosystem with long track record in TypeScript projects.
  • Decorator-based entity definitions integrate with NestJS patterns.
  • Built-in migration tooling could consolidate with or replace Prisma Migrate.
  • Active Record and Data Mapper patterns provide flexible usage modes.

Why It Was Rejected:

  • TypeORM's runtime overhead is comparable to Prisma's for complex queries. The decorator-based entity system and query builder add abstraction layers that reintroduce the category of overhead this migration aims to eliminate.
  • TypeORM's query builder generates SQL through its own internal planner, reducing query transparency similarly to Prisma.
  • Schema-per-tenant support in TypeORM requires custom connection management or dynamic schema injection, which does not provide the per-query withSchema() ergonomics that Kysely offers.
  • Adopting TypeORM would require defining entities with decorators alongside the existing Prisma schema, creating a dual-schema maintenance burden.
  • TypeORM has documented performance and stability concerns in high-concurrency environments.

Conclusion: TypeORM reintroduces the runtime overhead and query opacity that motivate this migration. Its schema-per-tenant support lacks the per-query scoping guarantees required by Invariant 2.

On this page