♠♠기술 구현

카테고리별 랭킹 구현 과정

흑백 개발자 2024. 11. 6. 10:29
728x90
  • 지피티 질문:
    RankingTabScreen 의 카드를 보면 RankingCardWidget에서
    이미지,이름,할인전가격, 할인률 ,할인후가격, 태그,별점,리뷰순 이 모두 직접 적은거잖아? 이거를 파이어스토어 데이터베이스에서 끌어오고 싶어. 즉 products 의 상품들을 끌어오고 싶다는 소리야. 상품마다 필드값에서 salesVolume 이 있지? 상품 카드를 salesVolume 이 가장 높을수록 위쪽에 배치하도록 하고 싶어. 즉,판매량이 높을수록 위에 배치하고 싶은데 어떻게해?

블록으로

 

랭킹 탭 구현 과정 정리

1. 초기 요구사항

  • 판매량(salesVolume)이 높은 상품이 위쪽에 배치되어야 함
  • 카테고리별 필터링 기능 필요
  • 상품 카드에는 상품 이미지, 이름, 가격, 할인율, 별점, 리뷰 수 표시

2. 구현 단계

2.1 BLoC 패턴 구현

// Events
abstract class RankingEvent {}

class LoadRankingProducts extends RankingEvent {
  final String? categoryId;  // optional - 전체 조회시는 null
  LoadRankingProducts({this.categoryId});
}

// States
abstract class RankingState {}

class RankingInitial extends RankingState {}
class RankingLoading extends RankingState {}
class RankingLoaded extends RankingState {
  final List<ProductModel> products;
  RankingLoaded(this.products);
}
class RankingError extends RankingState {
  final String message;
  RankingError(this.message);
}

2.2 카테고리 매핑 설정

// 카테고리와 ID 매핑
final List<String> categoryFilters = [
  '전체',
  '스킨케어',    // ID: '1'
  '마스크팩',    // ID: '2'
  '클렌징',     // ID: '3'
  '선케어',     // ID: '4'
  '메이크업',    // ID: '5'
  '뷰티소품',    // ID: '6'
  '맨즈케어',    // ID: '7'
  '헤어케어',    // ID: '8'
  '바디케어'     // ID: '9'
];

2.3 Repository 구현

초기에는 복합 쿼리를 시도했으나 Firestore 인덱스 에러 발생:

Future<List<ProductModel>> getRankingProducts(String? categoryId) async {
  try {
    Query query = _firestore.collection('products');
    
    if (categoryId != null) {
      // 1. 카테고리로 필터링
      query = query.where('categoryId', isEqualTo: categoryId);
      final QuerySnapshot snapshot = await query.get();
      
      var products = snapshot.docs
          .map((doc) {
            Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
            data['productId'] = doc.id;
            return ProductModel.fromMap(data);
          })
          .toList();
      
      // 2. 클라이언트에서 정렬
      products.sort((a, b) => b.salesVolume.compareTo(a.salesVolume));
      
      // 3. 상위 10개만 반환
      return products.take(10).toList();
    } else {
      // 전체 상품 조회
      return await _getTopProducts();
    }
  } catch (e) {
    print('Error in getRankingProducts: $e');
    throw Exception('랭킹 상품을 불러오는데 실패했습니다.');
  }
}

2.4 데이터 처리 방식 비교

방법 1 (선택한 방법):

  • 장점:
    • 구현이 단순
    • 한 번의 쿼리로 처리
    • 추가 정렬/필터링이 필요할 때 유연함
  • 단점:
    • 카테고리의 상품이 많을 경우 모든 데이터를 가져와야 함
    • 클라이언트에서 정렬하므로 메모리 사용량 증가

방법 2 (두 단계 쿼리):

  • 장점:
    • 메모리 사용이 더 효율적
    • 대량의 데이터 처리에 적합
  • 단점:
    • 구현이 더 복잡
    • 두 번의 쿼리로 인한 지연 가능성

2.5 복합 인덱스 설명

복합 인덱스는 여러 필드를 조합해서 빠른 검색을 가능하게 하는 데이터베이스 기능입니다.

// 단일 필드 쿼리 (인덱스 불필요)
query.where('categoryId', isEqualTo: '1')  

// 복합 쿼리 (복합 인덱스 필요)
query.where('categoryId', isEqualTo: '1')
     .orderBy('salesVolume', descending: true)

현재는 복합 인덱스 대신 방법 1을 선택하여:

  1. 먼저 카테고리로 필터링
  2. 앱에서 판매량 기준으로 정렬
  3. 상위 10개 선택

하는 방식으로 구현했습니다.

3. 최종 선택 및 권장사항

  • 현재 단계: 방법 1 사용 (개발 초기에 적합)
  • 확장 계획:
    • 상품 수가 적을 때 (100개 이하/카테고리): 현재 방식 유지
    • 상품 수가 많아질 때: 방법 2 또는 Firestore 인덱스로 마이그레이션
  •  
728x90