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_
相关产品推荐
相关产品推荐

