Skip to content

Conversation

@jsoonworld
Copy link
Member

@jsoonworld jsoonworld commented Apr 1, 2025

πŸ“„ Work Description

  • λΈŒλ ŒμΉ˜κ°„ μΆ©λŒμ„ ν•΄κ²°ν•˜κ³  μ½”λ“œ 리뷰가 λλ‚œ PR듀을 λͺ¨μ•„μ„œ, develop λ¨Έμ§€ν•©λ‹ˆλ‹€.

πŸ’¬ To Reviewers

  • 이제 방법을 ν„°λ“ν•˜μ—¬, 이런 pr은 λ§ˆμ§€λ§‰μ΄ 될 것 μž…λ‹ˆλ‹€. μ•„μžμž

βœ… PR check list

  • Reviewers
  • Assignees
  • Labels

μ‚¬μš©μžκ°€ μƒˆλ‘œ λ°œκΈ‰λ°›μ€ FCM 토큰을 μ„œλ²„μ— μ „λ‹¬ν•˜μ—¬ μ—…λ°μ΄νŠΈν•  수 μžˆλŠ” POST /api/v1/users/{userId}/fcm-tokens μ—”λ“œν¬μΈνŠΈλ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μš”μ²­ μ‹œ μƒˆ 토큰을 UserService둜 전달해 κ°±μ‹  처리
- 성곡 μ‹œ HTTP 201 μƒνƒœμ™€ FCM_TOKEN_UPDATED 성곡 μ½”λ“œλ₯Ό λ°˜ν™˜
μ‚¬μš©μžμ˜ FCM 토큰을 κ°±μ‹ ν•˜κΈ° μœ„ν•œ FcmTokenUpdateService μΈν„°νŽ˜μ΄μŠ€λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- updateFcmToken(Long userId, String newToken) λ©”μ„œλ“œλ‘œ 토큰 κ°±μ‹  μ±…μž„ λΆ€μ—¬
UserRepositoryλ₯Ό 톡해 μ‚¬μš©μžλ₯Ό μ‘°νšŒν•œ ν›„, FcmTokenUpdateService μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 전달받은 μƒˆ 토큰을 μ—…λ°μ΄νŠΈν•˜λŠ” λ‘œμ§μ„ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- updateFcmToken λ©”μ„œλ“œμ—μ„œ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ‚¬μš©μž ID일 경우 μ˜ˆμ™Έ 처리
- νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ User μ—”ν‹°ν‹°μ˜ FCM 토큰을 κ°±μ‹ 
μƒˆλ‘œμš΄ FCM 토큰을 전달받아 μœ μ € 객체에 λ°˜μ˜ν•  수 μžˆλŠ” updateFcmToken λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.
- FcmTokenUpdateServiceλ₯Ό ν™œμš©ν•΄ μ‚¬μš©μž FCM 토큰 κ°±μ‹ 
- @transactional을 μ μš©ν•˜μ—¬ DB λ³€κ²½ 사항 반영
FCM 토큰을 μƒˆλ‘œ κ°±μ‹ ν–ˆμ„ λ•Œ λ°˜ν™˜ν•  FCM_TOKEN_UPDATED μƒνƒœ μ½”λ“œλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.
- 201 Created μƒνƒœμ½”λ“œμ™€ "FCM 토큰이 μ„±κ³΅μ μœΌλ‘œ μ—…λ°μ΄νŠΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€." λ©”μ‹œμ§€λ‘œ ꡬ성
isFcmTokenExpired λ©”μ„œλ“œ λ‚΄ λΆˆν•„μš”ν•œ 두 번의 return 문을 μ •λ¦¬ν•˜κ³ , currentToken λ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜λ„λ‘ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€.
- μ€‘λ³΅λœ return 제거
- μ½”λ“œ 가독성과 μœ μ§€λ³΄μˆ˜μ„± ν–₯상
κΈ°μ‘΄ 토큰 객체가 μƒˆ κ°’μœΌλ‘œ ꡐ체될 수 μžˆλ„λ‘ updateValue λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μƒˆλ‘œμš΄ FcmToken μΈμŠ€ν„΄μŠ€λ₯Ό 생성해 μƒνƒœ λ³€κ²½ λ‘œμ§μ„ μΊ‘μŠν™”
- 객체지ν–₯적 μ±…μž„ 뢄리λ₯Ό κ°•ν™”
FCM 토큰을 μƒˆλ‘œ λ°œκΈ‰λ°›μ•„ μ„œλ²„μ— μ „λ‹¬ν•˜κΈ° μœ„ν•œ μš”μ²­ DTOλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.
- newToken ν•„λ“œ 및 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ of μ •μ˜
FcmTokenUpdateServiceImpl에 λŒ€ν•œ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μœ νš¨ν•œ μ‚¬μš©μž ID와 μƒˆλ‘œμš΄ ν† ν°μœΌλ‘œ μš”μ²­ν•  λ•Œ μ •μƒμ μœΌλ‘œ μ—…λ°μ΄νŠΈλ˜λŠ”μ§€ 확인
- μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ‚¬μš©μž ID에 λŒ€ν•΄ USER_NOT_FOUND μ˜ˆμ™Έ λ°œμƒ 검증
- null 토큰에 λŒ€ν•œ FCM_TOKEN_NOT_NULL μ˜ˆμ™Έ λ°œμƒ 검증
- μ΄ˆκΈ°ν™” 성곡/μ‹€νŒ¨ 둜그 λ©”μ‹œμ§€λ₯Ό FcmLogMessages μƒμˆ˜λ‘œ λΆ„λ¦¬ν•˜μ—¬ μž¬μ‚¬μš©μ„±κ³Ό 일관성 ν–₯상
- μ„œλΉ„μŠ€ ν‚€ λ¬Έμžμ—΄ 인코딩을 StandardCharsets.UTF_8μ—μ„œ Encoding.UTF_8둜 ν†΅μΌν•˜μ—¬ μ½”λ“œ 일관성 μœ μ§€
- FcmConfig, FcmProperties 클래슀λ₯Ό κΈ°μ‘΄ user.config νŒ¨ν‚€μ§€μ—μ„œ fcm.config νŒ¨ν‚€μ§€λ‘œ μ΄λ™ν•˜μ—¬ 관심사 뢄리
- @EnableConfigurationProperties(FcmProperties.class) μœ„μΉ˜λ₯Ό TerningApplicationμ—μ„œ μ œκ±°ν•˜κ³  FcmConfig둜 μ΄λ™ν•˜μ—¬ ν…ŒμŠ€νŠΈ ν™˜κ²½μ—μ„œμ˜ λ‘œλ”© 였λ₯˜ λ°©μ§€
- μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ΄ˆκΈ°ν™” μ‹œ Firebase 섀정이 곡톡 λͺ¨λ“ˆλ‘œ κ΄€λ¦¬λ˜λ„λ‘ ꡬ쑰 정리
[✨ feat] FCM 토큰 μž¬λ°œκΈ‰ API κ΅¬ν˜„
- Message.of(template, params) ν˜•νƒœλ‘œ κ°œμ„ ν•˜μ—¬ μ™ΈλΆ€μ—μ„œ 포맷된 λ©”μ‹œμ§€λ₯Ό λ„˜κΈ°λ˜ 방식 제거
- 메인/μ„œλΈŒ λ©”μ‹œμ§€μ˜ 포맷 μ±…μž„μ„ Message λ‚΄λΆ€λ‘œ μœ„μž„ν•˜μ—¬ 응집도 ν–₯상
- 메인 λ©”μ‹œμ§€λ„ ν™•μž₯μ„± κ³ λ €ν•΄ νŒŒλΌλ―Έν„° 기반 포맷 ꡬ쑰둜 톡일
- Message.of(template, params) μ‚¬μš© 방식에 맞게 ν…ŒμŠ€νŠΈ μ½”λ“œ μ „λ°˜ μˆ˜μ •
- 메인 λ©”μ‹œμ§€λ„ νŒŒλΌλ―Έν„° 기반 포맷 ꡬ쑰λ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ null λŒ€μ‹  VALID_PARAMS 전달
- μ‹€νŒ¨ μΌ€μ΄μŠ€μ—μ„œλ„ Message λ‚΄λΆ€ 포맷 λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•˜λ„λ‘ λ³€κ²½
- `application.yml`, `application-dev.yml` νŒŒμΌμ„ `.gitignore`에 μΆ”κ°€ν•˜μ—¬ λ―Όκ°ν•œ μ„€μ • 정보가 Git에 ν¬ν•¨λ˜μ§€ μ•Šλ„λ‘ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- QueryDSLμ—μ„œ μƒμ„±λ˜λŠ” Q 클래슀 경둜(`src/main/generated`)도 λ¬΄μ‹œ λŒ€μƒμ— ν¬ν•¨μ‹œμΌœ λΉŒλ“œ 결과물이 μΆ”μ λ˜μ§€ μ•Šλ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.
- QueryDSL 섀정을 μœ„ν•œ μ˜μ‘΄μ„± 및 κ΄€λ ¨ 디렉토리(`src/main/generated/querydsl/`) 섀정을 μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `compileOnly` μ„€μ • 쀑볡을 μ œκ±°ν•˜κ³  `querydsl` 섀정을 `compileClasspath`에 μ—°κ²°ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- WebFlux, Spring Batch μ˜μ‘΄μ„±μ„ ν”„λ‘œμ νŠΈμ— μΆ”κ°€ν•˜μ—¬ κ΄€λ ¨ κΈ°λŠ₯ 개발 μ€€λΉ„λ₯Ό μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€.
- PostgreSQL μ˜μ‘΄μ„± μ„ μ–Έ 쀑 μ€‘λ³΅λœ ꡬ문을 μ •λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- QueryDSL μ‚¬μš©μ„ μœ„ν•΄ `JPAQueryFactory`λ₯Ό 빈으둜 λ“±λ‘ν•˜λŠ” `QuerydslConfig` 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `EntityManager`λ₯Ό μ£Όμž…λ°›μ•„ `JPAQueryFactory`λ₯Ό μƒμ„±ν•˜λ©°, μ˜μ‘΄μ„± μ£Όμž…μ„ 톡해 QueryDSL 쿼리 객체λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
- ν•΄λ‹Ή 섀정을 톡해 μ„œλΉ„μŠ€ 및 리포지토리 λ‹¨μ—μ„œ QueryDSL을 νŽΈλ¦¬ν•˜κ²Œ ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
- μ™ΈλΆ€ API ν˜ΈμΆœμ„ μœ„ν•œ `WebClient` μ„€μ • 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `WebClient.Builder`λ₯Ό 빈으둜 μ£Όμž…λ°›μ•„ 곡용 `WebClient` μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
- ν–₯ν›„ 비동기 HTTP μš”μ²­ 처리 μ‹œ μž¬μ‚¬μš© κ°€λŠ₯ν•œ ν΄λΌμ΄μ–ΈνŠΈλ‘œ ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
- 운영 μ„œλ²„μ—μ„œ 전달받은 슀크랩 μœ μ € 정보λ₯Ό ν‘œν˜„ν•˜κΈ° μœ„ν•œ `OpsScrapUserResponse` record 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μœ μ € IDλ₯Ό 단일 κ°’μœΌλ‘œ κ°€μ§€λ©°, 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ `from`을 톡해 λͺ…μ‹œμ μΈ 생성 방식을 μ œκ³΅ν•©λ‹ˆλ‹€.
- ν–₯ν›„ μ•Œλ¦Ό μ„œλ²„μ—μ„œ 슀크랩 동기화 μš”μ²­ 처리 μ‹œ μ‚¬μš©λ©λ‹ˆλ‹€.
- 운영 μ„œλ²„λ‘œλΆ€ν„° μˆ˜μ‹ ν•œ λ‹€μˆ˜μ˜ 슀크랩 μœ μ € IDλ₯Ό 응닡 ν˜•νƒœλ‘œ κ°μ‹ΈλŠ” `OpsScrapUsersResponse` record 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μœ μ € ID 리슀트λ₯Ό `OpsScrapUserResponse`둜 λ³€ν™˜ν•˜λŠ” 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ `of`λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
- 응닡 ꡬ쑰λ₯Ό λͺ…ν™•νžˆ λΆ„λ¦¬ν•˜μ—¬ μ™ΈλΆ€ API 톡신 μ‹œ μΌκ΄€λœ 포맷을 μœ μ§€ν•  수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- 운영 μ„œλ²„μ˜ `/api/v1/external/scraps/unsynced` APIλ₯Ό ν˜ΈμΆœν•˜μ—¬ λ―Έλ™κΈ°ν™”λœ 슀크랩 μœ μ € λͺ©λ‘μ„ κ°€μ Έμ˜€λŠ” ν΄λΌμ΄μ–ΈνŠΈλ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- WebClientλ₯Ό μ‚¬μš©ν•˜μ—¬ 비동기 λ°©μ‹μœΌλ‘œ ν˜ΈμΆœν•˜κ³ , 응닡은 `OpsScrapUsersResponse`둜 νŒŒμ‹±λ©λ‹ˆλ‹€.
- API URL은 `application.yml`에 μ„€μ •λœ `internal.api.scrap.unsynced` 값을 톡해 μ£Όμž…λ°›μŠ΅λ‹ˆλ‹€.
- ν–₯ν›„ 배치 μž‘μ—…μ—μ„œ 운영 μ„œλ²„μ™€ 동기화λ₯Ό μœ„ν•œ 핡심 의쑴 μ»΄ν¬λ„ŒνŠΈλ‘œ μ‚¬μš©λ©λ‹ˆλ‹€.
- `/api/v1/scraps/sync` POST APIλ₯Ό 톡해 운영 μ„œλ²„μ—μ„œ λ―Έλ™κΈ°ν™”λœ 슀크랩 μœ μ € 정보λ₯Ό μ‘°νšŒν•˜κ³  μ €μž₯ν•˜λŠ” κΈ°λŠ₯을 κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- λ‚΄λΆ€ λ‘œμ§μ€ `ScrapService`의 `fetchAndSaveScrapUsersFromOps()` λ©”μ„œλ“œλ₯Ό 톡해 μ²˜λ¦¬λ©λ‹ˆλ‹€.
- μ™ΈλΆ€ μ‹œμŠ€ν…œκ³Όμ˜ 슀크랩 동기화λ₯Ό μœ„ν•œ μ§„μž…μ  역할을 ν•©λ‹ˆλ‹€.
- 운영 μ„œλ²„μ—μ„œ 전달받은 미동기화 슀크랩 μœ μ € 정보λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ `ScrapService` 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- λ‚΄λΆ€μ—μ„œλŠ” `ScrapStatusSyncService`λ₯Ό 톡해 μ‹€μ œ 동기화 λ‘œμ§μ„ μœ„μž„ μ²˜λ¦¬ν•˜λ©°, νŠΈλžœμž­μ…˜μ„ μ μš©ν•˜μ—¬ 일관성을 보μž₯ν•©λ‹ˆλ‹€.
- μ»¨νŠΈλ‘€λŸ¬μ™€ 도메인 μ„œλΉ„μŠ€ μ‚¬μ΄μ˜ μ—°κ²° 역할을 μˆ˜ν–‰ν•˜λŠ” κ³„μΈ΅μž…λ‹ˆλ‹€.
- 운영 μ„œλ²„λ‘œλΆ€ν„° 전달받은 슀크랩 μœ μ € 정보λ₯Ό 기반으둜 동기화 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κΈ° μœ„ν•œ 도메인 μ„œλΉ„μŠ€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- ν–₯ν›„ κ΅¬ν˜„μ²΄μ—μ„œ 동기화 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ λ‹΄λ‹Ήν•˜κ²Œ 되며, μ„œλΉ„μŠ€ 계측(`ScrapService`)μ—μ„œ ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 λ‘œμ§μ„ μœ„μž„ν•©λ‹ˆλ‹€.
- μœ μ—°ν•œ κ΅¬ν˜„μ²΄ ꡐ체 및 ν…ŒμŠ€νŠΈ μš©μ΄μ„±μ„ κ³ λ €ν•œ κ΅¬μ‘°μž…λ‹ˆλ‹€.
- `ScrapStatusSyncService` μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ²΄λ‘œ `ScrapStatusSyncServiceImpl`을 μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `OpsScrapUserClient`λ₯Ό 톡해 운영 μ„œλ²„μ—μ„œ λ―Έλ™κΈ°ν™”λœ μœ μ € λͺ©λ‘μ„ μ‘°νšŒν•˜κ³ , μœ μ € ID 리슀트λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€.
- μœ μ € ID λͺ©λ‘μ΄ λΉ„μ–΄μžˆμ§€ μ•Šμ€ 경우 `ScrapSyncManager`λ₯Ό 톡해 동기화 처리λ₯Ό μœ„μž„ν•©λ‹ˆλ‹€.
- μ™ΈλΆ€ API 연동뢀터 동기화 μ‹€ν–‰κΉŒμ§€μ˜ 전체 ν”„λ‘œμ„ΈμŠ€λ₯Ό λ‹΄λ‹Ήν•˜λŠ” 핡심 λ‘œμ§μž…λ‹ˆλ‹€.
- 운영 μ„œλ²„μ—μ„œ 전달받은 μœ μ € ID λͺ©λ‘μ„ 기반으둜 슀크랩 데이터λ₯Ό μƒμ„±ν•˜κ±°λ‚˜ μƒνƒœλ₯Ό κ°±μ‹ ν•˜λŠ” `ScrapSyncManager`λ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μœ μ € 정보와 κΈ°μ‘΄ 슀크랩 데이터λ₯Ό Map으둜 κ΅¬μ„±ν•˜μ—¬ 효율적인 쑰회 및 비ꡐλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
- `createOrUpdateScrap` λ©”μ„œλ“œλ‘œ μœ μ €λ³„ 처리 λ‘œμ§μ„ λΆ„λ¦¬ν•˜μ—¬ 가독성과 μ±…μž„ 뢄리λ₯Ό κ°•ν™”ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- μ‹ κ·œ μœ μ €μ— λŒ€ν•΄μ„œλŠ” μŠ€ν¬λž©μ„ μƒμ„±ν•˜κ³ , κΈ°μ‘΄ μœ μ € 쀑 미슀크랩 μƒνƒœμΈ κ²½μš°μ—λ§Œ μƒνƒœλ₯Ό κ°±μ‹ ν•˜λ„λ‘ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
- Spring Batch의 `ItemProcessor`λ₯Ό κ΅¬ν˜„ν•œ `ScrapUserItemProcessor` 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- 운영 μ„œλ²„λ‘œλΆ€ν„° μ „λ‹¬λœ 슀크랩 μœ μ € 데이터λ₯Ό 둜그둜 ν™•μΈν•˜λ©°, 가곡 없이 κ·ΈλŒ€λ‘œ λ‹€μŒ λ‹¨κ³„λ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€.
- μœ μ € ID 둜그λ₯Ό 톡해 처리 흐름 좔적이 κ°€λŠ₯ν•˜λ„λ‘ 디버깅 λ©”μ‹œμ§€λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- ν–₯ν›„ 데이터 가곡이 ν•„μš”ν•  경우 ν™•μž₯ κ°€λŠ₯성을 염두에 λ‘” κ΅¬μ‘°μž…λ‹ˆλ‹€.
- Spring Batch의 `ItemReader`λ₯Ό κ΅¬ν˜„ν•œ `ScrapUserItemReader` 클래슀λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `OpsScrapUserClient`λ₯Ό 톡해 운영 μ„œλ²„μ—μ„œ λ―Έλ™κΈ°ν™”λœ 슀크랩 μœ μ € λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.
- 졜초 read μ‹œ 리슀트λ₯Ό μ‘°νšŒν•˜κ³ , μ΄ν›„μ—λŠ” 반볡자λ₯Ό 톡해 ν•˜λ‚˜μ”© 처리 λŒ€μƒ μœ μ €λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
- 둜그λ₯Ό 톡해 μœ μ € 쑰회 μ‹œμž‘ μ‹œμ μ„ λͺ…ν™•νžˆ κΈ°λ‘ν•˜λ©° 배치 흐름을 좔적할 수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- 맀일 20μ‹œ 30뢄에 슀크랩 μœ μ € 동기화 배치λ₯Ό μ‹€ν–‰ν•˜λŠ” `ScrapUserSyncJobScheduler`λ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `JobLauncher`λ₯Ό 톡해 `scrapSyncJob`을 μ‹€ν–‰ν•˜λ©°, λ§€ μ‹€ν–‰λ§ˆλ‹€ κ³ μœ ν•œ `run.id` νŒŒλΌλ―Έν„°λ₯Ό λΆ€μ—¬ν•©λ‹ˆλ‹€.
- 성곡/μ‹€νŒ¨ 여뢀에 따라 둜그λ₯Ό 좜λ ₯ν•˜μ—¬ λͺ¨λ‹ˆν„°λ§μ΄ κ°€λŠ₯ν•˜λ„λ‘ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- 배치 μ‹€ν–‰ μŠ€μΌ€μ€„μ€ cron ν‘œν˜„μ‹ `"0 30 20 * * *"`둜 μ„€μ •λ˜μ–΄ 있으며, 운영 ν™˜κ²½ 기쀀에 따라 λ³€κ²½ κ°€λŠ₯ν•©λ‹ˆλ‹€.
- Spring Batch 기반의 슀크랩 μœ μ € 동기화 배치 μž‘μ„ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- `scrapUserSyncJob`은 단일 Step으둜 κ΅¬μ„±λ˜λ©°, Reader β†’ Processor β†’ Writer 순으둜 μœ μ € 동기화λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€.
- chunk λ‹¨μœ„λŠ” 100으둜 μ„€μ •λ˜μ–΄ 있으며, μž₯μ•  볡원λ ₯을 μœ„ν•΄ μ˜ˆμ™Έ λ°œμƒ μ‹œ μ΅œλŒ€ 3νšŒκΉŒμ§€ μž¬μ‹œλ„ν•˜λ„λ‘ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
- Step 이름에 μ˜€νƒ€(`"s crapUserSyncStep"`)κ°€ μžˆμ–΄ `"scrapUserSyncStep"`으둜의 μˆ˜μ •μ΄ ν•„μš”ν•©λ‹ˆλ‹€.
μ•Œλ¦Ό 전솑 μ‹œκ°μ„ ν‘œν˜„ν•˜λŠ” κ°’ 객체 ScheduledTime을 κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

