코딩/♠♠기술 구현

장바구니 데이터베이스 연결-잘못된 연결

흑백 개발자 2024. 11. 8. 21:02
728x90

데비에 두컬렉션을 걸쳐서 했지때문에 잘못된 연결이다

결국 롤백하고 유저로만 연결했다.

Firebase를 활용한 장바구니 기능 구현 과정

1. 데이터 구조 설계

Firebase Firestore에서 장바구니 데이터를 양방향으로 관리하기 위해 두 개의 컬렉션을 활용했습니다:

users 컬렉션

{
  "userId": {
    "cartItems": ["productId1", "productId2", ...] // 장바구니에 담은 상품 ID 목록
  }
}

products 컬렉션

{
  "productId": {
    "cartList": [
      {
        "userId": "userId1",
        "quantity": 1
      }
    ],
    // 기타 상품 정보...
  }
}

이러한 양방향 구조를 채택한 이유:

  1. 사용자별 장바구니 조회 성능 최적화
  2. 상품별 장바구니 담긴 현황 파악 용이
  3. 데이터 일관성 유지

2. 주요 기능 구현

2.1 장바구니 담기 기능

addToCart 메서드를 구현하여 Firebase Transaction을 활용한 안전한 데이터 업데이트:

Future<void> addToCart(String productId, String userId) async {
  try {
    await _firestore.runTransaction((transaction) async {
      final productDoc = _firestore.collection('products').doc(productId);
      final userDoc = _firestore.collection('users').doc(userId);
      
      // 현재 상태 가져오기
      final productSnapshot = await transaction.get(productDoc);
      final userSnapshot = await transaction.get(userDoc);
      
      // cartList와 cartItems 업데이트
      List<Map<String, dynamic>> cartList = List<Map<String, dynamic>>.from(
          productSnapshot.get('cartList') ?? []);
      List<String> cartItems = List<String>.from(
          userSnapshot.exists ? userSnapshot.get('cartItems') ?? [] : []);
      
      // 중복 체크 후 추가
      if (!cartItems.contains(productId)) {
        cartItems.add(productId);
        cartList.add({
          'productId': productId,
          'quantity': 1
        });
        
        // 두 컬렉션 동시 업데이트
        transaction.update(productDoc, {'cartList': cartList});
        if (!userSnapshot.exists) {
          transaction.set(userDoc, {'cartItems': cartItems});
        } else {
          transaction.update(userDoc, {'cartItems': cartItems});
        }
      }
    });
  } catch (e) {
    throw Exception('장바구니 추가에 실패했습니다.');
  }
}

2.2 장바구니 조회 기능

사용자의 cartItems 목록을 기반으로 상품 정보를 조회:

Future<List<ProductModel>> loadCartItems() async {
  try {
    final userId = await OnlyYouSharedPreference().getCurrentUserId();
    final userDoc = await _firestore.collection('users').doc(userId).get();
    
    // cartItems 목록 가져오기
    List<String> cartItems = List<String>.from(userDoc.get('cartItems') ?? []);
    
    // 상품 정보 조회
    final products = await Future.wait(
      cartItems.map((productId) async {
        final doc = await _firestore.collection('products').doc(productId).get();
        return doc;
      })
    );

    // ProductModel로 변환
    return products
        .where((doc) => doc != null && doc.exists)
        .map((doc) {
          final data = doc!.data() as Map<String, dynamic>;
          data['productId'] = doc.id;
          return ProductModel.fromMap(data);
        })
        .toList();
  } catch (e) {
    throw Exception('장바구니 상품을 불러오는데 실패했습니다.');
  }
}

2.3 장바구니 삭제 기능

특정 상품을 장바구니에서 제거할 때도 양쪽 컬렉션 동시 업데이트:

Future<void> removeProduct(String productId) async {
  try {
    final userId = await OnlyYouSharedPreference().getCurrentUserId();
    
    await _firestore.runTransaction((transaction) async {
      // 상품과 유저 문서 동시 업데이트
      final productDoc = _firestore.collection('products').doc(productId);
      final userDoc = _firestore.collection('users').doc(userId);
      
      final productSnapshot = await transaction.get(productDoc);
      final userSnapshot = await transaction.get(userDoc);
      
      // cartList에서 현재 유저 제거
      List<Map<String, dynamic>> cartList = List<Map<String, dynamic>>.from(
          productSnapshot.get('cartList') ?? []);
      cartList.removeWhere((item) => item['userId'] == userId);
      
      // cartItems에서 현재 상품 제거
      List<String> cartItems = List<String>.from(
          userSnapshot.get('cartItems') ?? []);
      cartItems.remove(productId);
      
      // 트랜잭션으로 동시 업데이트
      transaction.update(productDoc, {'cartList': cartList});
      transaction.update(userDoc, {'cartItems': cartItems});
    });
  } catch (e) {
    throw Exception('장바구니에서 상품 제거에 실패했습니다.');
  }
}

3. 주요 구현 포인트

  1. 데이터 일관성: Firebase Transaction을 활용하여 여러 문서의 동시 업데이트 시 데이터 일관성 보장
  2. 에러 처리: try-catch 구문으로 각종 예외 상황에 대한 적절한 에러 처리 구현
  3. Null Safety: Dart의 Null Safety를 활용하여 안전한 데이터 처리
  4. 양방향 데이터 관리:
    • users 컬렉션: 빠른 장바구니 목록 조회
    • products 컬렉션: 상품별 장바구니 현황 파악
  5. 성능 최적화:
    • 불필요한 데이터 조회 최소화
    • Future.wait를 활용한 병렬 처리
    • 적절한 인덱싱으로 쿼리 성능 향상

4. UI/UX 고려사항

  1. 장바구니 담기 성공/실패 시 스낵바로 피드백 제공
  2. 로딩 상태 표시로 사용자 경험 개선
  3. 장바구니 상품 삭제 시 확인 다이얼로그 제공
  4. 에러 발생 시 적절한 에러 메시지 표시

5. 향후 개선 사항

  1. 상품 수량 조절 기능 추가
  2. 장바구니 상품 전체 선택/해제 기능
  3. 로컬 캐싱을 통한 오프라인 지원
  4. 실시간 업데이트 지원 (Stream 활용)

이러한 구현을 통해 안정적이고 사용자 친화적인 장바구니 기능을 제공할 수 있었습니다.

728x90