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

Clean Architecture下如何在UseCase层抽象Firebase数据库事务

解决方案:在Clean Architecture中抽象Firebase事务

要在UseCase层实现事务操作且不依赖Firebase具体实现,我们可以通过抽象事务接口+工作单元模式来实现,完全符合Clean Architecture的依赖倒置原则。

步骤1:在Core层定义事务抽象

在你的core包中添加事务相关的抽象接口,确保业务逻辑层完全与外部存储实现解耦:

// core/packages/core/src/transaction.ts
// 标记接口,用于传递事务上下文(Core层无需知道具体实现)
export interface TransactionContext {}

// 事务核心接口,定义事务执行的契约
export interface Transaction {
  // 接收一个带事务上下文的操作函数,返回执行结果
  run<T>(operation: (ctx: TransactionContext) => Promise<T>): Promise<T>;
}

同时修改你的仓库接口,让写操作方法支持接收事务上下文参数:

// core/packages/core/src/repositories/UserRepository.ts
export abstract class UserRepository {
  abstract getUser(userId: string): Promise<User>;
  abstract createUser(user: User): Promise<string>;
  // 添加可选的ctx参数,用于事务场景
  abstract updateUser(user: User, ctx?: TransactionContext): Promise<void>;
}

// core/packages/core/src/repositories/PromotionRepository.ts
export abstract class PromotionRepository {
  // 添加可选的ctx参数
  abstract save(promotion: Promotion, ctx?: TransactionContext): Promise<void>;
}

步骤2:在UseCase中使用抽象事务

修改你的CheckAffiliateCodeUsecase,注入Transaction接口,并用它包裹需要原子执行的写操作:

// core/packages/core/src/usecases/CheckAffiliateCodeUsecase.ts
export class CheckAffiliateCodeUsecase implements Usecase<void> {
  constructor(
    private userRepository: UserRepository,
    private promotionRepository: PromotionRepository,
    private transaction: Transaction // 注入抽象事务依赖
  ) {}

  async execute(userId: string, affiliateCode: string) {
    // 1. 前置业务校验逻辑
    const user = await this.userRepository.getUser(userId);
    if (!user) throw new Error("用户不存在");
    // ...其他校验

    // 2. 准备业务实体
    const promotionForNewUser = Promotion.createPromotion({
      userId: "new-user-id",
      code: affiliateCode,
      // 其他字段
    });
    const promotionForOldUser = Promotion.createPromotion({
      userId: userId,
      code: affiliateCode,
      // 其他字段
    });
    user.upgrade(affiliateCode);

    // 3. 用事务包裹所有写操作,保证原子性
    await this.transaction.run(async (ctx) => {
      await this.promotionRepository.save(promotionForNewUser, ctx);
      await this.promotionRepository.save(promotionForOldUser, ctx);
      await this.userRepository.updateUser(user, ctx);
    });
  }
}

步骤3:在应用层实现Firebase事务适配

在你的应用实现层(比如Firebase相关的包),实现Transaction接口,对接Firestore的具体事务逻辑:

// apps/your-app/src/infrastructure/firebase/FirestoreTransaction.ts
import { Transaction as CoreTransaction, TransactionContext } from '@your-project/core';
import { firestore } from 'firebase-admin';

// 实现Core层的Transaction接口
export class FirestoreTransaction implements CoreTransaction {
  constructor(private db: firestore.Firestore) {}

  async run<T>(operation: (ctx: TransactionContext) => Promise<T>): Promise<T> {
    // 调用Firestore的runTransaction方法,传递内部事务实例给操作函数
    return this.db.runTransaction(async (firestoreTx) => {
      return operation(firestoreTx);
    });
  }
}

同时修改仓库的Firebase实现,支持根据事务上下文选择操作方式:

// apps/your-app/src/infrastructure/firebase/repositories/UserRepositoryImpl.ts
import { UserRepository, User, TransactionContext } from '@your-project/core';
import { firestore } from 'firebase-admin';

export class UserRepositoryImpl implements UserRepository {
  constructor(private db: firestore.Firestore) {}

  async getUser(userId: string): Promise<User> {
    const doc = await this.db.doc(`users/${userId}`).get();
    if (!doc.exists) throw new Error("用户不存在");
    return User.fromJSON(doc.data()); // 假设User实体有fromJSON静态方法
  }

  async updateUser(user: User, ctx?: TransactionContext): Promise<void> {
    const userRef = this.db.doc(`users/${user.id}`);
    const userData = user.toJSON(); // 假设User实体有toJSON方法

    // 判断是否处于Firestore事务上下文
    if (ctx && ctx instanceof firestore.Transaction) {
      await ctx.update(userRef, userData);
    } else {
      await userRef.update(userData);
    }
  }

  // ...其他方法实现
}

// PromotionRepositoryImpl类似实现
export class PromotionRepositoryImpl implements PromotionRepository {
  constructor(private db: firestore.Firestore) {}

  async save(promotion: Promotion, ctx?: TransactionContext): Promise<void> {
    const promotionRef = this.db.doc(`promotions/${promotion.id}`);
    const promotionData = promotion.toJSON();

    if (ctx && ctx instanceof firestore.Transaction) {
      await ctx.set(promotionRef, promotionData, { merge: true });
    } else {
      await promotionRef.set(promotionData, { merge: true });
    }
  }
}

步骤4:依赖注入配置

最后,在你的应用启动时,将Firebase的具体实现注入到UseCase中:

// apps/your-app/src/di/config.ts
import { firestore } from 'firebase-admin';
import { CheckAffiliateCodeUsecase } from '@your-project/core';
import { FirestoreTransaction, UserRepositoryImpl, PromotionRepositoryImpl } from './infrastructure/firebase';

// 初始化Firestore实例
const db = firestore();

// 创建具体实现实例
const userRepo = new UserRepositoryImpl(db);
const promotionRepo = new PromotionRepositoryImpl(db);
const transaction = new FirestoreTransaction(db);

// 创建UseCase实例,注入抽象依赖
const checkAffiliateCodeUsecase = new CheckAffiliateCodeUsecase(
  userRepo,
  promotionRepo,
  transaction
);

这样,你的UseCase层完全不知道Firebase的存在,只依赖Core层的抽象接口,同时保证了多写操作的原子性。如果未来替换存储方案(比如换成PostgreSQL),只需要实现新的Transaction和仓库实现即可,无需修改业务逻辑。

内容的提问来源于stack exchange,提问作者mef_

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 17:57:16