- of(LocalDateTime) 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ 제곡
- isDue(now): μ§€μ •λœ μ‹œκ°μ΄ ν˜„μž¬ μ‹œκ° 이전인지 νŒλ‹¨
- JPA @embeddable 및 λΆˆλ³€ 객체 섀계 적용
μ•Œλ¦Όμ˜ λ°œμ†‘ μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ” κ°’ 객체 SentStatusλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

- markNotSent(): 초기 μƒνƒœ
- markSent(): λ°œμ†‘ μ™„λ£Œ μƒνƒœλ‘œ μ „ν™˜
- isSent(), value(): λ°œμ†‘ μ—¬λΆ€ 확인

JPA @embeddable 적용 및 λΆˆλ³€ 객체 νŒ¨ν„΄μ„ 기반으둜 μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€.
μ•Œλ¦Ό 생성을 μœ„ν•œ μš”μ²­ 데이터λ₯Ό λ‹΄λŠ” DTO인 NotificationCreateRequestλ₯Ό μ •μ˜ν–ˆμŠ΅λ‹ˆλ‹€.

- ν…œν”Œλ¦Ώ νƒ€μž… 전달을 μœ„ν•œ template ν•„λ“œ 포함
- 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ of(String) 제곡
- CRON μŠ€μΌ€μ€„, run.id ν‚€ 값을 μƒμˆ˜λ‘œ μΆ”μΆœν•˜μ—¬ μž¬μ‚¬μš©μ„± ν–₯상
- λΆˆν•„μš”ν•œ 둜그 μƒμˆ˜ 제거 및 log λ©”μ‹œμ§€ 톡일
- μ˜ˆμ™Έ λ°œμƒ μ‹œ ScrapException을 λ˜μ§€λ„λ‘ 처리 방식 κ°œμ„ 
슀크랩 동기화 배치 μ‹€ν–‰ 쀑 μ˜ˆμ™Έ 상황을 μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ SYNC_JOB_FAILED μ—λŸ¬ μ½”λ“œλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.
슀크랩된 μœ μ € 쑰회 κΈ°λŠ₯ κ°•ν™”λ₯Ό μœ„ν•΄ ScrapRepositoryCustom에 μ‚¬μš©μž 기반 쑰회 λ©”μ„œλ“œλ₯Ό μ •μ˜ν–ˆμŠ΅λ‹ˆλ‹€.

