You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为自定义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中重复编写,维护成本高。

优化解决方案

可以通过提取公共查询逻辑到抽象基类,同时分离事务和非事务实例的能力:

方案:抽象基类+子类分离

  1. 定义抽象基类,封装所有公共查询逻辑,依赖通用的查询执行器:
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;
  }

  // 其他公共查询方法都写在这里
}
  1. 实现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);
  }
}
  1. 实现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ı

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:35:30