Skip to content

Task 1-1: Async Queue Pattern cho Rental Operations

Phase: 1 Priority: High Module: rental Depends 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ào RentalService (hoặc RentalController). Tạo RentalProcessor class mới với @Processor(QUEUE_NAME.RENTAL) decorator. Thêm queue name mới vào QUEUE_NAME constants. Import BullModule.registerQueue({ name: QUEUE_NAME.RENTAL }) vào RentalModule. 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.ts với RentalProcessor class:
    @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 RentalModule import BullModule.registerQueue(...) cho mỗi queue mới
  • [ ] Controller trả { queued: true, jobId } với HTTP 202

Verification / Acceptance Criteria

  • [ ] POST /rental/create-rent-nft trả 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.ts
  • src/rental/rental.processor.ts (tạo mới)
  • src/rental/rental.module.ts
  • src/rental/rental.controller.ts
  • src/constants/queue-name.constants.ts (hoặc tương đương)