- findScrapsByUserIds(List<Long>)
- findDistinctScrappedUsers()
슀크랩 μƒνƒœκ°€ SCRAPPED인 μœ μ €λ§Œ μ‘°νšŒν•˜λŠ” findDistinctScrappedUsers λ©”μ„œλ“œλ₯Ό ScrapRepositoryImpl에 κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

- distinct + fetchJoin으둜 쀑볡 제거 및 μ„±λŠ₯ μ΅œμ ν™”
Notifications ν΄λž˜μŠ€μ— λΆˆλ³€μ„±μ„ 보μž₯ν•˜λŠ” 일급 μ»¬λ ‰μ…˜μ„ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

- `add()`, `remove()` λ©”μ„œλ“œλ‘œ μƒˆλ‘œμš΄ μ•Œλ¦Ό μΆ”κ°€ 및 제거 μ‹œ λΆˆλ³€ μ»¬λ ‰μ…˜ λ°˜ν™˜
- `values()` λ©”μ„œλ“œλ‘œ μ•Œλ¦Ό 리슀트λ₯Ό λΆˆλ³€ 리슀트둜 λ°˜ν™˜
- `empty()` λ©”μ„œλ“œλ₯Ό 톡해 빈 μ•Œλ¦Ό λͺ©λ‘μ„ 생성할 수 μžˆλ„λ‘ 함
Scraps ν΄λž˜μŠ€μ— λΆˆλ³€μ„±μ„ 보μž₯ν•˜λŠ” 일급 μ»¬λ ‰μ…˜μ„ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

