Files
lunchpick/doc/03_architecture/tech_stack_decision.md
JiWoong Sul 85fde36157 feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다.

주요 기능:
- 네이버 지도 연동 맛집 추가
- 랜덤 메뉴 추천 시스템
- 날씨 기반 거리 조정
- 방문 기록 관리
- Bluetooth 맛집 공유
- 다크모드 지원

기술 스택:
- Flutter 3.8.1+
- Riverpod 상태 관리
- Hive 로컬 DB
- Clean Architecture

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 19:03:28 +09:00

7.2 KiB

기술 스택 결정 문서

1. 개요

이 문서는 "오늘 뭐 먹Z?" 앱의 네이버 단축 URL 처리 기능 확장을 위한 기술 스택 선택 근거와 결정 사항을 설명합니다.

2. 현재 기술 스택 분석

2.1 기존 스택

  • 상태 관리: Riverpod 2.4.0
  • 로컬 저장소: Hive 2.2.3
  • 네트워킹: Dio 5.4.0, HTTP 1.1.0
  • HTML 파싱: html 0.15.4
  • 아키텍처: Clean Architecture

2.2 강점 분석

  • Riverpod의 강력한 의존성 주입과 상태 관리
  • Hive의 빠른 성능과 간단한 사용법
  • Clean Architecture로 인한 명확한 책임 분리

3. 네이버 URL 처리를 위한 기술 선택

3.1 HTTP 클라이언트

선택: Dio + HTTP (하이브리드 접근)

근거:

  • Dio: 인터셉터, 타임아웃, 재시도 등 고급 기능 필요한 API 호출용
  • HTTP: 단순한 리다이렉션 처리와 기존 NaverMapParser 호환성

구현 전략:

// API 호출용 (Dio)
class NaverLocalApiClient {
  final Dio _dio = Dio(BaseOptions(
    connectTimeout: const Duration(seconds: 10),
    receiveTimeout: const Duration(seconds: 10),
  ))..interceptors.addAll([
    LogInterceptor(),
    RetryInterceptor(),
  ]);
}

// 단순 요청용 (HTTP)
class NaverUrlResolver {
  final http.Client _client;
}

3.2 HTML 파싱

선택: html 패키지 유지

근거:

  • 이미 프로젝트에서 사용 중
  • DOM 기반 파싱으로 안정적
  • 네이버 지도 페이지 구조에 적합

대안 검토:

  • BeautifulSoup (Python 전용)
  • web_scraper (기능 중복, 추가 의존성)
  • html (현재 선택)

3.3 상태 관리

선택: Riverpod 유지

근거:

  • 이미 프로젝트 전체에서 사용 중
  • 컴파일 타임 안전성
  • 의존성 주입 기능 내장
  • 테스트 용이성

Provider 구조:

// 새로운 Provider 추가
final naverUrlProcessorProvider = Provider((ref) {
  return NaverUrlProcessor(
    mapParser: ref.watch(naverMapParserProvider),
    apiClient: ref.watch(naverLocalApiClientProvider),
  );
});

3.4 로컬 캐싱

선택: Hive + 메모리 캐시 조합

근거:

  • Hive: 영구 저장이 필요한 식당 데이터
  • 메모리 캐시: URL 리다이렉션, API 결과 등 임시 데이터

구현:

class CacheManager {
  // 메모리 캐시 (LRU)
  final _urlCache = LruMap<String, String>(maximumSize: 100);
  final _apiCache = LruMap<String, dynamic>(maximumSize: 50);
  
  // Hive 박스
  late Box<Restaurant> _restaurantBox;
}

3.5 에러 처리 및 로깅

선택: 계층별 예외 + 구조화된 로깅

근거:

  • Clean Architecture의 계층 분리 원칙 준수
  • 디버깅 용이성
  • 프로덕션 모니터링 준비

구현:

// 계층별 예외
sealed class AppException implements Exception {
  final String message;
  final StackTrace? stackTrace;
}

class DataException extends AppException {}
class DomainException extends AppException {}
class PresentationException extends AppException {}

// 구조화된 로깅
class StructuredLogger {
  void log(LogLevel level, String message, {
    Map<String, dynamic>? data,
    Exception? error,
    StackTrace? stackTrace,
  });
}

4. 아키텍처 패턴 결정

4.1 Repository Pattern 확장

결정: Repository Pattern + Facade Pattern

