# UseCase 패턴 가이드 ## 📌 개요 UseCase 패턴은 Clean Architecture의 핵심 개념으로, 비즈니스 로직을 캡슐화하여 재사용성과 테스트 용이성을 높입니다. ## 🏗️ 구조 ``` lib/ ├── domain/ │ └── usecases/ │ ├── base_usecase.dart # 기본 UseCase 인터페이스 │ ├── auth/ # 인증 관련 UseCase │ │ ├── login_usecase.dart │ │ ├── logout_usecase.dart │ │ └── ... │ ├── company/ # 회사 관리 UseCase │ │ ├── get_companies_usecase.dart │ │ ├── create_company_usecase.dart │ │ └── ... │ └── ... ``` ## 🔑 핵심 개념 ### 1. UseCase 추상 클래스 ```dart abstract class UseCase { Future> call(Params params); } ``` - **Type**: 성공 시 반환할 데이터 타입 - **Params**: UseCase 실행에 필요한 파라미터 - **Either**: 실패(Left) 또는 성공(Right) 결과를 담는 컨테이너 ### 2. Failure 클래스 ```dart abstract class Failure { final String message; final String? code; final dynamic originalError; } ``` 다양한 실패 타입: - **ServerFailure**: 서버 에러 - **NetworkFailure**: 네트워크 에러 - **AuthFailure**: 인증 에러 - **ValidationFailure**: 유효성 검증 에러 - **PermissionFailure**: 권한 에러 ## 📝 UseCase 구현 예시 ### 1. 로그인 UseCase ```dart class LoginUseCase extends UseCase { final AuthService _authService; LoginUseCase(this._authService); @override Future> call(LoginParams params) async { try { // 1. 유효성 검증 if (!_isValidEmail(params.email)) { return Left(ValidationFailure( message: '올바른 이메일 형식이 아닙니다.', )); } // 2. 비즈니스 로직 실행 final response = await _authService.login( LoginRequest( email: params.email, password: params.password, ), ); // 3. 성공 결과 반환 return Right(response); } on DioException catch (e) { // 4. 에러 처리 return Left(_handleDioError(e)); } } } ``` ### 2. 파라미터가 없는 UseCase ```dart class LogoutUseCase extends UseCase { final AuthService _authService; LogoutUseCase(this._authService); @override Future> call(NoParams params) async { try { await _authService.logout(); return const Right(null); } catch (e) { return Left(UnknownFailure( message: '로그아웃 중 오류가 발생했습니다.', )); } } } ``` ## 🎯 Controller에서 UseCase 사용 ### 1. UseCase 초기화 ```dart class LoginControllerWithUseCase extends ChangeNotifier { late final LoginUseCase _loginUseCase; LoginControllerWithUseCase() { final authService = inject(); _loginUseCase = LoginUseCase(authService); } } ``` ### 2. UseCase 실행 ```dart Future login() async { final params = LoginParams( email: emailController.text, password: passwordController.text, ); final result = await _loginUseCase(params); return result.fold( (failure) { // 실패 처리 _errorMessage = failure.message; notifyListeners(); return false; }, (loginResponse) { // 성공 처리 return true; }, ); } ``` ## 🧪 테스트 작성 ### 1. UseCase 단위 테스트 ```dart void main() { late LoginUseCase loginUseCase; late MockAuthService mockAuthService; setUp(() { mockAuthService = MockAuthService(); loginUseCase = LoginUseCase(mockAuthService); }); test('로그인 성공 테스트', () async { // Given const params = LoginParams( email: 'test@example.com', password: 'password123', ); final expectedResponse = LoginResponse(...); when(mockAuthService.login(any)) .thenAnswer((_) async => expectedResponse); // When final result = await loginUseCase(params); // Then expect(result.isRight(), true); result.fold( (failure) => fail('Should not fail'), (response) => expect(response, expectedResponse), ); }); test('잘못된 이메일 형식 테스트', () async { // Given const params = LoginParams( email: 'invalid-email', password: 'password123', ); // When final result = await loginUseCase(params); // Then expect(result.isLeft(), true); result.fold( (failure) => expect(failure, isA()), (response) => fail('Should not succeed'), ); }); } ``` ### 2. Controller 테스트 ```dart void main() { late LoginControllerWithUseCase controller; late MockLoginUseCase mockLoginUseCase; setUp(() { mockLoginUseCase = MockLoginUseCase(); controller = LoginControllerWithUseCase(); controller._loginUseCase = mockLoginUseCase; }); test('로그인 버튼 클릭 시 UseCase 호출', () async { // Given controller.emailController.text = 'test@example.com'; controller.passwordController.text = 'password123'; when(mockLoginUseCase(any)) .thenAnswer((_) async => Right(LoginResponse())); // When final result = await controller.login(); // Then expect(result, true); verify(mockLoginUseCase(any)).called(1); }); } ``` ## 💡 장점 1. **단일 책임 원칙**: 각 UseCase는 하나의 비즈니스 로직만 담당 2. **테스트 용이성**: 비즈니스 로직을 독립적으로 테스트 가능 3. **재사용성**: 여러 Controller에서 동일한 UseCase 재사용 4. **의존성 역전**: Controller가 구체적인 Service가 아닌 UseCase에 의존 5. **에러 처리 표준화**: Either 패턴으로 일관된 에러 처리 ## 📋 구현 체크리스트 ### UseCase 생성 시 - [ ] UseCase 클래스 생성 (base_usecase 상속) - [ ] 파라미터 클래스 정의 (필요한 경우) - [ ] 유효성 검증 로직 구현 - [ ] 에러 처리 구현 - [ ] 성공/실패 케이스 모두 처리 ### Controller 리팩토링 시 - [ ] UseCase 의존성 주입 - [ ] 비즈니스 로직을 UseCase 호출로 대체 - [ ] Either 패턴으로 결과 처리 - [ ] 에러 메시지 사용자 친화적으로 변환 ### 테스트 작성 시 - [ ] UseCase 단위 테스트 - [ ] 성공 케이스 테스트 - [ ] 실패 케이스 테스트 - [ ] 경계값 테스트 - [ ] Controller 통합 테스트 ## 🔄 마이그레이션 전략 ### Phase 1: 핵심 기능부터 시작 1. 인증 관련 기능 (로그인, 로그아웃) 2. CRUD 기본 기능 3. 복잡한 비즈니스 로직 ### Phase 2: 점진적 확산 1. 새로운 기능은 UseCase 패턴으로 구현 2. 기존 코드는 리팩토링 시 UseCase 적용 3. 테스트 커버리지 확보 ### Phase 3: 완전 마이그레이션 1. 모든 비즈니스 로직 UseCase화 2. Service 레이어는 데이터 액세스만 담당 3. Controller는 UI 로직만 담당 ## 📚 참고 자료 - [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - [Either Pattern in Dart](https://pub.dev/packages/dartz) - [Flutter Clean Architecture](https://resocoder.com/flutter-clean-architecture-tdd/) --- **작성일**: 2025-01-09 **버전**: 1.0