- `add()`, `remove()` λ©”μ„œλ“œλ‘œ μƒˆλ‘œμš΄ 슀크랩 μΆ”κ°€ 및 제거 μ‹œ λΆˆλ³€ μ»¬λ ‰μ…˜ λ°˜ν™˜
- `values()` λ©”μ„œλ“œλ‘œ 슀크랩 리슀트λ₯Ό λΆˆλ³€ 리슀트둜 λ°˜ν™˜
- `empty()` λ©”μ„œλ“œλ₯Ό 톡해 빈 슀크랩 λͺ©λ‘μ„ 생성할 수 μžˆλ„λ‘ 함
User μ—”ν‹°ν‹°μ—μ„œ μ€‘λ³΅λœ notifications와 scraps ν•„λ“œλ₯Ό μ œκ±°ν•˜κ³ , 일급 μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•˜λ„λ‘ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€.

- notifications ν•„λ“œ: List<Notifications> β†’ List<Notification>으둜 λ³€κ²½
- scraps ν•„λ“œ: List<Scrap> β†’ 일급 μ»¬λ ‰μ…˜μΈ Scraps둜 λ³€κ²½
- λΆˆν•„μš”ν•œ ν•„λ“œ 쀑볡을 ν•΄κ²°ν•˜μ—¬ 가독성과 μ½”λ“œ μ•ˆμ •μ„± ν–₯상
- λ©”μ‹œμ§€ ν¬λ§·μ—μ„œ {username}이 ν¬ν•¨λ˜μ§€ μ•Šλ„λ‘ κ²€μ¦ν•˜λŠ” ν…ŒμŠ€νŠΈ μΆ”κ°€
- λͺ¨λ“  μœ μ €μ—κ²Œ '졜근 인턴십 μΆ”μ²œ' μ•Œλ¦Ό 생성 ν…ŒμŠ€νŠΈ κ΅¬ν˜„
- μŠ€ν¬λž©ν•œ μœ μ €μ—κ²Œ '관심 곡고 μ•Œλ¦Ό' 생성 ν…ŒμŠ€νŠΈ κ΅¬ν˜„
- μŠ€ν¬λž©ν•˜μ§€ μ•Šμ€ μœ μ €μ—κ²Œ μ•Œλ¦Όμ΄ μƒμ„±λ˜μ§€ μ•Šλ„λ‘ κ²€μ¦ν•˜λŠ” ν…ŒμŠ€νŠΈ κ΅¬ν˜„
- μœ μ €κ°€ μ—†λŠ” 경우 μ•Œλ¦Όμ΄ μƒμ„±λ˜μ§€ μ•ŠμŒμ„ κ²€μ¦ν•˜λŠ” ν…ŒμŠ€νŠΈ κ΅¬ν˜„
- Notification μ—”ν‹°ν‹°μ˜ μ €μž₯, 쑰회, μ‚­μ œ κΈ°λŠ₯을 λͺ¨ν‚Ήν•˜μ—¬ ν…ŒμŠ€νŠΈν•˜λŠ” Repository κ΅¬ν˜„
- JPA의 κΈ°λ³Έ λ©”μ„œλ“œλ“€μ— λŒ€ν•œ κ΅¬ν˜„ μΆ”κ°€ (save, findById, delete λ“±)
- ν…ŒμŠ€νŠΈ ν™˜κ²½μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λ©”λͺ¨λ¦¬ 기반 Repository κ΅¬ν˜„
- Scrap μ—”ν‹°ν‹°μ˜ μ €μž₯, 쑰회, μ‚­μ œ κΈ°λŠ₯을 λͺ¨ν‚Ήν•˜μ—¬ ν…ŒμŠ€νŠΈν•˜λŠ” Repository κ΅¬ν˜„
- 슀크랩 μƒνƒœκ°€ 'SCRAPPED'인 μœ μ €λ₯Ό κ΅¬λ³„ν•˜λŠ” findDistinctScrappedUsers λ©”μ„œλ“œ κ΅¬ν˜„
- JPA의 κΈ°λ³Έ λ©”μ„œλ“œλ“€μ„ λͺ¨ν‚Ήν•˜μ—¬ ν…ŒμŠ€νŠΈ ν™˜κ²½μ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘ Repository κ΅¬ν˜„
[♻️ refactor] λ©”μ‹œμ§€ 포맷 λ‘œμ§μ„ λ‚΄λΆ€λ‘œ μ΄λ™ν•˜μ—¬ 생성 μ±…μž„ λͺ…ν™•ν™” 및 응집도 κ°œμ„ 
[✨ feat] μš΄μ˜μ„œλ²„μ—μ„œ 슀크랩 μœ μ € 쑰회 ν›„ μ €μž₯ν•˜λŠ” 동기화 둜직 κ΅¬ν˜„
[✨ feat] λ©”μ‹œμ§€-μœ μ € 쑰합을 톡해 μ•Œλ¦Ό 생성 및 μ €μž₯ 배치 κ΅¬ν˜„
@jsoonworld jsoonworld requested a review from Copilot April 1, 2025 13:24
@jsoonworld jsoonworld self-assigned this Apr 1, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a notification service implementation with scheduling support and updates several configuration components.

  • Introduces a NotificationCreationExecutor interface and corresponding command/service implementations.
  • Adds a REST controller to expose notification creation and integrates scheduling policies for notifications.
  • Adds or updates configuration classes for Firebase (FCM), WebClient, Querydsl, and batch processing support.

