refactor(api,mobile): Result 패턴 도입 및 레이어 아키텍처 재구성 (#116)#117
refactor(api,mobile): Result 패턴 도입 및 레이어 아키텍처 재구성 (#116)#117dydals3440 merged 16 commits intomainfrom
Conversation
- scheduledTime 필드를 DateTime?에서 DateTime? @db.Timestamptz(3)로 변경 - 타임존 인식 시간 저장으로 전환하여 다양한 타임존의 사용자 지원 - Google Calendar 패턴: 시간 이벤트는 TIMESTAMPTZ(UTC)로 저장
- toDateOnly: 날짜 문자열을 UTC 자정 Date로 변환 - getUserToday: 사용자 타임존 기준 오늘 날짜 반환 - toScheduledTime: 사용자 로컬 시간을 UTC Date로 변환 - startOfDayInTimezone: 타임존 기준 하루 시작 시각 반환 - @Timezone() 데코레이터: X-Timezone 헤더에서 타임존 추출 - date.util.spec.ts: 13개 테스트 케이스 추가
- @Timezone() 데코레이터로 사용자 타임존 수신 - toDateOnly()로 DATE 필드 변환 일관성 확보 - buildDateRangeFilter(): 다중일/단일일 투두 날짜 필터링 구현 - 다중일: todo.startDate <= filterEnd AND todo.endDate >= filterStart - 단일일: filterStart <= todo.startDate <= filterEnd - validateDateRange(): startDate > endDate 검증 추가 - getTodayTodoStats(): 타임존 기반 오늘 날짜 계산
- 단위 테스트: startDate만/endDate만/잘못된 범위 검증 (3건) - 단위 테스트: toggleComplete 타임존 연동 검증 - E2E 테스트: 다중일 투두 범위 필터, 특정 하루 조회, 기간 조회 (3건) - 통합 테스트: Overlapping Intervals 쿼리 조건 검증
- existsNotification: 같은 날 DAILY_COMPLETE 중복 체크 - findAlreadyNotifiedUserIds: Set 기반 배치 조회로 N+1 방지 - handleTodoAllCompleted: 중복 발송 시 스킵 - handleFriendCompleted: 이미 알림 받은 유저 필터링 - 리스너 테스트 6건 (GWT 패턴) - 레포지토리 테스트 4건
- @Timezone() 데코레이터로 사용자 타임존 수신 - startOfDayInTimezone()으로 일일 제한 카운트의 날짜 경계 계산 - 일일 제한 및 쿨다운 계산이 사용자 타임존 기준으로 동작
Result 타입 시스템:
- Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
- ok(), err(), unwrap(), isOk(), isErr() 헬퍼 함수
- 예상된 에러(4xx)는 Result.err()로 전달
- 예상치 못한 에러(5xx, 네트워크)는 throw InfraError
에러 체계 정비:
- InfraError: 네트워크/서버 에러 (ErrorBoundary 처리)
- ApiError: 4xx 비즈니스 에러 (Mutation onError 처리)
- ClientError 제거 → 도메인별 FeatureError로 대체
HTTP 클라이언트:
- HttpClient 포트를 Result<T, ApiError> 기반으로 재정의
- KyHttpClient: Result 기반 구현체
- 4xx → Result.err(ApiError), 5xx → throw InfraError
모델 정리: - auth.model.ts 신규: Zod 스키마 기반 도메인 타입 통합 - auth-tokens.model.ts, user.model.ts, auth.policy.ts 삭제 Mapper 이동: - services/auth.mapper.ts → repositories/auth.mapper.ts - DTO→도메인 변환 책임을 Repository 레이어로 이동 Repository: - Result<T, ApiError> 기반 계약으로 변경 - Zod safeParse 검증 + Mapper 변환 파이프라인 구축 Service: - Repository 인터페이스만 의존 (DTO 무관) - Policy 검증 → FeatureError 반환 Presentation: - unwrap(result)로 Result 해제 - onError에서 에러 타입별 UI 분기
Mapper 이동: - services/friend.mapper.ts → repositories/friend.mapper.ts - DTO→도메인 변환을 Repository에서 수행 Repository: - Result<T, ApiError> 기반 계약으로 변경 - Page<FriendUser>, Page<FriendRequest> 등 통합 페이지네이션 타입 사용 Service: - FriendPolicy.isValidTag() 사전 검증 - FriendError(EMPTY_TAG, INVALID_TAG) 반환 Presentation: - unwrap(result) 적용 - FriendList, FriendRequestRow 등 컴포넌트 에러 처리 개선
Mapper 이동: - services/notification.mapper.ts → repositories/notification.mapper.ts Repository: - Result<T, ApiError> 기반 계약으로 변경 - toNotification(), toNotificationListResult() 매퍼 적용 Service: - Repository 인터페이스만 의존 - push-token.service에서 InfraError 처리 개선 Presentation: - unwrap(result) 적용 - 불필요한 use-notification-handler 로직 정리
Mapper 이동: - services/todo.mapper.ts → repositories/todo.mapper.ts - toTodoItem(), toTodoItems(), toParsedTodoResult() 등 매퍼 함수 Repository: - Result<T, ApiError> 기반 계약으로 변경 - Zod safeParse 검증 후 매퍼 적용 Service: - TodoPolicy 기반 사전 검증 - get-todos-by-date → get-todos-by-range로 교체 Presentation: - unwrap(result) 적용 - Zod 스키마 기반 폼 검증 추가 - AddTodoBottomSheet, UserAvatarList 에러 처리 개선
DI 컨테이너: - KyHttpClient 기반 의존성 주입 체인 구성 - Repository → Service 주입 패턴 적용 쿼리 설정: - retry 로직에서 BusinessError/ApiError 구분 - 인증 에러(401, 403)는 재시도하지 않음 공유 유틸리티: - date.ts: 날짜 포맷팅 및 캘린더 유틸리티 확장 - timezone.ts: 디바이스 타임존 감지 (Intl.DateTimeFormat)
- CLAUDE.md: Result 패턴, 레이어 역할, Mapper 위치 문서화 - testing-guide.md: Result 기반 테스트 패턴 가이드 추가 - error-handling.md: 에러 분류 전략 및 처리 흐름 재작성
Summary of ChangesHello @dydals3440, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 모바일 앱의 에러 처리 및 아키텍처를 현대화하고, API 서버의 핵심 기능을 강화하여 사용자 경험과 시스템 안정성을 향상시키는 데 중점을 둡니다. 특히, 모바일 앱은 Result 패턴과 재구성된 레이어를 통해 예측 가능한 에러 흐름을 구축하며, API는 타임존, 고급 날짜 필터링, 알림 중복 방지 기능을 도입하여 데이터 처리의 정확성과 효율성을 높였습니다. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
훌륭하고 광범위한 리팩토링입니다. API와 모바일 앱 모두의 안정성과 유지보수성을 크게 향상시켰습니다. 에러 처리를 위한 Result 패턴 도입, 모바일 아키텍처의 명확한 레이어 분리, 그리고 API 측의 타임존 지원 및 날짜 필터링 개선과 같은 핵심 기능 추가 모두 매우 잘 구현되었습니다. 함께 업데이트된 문서도 상세하여 향후 개발에 큰 도움이 될 것입니다. 중복된 주석에 대한 사소한 제안이 하나 있습니다. 전반적으로 훌륭한 작업입니다!
- README: 깨진 링크 수정, ORM 표기에 adapter-pg 추가, 구조도에 date/decorators 디렉토리 반영 - architecture.md: AI 일일 제한 FREE 10회→5회 수정, 타임스탬프 예시 현재 연도로 수정 - api-conventions.md: 타임스탬프 예시 현재 연도로 수정, AI 제한 5회 반영, 타임존 처리 규칙 섹션 추가 - testing-guide.md: 문서 버전 1.0.1 및 최종 수정일 갱신 - PUSH_NOTIFICATION_GUIDE: 모바일 구현 상태 3개 항목 "완료"로 수정 - cheer/nudge/todo controller: @Timezone() 사용 메서드에 @ApiHeader X-Timezone 추가
- Feature 구조도: ClientError 클래스 → {Feature}Error 클래스 (BusinessError 구현)
- 새 Feature 체크리스트: 동일 용어 수정
- 아키텍처 레이어 다이어그램: shared/types/ 디렉토리 추가 (Page<T> 등)
📋 개요
모바일 앱에 Result 패턴을 도입하여 에러 처리를 타입 안전하게 개선하고, 각 feature의 레이어별 역할을 명확히 재구성합니다.
API 서버에는 타임존 지원, Overlapping Intervals 날짜 필터링, 알림 중복 방지 로직을 추가합니다.
🏷️ 변경 유형
refactor- 코드 리팩토링feat- 새로운 기능 추가test- 테스트 추가/수정docs- 문서 수정📦 영향 범위
apps/api- NestJS 백엔드apps/mobile- Expo 모바일 앱📝 변경 내용
1. Result 패턴 도입 (Mobile)
기존에는 에러 처리가
throw기반으로 혼재되어 있어, **예상된 에러(4xx)**와 **예상치 못한 에러(5xx, 네트워크)**의 구분이 불명확했습니다.Before
After
Result 타입 정의
에러 분류 전략
throwResult.err()onErrorResult.err()onError2. 레이어별 역할 재구성 (Mobile)
각 레이어의 책임을 명확히 분리하고, Mapper의 위치를
services/→repositories/로 이동했습니다.레이어 구조 및 역할
Mapper 위치 변경 이유
services/)repositories/)각 레이어가 아는 것 / 모르는 것
적용된 Feature 모듈
auth.mapper.tsfriend.mapper.tsnotification.mapper.tstodo.mapper.ts3. 타임존 지원 (API)
X-Timezone헤더에서 IANA 타임존 추출 (기본값: UTC)scheduledTime필드 타입 변경 (DateTime → Timestamptz)적용 모듈: Todo, Cheer, Nudge - 일일 제한/쿨다운/완료 판정이 사용자 타임존 기준으로 동작
4. Overlapping Intervals 날짜 필터링 (API)
다중일 투두를 캘린더에서 올바르게 표시하기 위한 범위 필터링:
todo.startDate <= filterEnd AND todo.endDate >= filterStartfilterStart <= todo.startDate <= filterEnd5. 알림 중복 발송 방지 (API)
existsNotification(): 같은 날 이미 발송 여부 체크findAlreadyNotifiedUserIds(): Set 기반 배치 조회로 N+1 방지🧪 테스트
테스트 방법
테스트 결과
추가된 테스트
✅ 체크리스트
작성자 확인
pnpm lint(Biome) 검사를 통과했습니다pnpm test)pnpm build)🔗 관련 이슈
Closes #116