Task 1-1: Async Queue Pattern cho Rental Operations
Phase: 1 Priority: High Module:
rentalDepends on: Không có Reference: docs/bountyhunter-blockchain-p2/details/feature-nft-rental/SPEC.md
Background
RentalService hiện đang gọi blockchain synchronously — HTTP request phải chờ toàn bộ quá trình on-chain transaction hoàn tất (có thể 10-30 giây trên các chain chậm). Điều này gây ra HTTP timeout risk, block thread Node.js, và trải nghiệm người dùng kém. Khác với MarketplaceService đã dùng Bull queue, RentalService chưa được migrate sang async pattern.
Tasks
Note: Cần thêm
@InjectQueue(QUEUE_NAME.RENTAL)vàoRentalService(hoặcRentalController). TạoRentalProcessorclass mới với@Processor(QUEUE_NAME.RENTAL)decorator. Thêm queue name mới vàoQUEUE_NAMEconstants. ImportBullModule.registerQueue({ name: QUEUE_NAME.RENTAL })vàoRentalModule. Controller sau khi refactor trả về{ queued: true, jobId: job.id }thay vì kết quả on-chain.
- [ ] Thêm queue name mới vào constants:
// Trong queue-name.constants.ts hoặc tương đương: export enum QUEUE_NAME { // ...existing... RENTAL_CREATE = 'RENTAL_CREATE', RENTAL_RETURN = 'RENTAL_RETURN', RENTAL_RENT = 'RENTAL_RENT', RENTAL_CANCEL = 'RENTAL_CANCEL', } - [ ] Tạo
rental.processor.tsvớiRentalProcessorclass:@Processor(QUEUE_NAME.RENTAL_CREATE) export class RentalProcessor { private readonly logger = new Logger(RentalProcessor.name); constructor(private readonly blockchainUtil: BlockchainUtil) {} @Process() async handleCreateRentNft(job: Job): Promise<void> { const { webhookUrl, ...data } = job.data; this.logger.log(`[RENTAL] action=CREATE_RENT jobId=${job.id} status=started`); try { const txHash = await this.blockchainUtil.createRentNft(data); await job.update({ ...job.data, txHash }); await postWithRetry(webhookUrl, { status: 'SUCCESS', txHash }); this.logger.log(`[RENTAL] action=CREATE_RENT jobId=${job.id} status=success txHash=${txHash}`); } catch (error) { this.logger.error(`[RENTAL] action=CREATE_RENT jobId=${job.id} error=${error.message}`); await this.notifyWebhookOrRetry(job, webhookUrl, { status: 'FAILED', error: error.message }); } } // ... handlers cho RETURN, RENT, CANCEL tương tự } - [ ] Refactor
RentalService— thay vì gọi blockchain trực tiếp, push job vào queue:async createRentNft(dto: CreateRentNftDto): Promise<{ queued: boolean; jobId: string }> { const job = await this.rentalQueue.add(dto); this.logger.log(`[RENTAL] action=CREATE_RENT_QUEUED jobId=${job.id}`); return { queued: true, jobId: String(job.id) }; } - [ ] Áp dụng cho cả 4 operations:
createRentNft,returnRentNft,rentNft,cancelRentNft - [ ] Cập nhật
RentalModuleimportBullModule.registerQueue(...)cho mỗi queue mới - [ ] Controller trả
{ queued: true, jobId }với HTTP 202
Verification / Acceptance Criteria
- [ ]
POST /rental/create-rent-nfttrả về HTTP 202 với{ queued: true, jobId: "..." }ngay lập tức (không chờ on-chain) - [ ] Sau khi job hoàn thành → webhook callback được gọi với
{ status: 'SUCCESS', txHash: ... } - [ ] Khi blockchain timeout → job retry theo
QUEUE_MAX_RETRY, không timeout HTTP - [ ] 4 operations đều được queue hóa, không còn synchronous blockchain call
- [ ] TypeScript compile không có lỗi
Files to Modify
src/rental/rental.service.tssrc/rental/rental.processor.ts(tạo mới)src/rental/rental.module.tssrc/rental/rental.controller.tssrc/constants/queue-name.constants.ts(hoặc tương đương)