★★★트러블 슈팅

파베에서 데이터 끌어올때 블록만?VS 레퍼지토리 같이?

흑백 개발자 2024. 11. 6. 11:16
728x90

결론은 레퍼지토리와 같이 해야한다 그래야 다른곳에서도 끌어올 수 있다.

파이어베이스 데이터 접근을 Repository vs Bloc에서 직접 처리하는 것의 주요 차이점을 설명하겠습니다:

1. 관심사의 분리 (Separation of Concerns)
```dart
// Repository 패턴 사용
class TodaySaleRepository {
  Future<List<ProductModel>> getTodaySaleProducts() async {
    final snapshot = await _firestore
        .collection('products')
        .where('discountPercent', isGreaterThanOrEqualTo: 40)
        .get();
    return snapshot.docs.map((doc) => ProductModel.fromMap(doc.data())).toList();
  }
}

// Bloc은 비즈니스 로직에만 집중
class TodaySaleBloc extends Bloc<TodaySaleEvent, TodaySaleState> {
  final TodaySaleRepository _repository;
  
  on<LoadTodaySaleProducts>((event, emit) async {
    try {
      final products = await _repository.getTodaySaleProducts();
      emit(TodaySaleLoaded(products));
    } catch (e) {
      emit(TodaySaleError('로드 실패'));
    }
  });
}

// Bloc에서 직접 처리 (좋지 않은 예)
class TodaySaleBloc extends Bloc<TodaySaleEvent, TodaySaleState> {
  final FirebaseFirestore _firestore;
  
  on<LoadTodaySaleProducts>((event, emit) async {
    try {
      final snapshot = await _firestore
          .collection('products')
          .where('discountPercent', isGreaterThanOrEqualTo: 40)
          .get();
      final products = snapshot.docs.map((doc) => 
          ProductModel.fromMap(doc.data())).toList();
      emit(TodaySaleLoaded(products));
    } catch (e) {
      emit(TodaySaleError('로드 실패'));
    }
  });
}
```

2. 재사용성
```dart
// Repository 사용 시 다른 Bloc에서도 재사용 가능
class OtherBloc extends Bloc<OtherEvent, OtherState> {
  final TodaySaleRepository _repository;
  
  on<LoadProducts>((event, emit) async {
    final products = await _repository.getTodaySaleProducts();
    // 다른 로직...
  });
}
```

3. 테스트 용이성
```dart
// Repository Mock 생성이 쉬움
class MockTodaySaleRepository implements TodaySaleRepository {
  @override
  Future<List<ProductModel>> getTodaySaleProducts() async {
    return [/* test data */];
  }
}

// 테스트 코드
test('should load products', () async {
  final repository = MockTodaySaleRepository();
  final bloc = TodaySaleBloc(repository: repository);
  
  bloc.add(LoadTodaySaleProducts());
  
  await expectLater(
    bloc.stream,
    emitsInOrder([
      isA<TodaySaleLoading>(),
      isA<TodaySaleLoaded>(),
    ]),
  );
});
```

4. 데이터 소스 변경 용이성
```dart
// Repository를 통해 데이터 소스 변경이 쉬움
class TodaySaleRepository {
  Future<List<ProductModel>> getTodaySaleProducts() async {
    // Firebase에서 REST API로 변경하더라도
    // Bloc의 코드는 변경할 필요 없음
    final response = await http.get('api/products');
    return ProductModel.fromJson(response.body);
  }
}
```

5. 오류 처리와 데이터 변환
```dart
class TodaySaleRepository {
  Future<List<ProductModel>> getTodaySaleProducts() async {
    try {
      final snapshot = await _firestore.collection('products').get();
      
      // 데이터 변환 및 필터링 로직을 repository에서 처리
      return snapshot.docs
          .map((doc) => ProductModel.fromMap(doc.data()))
          .where((product) => product.discountPercent >= 40)
          .toList();
          
    } catch (e) {
      // 구체적인 에러 처리
      if (e is FirebaseException) {
        throw RepositoryException('Firebase 연결 실패');
      }
      throw RepositoryException('알 수 없는 에러');
    }
  }
}
```

Repository 패턴을 사용하는 것이 더 좋은 이유:

1. **유지보수성**: 데이터 액세스 로직이 한 곳에 집중되어 있어 관리가 쉬움

2. **테스트 용이성**: Repository를 Mock으로 대체하여 테스트하기 쉬움

3. **코드 재사용**: 여러 Bloc에서 동일한 Repository 사용 가능

4. **관심사 분리**: 
   - Repository: 데이터 액세스 담당
   - Bloc: 비즈니스 로직 담당
   - UI: 화면 표시 담당

5. **유연성**: 데이터 소스 변경이 용이함 (Firebase → REST API 등)

6. **일관성**: 데이터 처리 방식의 일관성 유지

따라서 클린 아키텍처와 SOLID 원칙을 따르기 위해서는 Repository 패턴을 사용하는 것이 권장됩니다.

728x90