为自定义TypeScript Database类添加事务能力的优化方案咨询
为自定义Postgres封装类添加事务能力的优化方案
现有代码
我有一个封装Postgres连接和查询的自定义Database类:
export default class Database { private pool: Pool; constructor(pool: Pool) { this.pool = pool; } public async someQuery(params: ISomeQueryParams) { // 执行具体查询逻辑 } }
需求目标
需要为该类添加事务能力,实现两种调用方式:
- 非事务查询:
const db = new Database(pool); await db.someQuery(params);
- 事务查询:
const tx = db.startTx(); // db是Database实例 await tx.someQuery(params);
同时要求避免重复编写查询代码。
现有方案的缺陷
我尝试了两种方案,但都存在问题:
1. 自工厂类方案
将pool字段改为兼容连接池或单连接的client,新增启动事务方法返回自身新实例:
export default class Database { private client: Pool | PoolClient; constructor(client: Pool | PoolClient) { this.client = client; } public async someQuery(params: ISomeQueryParams) { // 执行具体查询逻辑 } public async startTx(): Database { const tx = await this.client.connect(); await tx.query('BEGIN'); return new Database(tx); } }
缺陷:非事务实例也会拥有事务专属方法(比如startTx),事务实例同样保留了不需要的能力,不符合单一职责。
2. 事务工厂类方案
新增Transaction类,Database的startTx生成该类实例:
export default class Database { private pool: Pool; constructor(pool: Pool) { this.pool = pool; } public async someQuery(params: ISomeQueryParams) { // 执行具体查询逻辑 } public async startTx(): Transaction { const tx = await this.pool.connect(); await tx.query('BEGIN'); return new Transaction(tx); } } class Transaction { private client: PoolClient; constructor(client: PoolClient) { this.client = client; } public async someQuery(params: ISomeQueryParams) { // 执行具体查询逻辑,和Database类中代码重复 } }
缺陷:每个查询方法都要在Database和Transaction中重复编写,维护成本高。
优化解决方案
可以通过提取公共查询逻辑到抽象基类,同时分离事务和非事务实例的能力:
方案:抽象基类+子类分离
- 定义抽象基类,封装所有公共查询逻辑,依赖通用的查询执行器:
abstract class BaseQueryExecutor { protected abstract getExecutor(): Pool | PoolClient; public async someQuery(params: ISomeQueryParams) { const executor = this.getExecutor(); // 这里编写统一的查询逻辑,示例: const result = await executor.query('SELECT * FROM some_table WHERE id = $1', [params.id]); return result.rows; } // 其他公共查询方法都写在这里 }
- 实现
Database类(非事务),继承基类并返回连接池作为执行器:
export default class Database extends BaseQueryExecutor { private pool: Pool; constructor(pool: Pool) { super(); this.pool = pool; } protected getExecutor(): Pool { return this.pool; } // 仅非事务实例拥有的方法:启动事务 public async startTx(): Transaction { const client = await this.pool.connect(); await client.query('BEGIN'); return new Transaction(client); } }
- 实现
Transaction类(事务),继承基类并返回事务客户端作为执行器,同时添加事务控制方法:
export class Transaction extends BaseQueryExecutor { private client: PoolClient; constructor(client: PoolClient) { super(); this.client = client; } protected getExecutor(): PoolClient { return this.client; } // 事务专属方法 public async commit() { await this.client.query('COMMIT'); this.client.release(); } public async rollback() { await this.client.query('ROLLBACK'); this.client.release(); } }
方案优势
- 避免重复代码:所有查询逻辑只在
BaseQueryExecutor中编写一次,两个子类自动继承 - 职责分离:
Database仅负责非事务查询和启动事务,Transaction仅负责事务内查询和事务控制 - 类型安全:非事务实例看不到事务专属方法,事务实例也不会有启动事务的方法,符合预期调用逻辑
使用示例
// 非事务查询 const db = new Database(pool); const data = await db.someQuery({ id: 1 }); // 事务查询 const tx = await db.startTx(); try { await tx.someQuery({ id: 2 }); await tx.commit(); } catch (err) { await tx.rollback(); throw err; }
内容的提问来源于stack exchange,提问作者Ahmet Yazıcı
相关产品推荐
相关产品推荐

