Files
superport/docs/usecase_guide.md
JiWoong Sul 162fe08618
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항

### 아키텍처 개선
- Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리)
- Use Case 패턴 도입으로 비즈니스 로직 캡슐화
- Repository 패턴으로 데이터 접근 추상화
- 의존성 주입 구조 개선

### 상태 관리 최적화
- 모든 Controller에서 불필요한 상태 관리 로직 제거
- 페이지네이션 로직 통일 및 간소화
- 에러 처리 로직 개선 (에러 메시지 한글화)
- 로딩 상태 관리 최적화

### Mock 서비스 제거
- MockDataService 완전 제거
- 모든 화면을 실제 API 전용으로 전환
- 불필요한 Mock 관련 코드 정리

### UI/UX 개선
- Overview 화면 대시보드 기능 강화
- 라이선스 만료 알림 위젯 추가
- 사이드바 네비게이션 개선
- 일관된 UI 컴포넌트 사용

### 코드 품질
- 중복 코드 제거 및 함수 추출
- 파일별 책임 분리 명확화
- 테스트 코드 업데이트

## 영향 범위
- 모든 화면의 Controller 리팩토링
- API 통신 레이어 구조 개선
- 에러 처리 및 로깅 시스템 개선

## 향후 계획
- 단위 테스트 커버리지 확대
- 통합 테스트 시나리오 추가
- 성능 모니터링 도구 통합
2025-08-11 00:04:28 +09:00

7.3 KiB

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 추상 클래스

abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}
  • Type: 성공 시 반환할 데이터 타입
  • Params: UseCase 실행에 필요한 파라미터
  • Either: 실패(Left) 또는 성공(Right) 결과를 담는 컨테이너

2. Failure 클래스

abstract class Failure {
  final String message;
  final String? code;
  final dynamic originalError;
}

다양한 실패 타입:

  • ServerFailure: 서버 에러
  • NetworkFailure: 네트워크 에러
  • AuthFailure: 인증 에러
  • ValidationFailure: 유효성 검증 에러
  • PermissionFailure: 권한 에러

📝 UseCase 구현 예시

1. 로그인 UseCase

class LoginUseCase extends UseCase<LoginResponse, LoginParams> {
  final AuthService _authService;

  LoginUseCase(this._authService);

  @override
  Future<Either<Failure, LoginResponse>> 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

class LogoutUseCase extends UseCase<void, NoParams> {
  final AuthService _authService;

  LogoutUseCase(this._authService);

  @override
  Future<Either<Failure, void>> call(NoParams params) async {
    try {
      await _authService.logout();
      return const Right(null);
    } catch (e) {
      return Left(UnknownFailure(
        message: '로그아웃 중 오류가 발생했습니다.',
      ));
    }
  }
}

🎯 Controller에서 UseCase 사용

1. UseCase 초기화

class LoginControllerWithUseCase extends ChangeNotifier {
  late final LoginUseCase _loginUseCase;
  
  LoginControllerWithUseCase() {
    final authService = inject<AuthService>();
    _loginUseCase = LoginUseCase(authService);
  }
}

2. UseCase 실행

Future<bool> 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 단위 테스트

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<ValidationFailure>()),
      (response) => fail('Should not succeed'),
    );
  });
}

2. Controller 테스트

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 로직만 담당

📚 참고 자료


작성일: 2025-01-09
버전: 1.0