코딩/♠♠기술 구현
장바구니 데이터베이스 연결-잘못된 연결
흑백 개발자
2024. 11. 8. 21:02
728x90
데비에 두컬렉션을 걸쳐서 했지때문에 잘못된 연결이다
결국 롤백하고 유저로만 연결했다.
Firebase를 활용한 장바구니 기능 구현 과정
1. 데이터 구조 설계
Firebase Firestore에서 장바구니 데이터를 양방향으로 관리하기 위해 두 개의 컬렉션을 활용했습니다:
users 컬렉션
{
"userId": {
"cartItems": ["productId1", "productId2", ...] // 장바구니에 담은 상품 ID 목록
}
}
products 컬렉션
{
"productId": {
"cartList": [
{
"userId": "userId1",
"quantity": 1
}
],
// 기타 상품 정보...
}
}
이러한 양방향 구조를 채택한 이유:
- 사용자별 장바구니 조회 성능 최적화
- 상품별 장바구니 담긴 현황 파악 용이
- 데이터 일관성 유지
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. 주요 구현 포인트
- 데이터 일관성: Firebase Transaction을 활용하여 여러 문서의 동시 업데이트 시 데이터 일관성 보장
- 에러 처리: try-catch 구문으로 각종 예외 상황에 대한 적절한 에러 처리 구현
- Null Safety: Dart의 Null Safety를 활용하여 안전한 데이터 처리
- 양방향 데이터 관리:
- users 컬렉션: 빠른 장바구니 목록 조회
- products 컬렉션: 상품별 장바구니 현황 파악
- 성능 최적화:
- 불필요한 데이터 조회 최소화
- Future.wait를 활용한 병렬 처리
- 적절한 인덱싱으로 쿼리 성능 향상
4. UI/UX 고려사항
- 장바구니 담기 성공/실패 시 스낵바로 피드백 제공
- 로딩 상태 표시로 사용자 경험 개선
- 장바구니 상품 삭제 시 확인 다이얼로그 제공
- 에러 발생 시 적절한 에러 메시지 표시
5. 향후 개선 사항
- 상품 수량 조절 기능 추가
- 장바구니 상품 전체 선택/해제 기능
- 로컬 캐싱을 통한 오프라인 지원
- 실시간 업데이트 지원 (Stream 활용)
이러한 구현을 통해 안정적이고 사용자 친화적인 장바구니 기능을 제공할 수 있었습니다.
728x90