근거:

  • 복잡한 네이버 URL 처리 로직을 단순한 인터페이스로 제공
  • 기존 Repository 구조와 일관성 유지
// Facade 패턴 적용
class NaverUrlProcessor {
  // 복잡한 내부 로직을 숨김
  Future<Restaurant> processNaverUrl(String url) {
    // 1. URL 검증
    // 2. 리다이렉션
    // 3. 스크래핑
    // 4. API 호출
    // 5. 매칭 및 병합
  }
}

4.2 의존성 주입

결정: Riverpod Provider 기반 DI

근거:

  • 기존 프로젝트 구조와 일치
  • 런타임 오버헤드 최소화
  • 테스트 시 모킹 용이

5. 외부 서비스 통합

5.1 네이버 로컬 API

결정: 직접 통합

근거:

  • 공식 SDK 없음
  • REST API로 간단한 구조
  • 필요한 기능만 선택적 구현 가능

API 엔드포인트:

class NaverApiEndpoints {
  static const String localSearch = '/v1/search/local.json';
  static const String placeDetail = '/v1/search/place/detail';
}

5.2 CORS 프록시 (웹 환경)

결정: allorigins.win 사용

근거:

  • 무료 서비스
  • 안정적인 가동률
  • JSON 응답 지원

대안:

  • 자체 프록시 서버 (유지보수 부담)
  • cors-anywhere (제한적)
  • allorigins.win (선택)

6. 테스트 전략

6.1 테스트 프레임워크

결정: Flutter Test + Mockito

근거:

  • Flutter 기본 제공
  • Riverpod과 호환
  • 풍부한 매칭 기능

6.2 테스트 범위

단위 테스트:
  - URL 파서: 90% 이상
  - API 클라이언트: 85% 이상
  - 매칭 알고리즘: 95% 이상

통합 테스트:
  - URL 처리 파이프라인: 핵심 시나리오
  - Repository 통합: 주요 플로우

E2E 테스트:
  - 실제 네이버 URL로 테스트 (CI 제외)

7. 성능 고려사항

7.1 네트워크 최적화

결정:

  • Connection pooling (Dio 기본 제공)
  • Request 타임아웃: 10초
  • 재시도: 최대 3회

7.2 메모리 최적화

결정:

  • LRU 캐시 크기 제한
  • 이미지 데이터 제외
  • 주기적 캐시 정리

8. 보안 고려사항

8.1 API 키 관리

결정: 환경 변수 + 난독화

// 컴파일 타임 주입
const String apiKey = String.fromEnvironment('NAVER_API_KEY');

// 런타임 난독화
final obfuscatedKey = base64.encode(utf8.encode(apiKey));

8.2 네트워크 보안

결정:

  • HTTPS 전용
  • Certificate pinning (선택적)
  • Request 서명 검증

9. 마이그레이션 계획

9.1 단계별 적용

  1. Phase 1: 기본 구조 구현

    • NaverLocalApiClient
    • 기본 에러 처리
  2. Phase 2: 고급 기능

    • 캐싱 레이어
    • 매칭 알고리즘
  3. Phase 3: 최적화

    • 성능 튜닝
    • 모니터링 추가

9.2 롤백 계획

  • Feature flag로 새 기능 제어
  • 기존 NaverMapParser 유지
  • 점진적 트래픽 전환

10. 결론

10.1 핵심 결정 사항

영역 선택 이유
HTTP 클라이언트 Dio + HTTP 용도별 최적화
상태 관리 Riverpod 프로젝트 일관성
로컬 저장소 Hive 기존 인프라 활용
캐싱 Hive + Memory 성능과 영속성 균형
아키텍처 Clean + Facade 복잡도 관리

10.2 예상 효과

  • 개발 속도: 기존 스택 활용으로 빠른 구현
  • 유지보수성: 명확한 책임 분리로 관리 용이
  • 확장성: 다른 플랫폼 추가 시 쉬운 확장
  • 성능: 캐싱과 최적화로 빠른 응답

10.3 리스크 및 대응

리스크 영향도 대응 방안
API 제한 캐싱 강화, Rate limiting
네이버 구조 변경 파서 업데이트 자동화
CORS 프록시 장애 대체 프록시 준비

10.4 향후 고려사항

  • GraphQL 도입 검토 (복잡한 쿼리 증가 시)
  • 자체 백엔드 구축 (사용자 증가 시)
  • ML 기반 매칭 알고리즘 (정확도 개선)