Reviewed Changes

Copilot reviewed 79 out of 79 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/main/java/org/terning/notification/application/executor/NotificationCreationExecutor.java New interface for executing notification creation.
src/main/java/org/terning/notification/application/command/NotificationCommandServiceImpl.java Implementation of the notification command service.
src/main/java/org/terning/notification/application/command/NotificationCommandService.java Notification command service interface definition.
src/main/java/org/terning/notification/application/NotificationService.java Service layer combining command service and executor calls.
src/main/java/org/terning/notification/api/NotificationController.java REST controller exposing the notification creation API endpoint.
src/main/java/org/terning/message/domain/vo/MessageWeeklyPolicy.java Weekly scheduling policy encapsulation for messages.
src/main/java/org/terning/message/domain/enums/SendSchedulePolicy.java Enum implementation for scheduling policies.
src/main/java/org/terning/message/domain/enums/MessageTemplateType.java Enhanced message template type with scheduling and target type updates.
src/main/java/org/terning/message/domain/Message.java Updated Message class adapting field names and factory method.
src/main/java/org/terning/infra/ops/scrap/dto/response/OpsScrapUsersResponse.java Response record for unsynced scrap users.
src/main/java/org/terning/infra/ops/scrap/dto/response/OpsScrapUserResponse.java Record for an individual scrap user response.
src/main/java/org/terning/infra/ops/scrap/OpsScrapUserClient.java Client for fetching unsynced users using WebClient.
src/main/java/org/terning/global/config/WebClientConfig.java WebClient bean configuration.
src/main/java/org/terning/global/config/QuerydslConfig.java JPAQueryFactory bean configuration using Querydsl.
src/main/java/org/terning/fcm/config/FcmConfig.java Firebase (FCM) configuration with custom properties and logging.
src/main/java/org/terning/TerningApplication.java Main application class with added batch processing support.

@jsoonworld jsoonworld added 🦊μž₯순🦊 πŸ”₯ !hotfix κΈ‰ν•˜κ²Œ 치λͺ…적인 버그λ₯Ό 고쳐야 ν•˜λŠ” 경우 πŸ”€ merge λ‹€λ₯Έ λΈŒλžœμΉ˜μ™€ 병합 and removed πŸ”₯ !hotfix κΈ‰ν•˜κ²Œ 치λͺ…적인 버그λ₯Ό 고쳐야 ν•˜λŠ” 경우 labels Apr 1, 2025
@jsoonworld jsoonworld changed the title Feat/#47 after [πŸ”€ merge] λΈŒλ ŒμΉ˜λ“€κ°„ 좩돌 ν•΄κ²° Apr 1, 2025
@jsoonworld jsoonworld merged commit a0871aa into develop Apr 1, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

πŸ”€ merge λ‹€λ₯Έ λΈŒλžœμΉ˜μ™€ 병합 size/XXL 🦊μž₯순🦊

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants