πβμ§λμ¨ λ‘μ§μ κ²°κ΅ μν λ¨Έμ μ΄λ€ β ProgressService μ€κ³κΈ°β
π 2025λ 4μ | βοΈ by λ°μ°¬ν¬ |
π§ λ¬Έμ μ μ: λ¨μν νΌμΌνΈκ° μλ, νμ΅ μνμ νλ¦
μ¬μ©μμ μ½ν μΈ νμ΅ μ§ν μν©μ βμ«μ νλ(%)βλ‘ νννλ 건 μ¬μ 보μ΄μ§λ§,
μ΄λ₯Ό μ λ’°ν μ μκ² μ μ₯νκ³ , μλ£ μ‘°κ±΄μ λ°λΌ μνλ₯Ό μ νν μ ννλ 건 μμ§ μμ μΌμ΄λ€.
μ΄λ² λͺ¨λμ λͺ©νλ λ¨μν progressPercent μ μ₯μ΄ μλλΌ,
μ νν μν μ μ΄(μ§λμ¨ β μλ£)κΉμ§ μ± μμ§λ βνμ΅ μν λ¨Έμ βμ μ€κ³νλ λ° μμλ€.
π― μ€κ³ λͺ©ν: μ§λμ¨ μ μ₯ + μλ£ μλν + νΌν¬λ¨Όμ€ μ΅μ ν
νλͺ© | μ λ΅ |
---|---|
π‘ λ¨μΌ μ§μ μ | λͺ¨λ λ‘μ§μ saveOrUpdateProgress() μ μ§μ€ |
π μλ£ μν μλν | μ 체 μ½ν μΈ μλ£ μ μλ£ μ²λ¦¬ |
πΎ DB λΆν μ΅μν | λΆνμν μ
λ°μ΄νΈ μ κ±° (WHERE progress < newValue ) |
π§ͺ ν μ€νΈ κ°λ₯μ± | JUnit κΈ°λ° λ¨μ ν μ€νΈ κ΅¬μ‘°λ‘ μ€κ³ |
π νμ₯μ± | Redis/Kafka κΈ°λ° νμ₯ μ¬μ§ ν보 |
π§± μ 체 ꡬ쑰 νλ¦
[μ¬μ©μ μ½ν
μΈ μ¬μ]
β
[ProgressService.saveOrUpdateProgress()]
βββ κΈ°μ‘΄ μ§λ < μ κ· μ§λμΌ κ²½μ°λ§ upsert
βββ lectureId μμΆμ (contentId β lectureId)
βββ μμ¬ λ―Έμλ£ μ½ν
μΈ μ = 0 β μλ£ μ²λ¦¬(markEnrollmentComplete)
π§ ν΅μ¬ λ‘μ§ μμΈ
1οΈβ£ μ§λμ¨ μ μ₯ - Upsert μ΅μ ν
mapper.upsertProgress(userId, contentId, progressPercent);
- κΈ°μ‘΄ μ§λμ¨λ³΄λ€ λμ κ²½μ°μλ§ κ°±μ β μλ―Έ μλ UPDATE λ°©μ§
- DB 쿼리μμ μ체 λΉκ΅ 쑰건 ν¬ν¨:
UPDATE user_progress
SET progress_percent = #{progressPercent}
WHERE content_id = #{contentId} AND user_id = #{userId} AND progress_percent < #{progressPercent}
β DB λΆνλ₯Ό μ΅μννκ³ , λ€νΈμν¬ λμλ μ€μ¬μ€
2οΈβ£ μλ£ μν μλν β μ ννκ³ λͺ ννκ²
int remaining = mapper.countIncompleteContents(userId, lectureId);
if (remaining == 0) {
mapper.markEnrollmentComplete(userId, lectureId);
}
lectureId
λcontentId
λ‘λΆν° μμΆμ - λ¨μ μ½ν μΈ κ° 0 β μλ£ μνλ‘ μ μ΄
π μ€λ¬΄μμλ βνλλΌλ μλ£ μ λ μ½ν μΈ κ° μμΌλ©΄ μλ£ Xβκ° λ§€μ° μ€μ
β μ¬μ©μμ μ λ’°μ 보μ 체κ³λ₯Ό μ§ν€λ κΈ°λ°μ΄ λλ€.
3οΈβ£ μ§λμ¨ μ‘°ν β Map κΈ°λ° κ΅¬μ‘°
Map<Integer, Integer> getProgressMap(userId, lectureId);
- νλ‘ νΈ(JSP, React λ±)μμ
contentId β percent
λ§€νμ΄ λ°λ‘ κ°λ₯ - λ°λ³΅ λ λλ§μ μ§κ΄μ μ΄λ©°, μΆκ° νλ νμ₯μλ μ 리
β μ€μκ° μ§λμ¨ UI ꡬμ±μ μ΄μμ μΈ κ΅¬μ‘°
βοΈ νΈλμμ & μμΈ μ²λ¦¬ μ λ΅
try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
...
throw new RuntimeException("β μ§λμ¨ μ μ₯ λλ μλ£ μν μ²λ¦¬ μ€ μ€λ₯", e);
}
try-with-resources
λ‘ SqlSession λμ λ°©μ§- λͺ¨λ νλ¦ μ€ νλλΌλ μ€ν¨ μ μ 체 μμΈ β λΆμμ ν μν μ μ₯ λ°©μ§
- λͺ νν λ©μμ§λ‘ λͺ¨λν°λ§/λ‘κΉ λμ κ°λ₯
π μ±λ₯ κ³ λ € λ° νμ₯ μ λ΅
μ λ΅ | μ€λͺ |
---|---|
λΆνμ UPDATE λ°©μ§ | progress < newValue 쑰건 μ μ© |
μλ£ κ²μ¦ μ΅μν | λ¨μ μ½ν
μΈ μλ§ μ²΄ν¬ (countIncompleteContents ) |
λΉλκΈ°ν νμ₯ | μΆν @Async , Kafkaλ‘ μ²λ¦¬ λΆλ¦¬ κ°λ₯ |
μΊμ± κ³ λ € | Redis κΈ°λ° μ§λμ¨ μΊμ (λν κ°μ λμ) |
λ°°μΉ μ²λ¦¬ | λ°λ³΅μ μΈ progress β complete μ ν μ Batching κ³ λ € κ°λ₯ |
π§ͺ λ¨μ ν μ€νΈ κ³ λ € ꡬ쑰
- Serviceμ DAO λΆλ¦¬ ꡬ쑰
-
saveOrUpdateProgress()
λ λΉμ¦λμ€ λ‘μ§μ κ·Έλλ‘ λ΄κ³ μμ΄β λ¨μ ν μ€νΈλ‘ λ‘μ§ νλ¦ κ²μ¦ κ°λ₯
μ:
@Test
void whenAllContentsComplete_thenLectureMarkedAsComplete() {
// given: λͺ¨λ μ½ν
μΈ μλ£λ μν
// when: λ§μ§λ§ progress μ μ₯
// then: μλ£ μ²λ¦¬ νμΈ
}
π§ μλμ΄ λ¦¬λ·° νΌλλ°± (μ€μ μ½λ 리뷰 μμ½)
νκ° νλͺ© | 리뷰 |
---|---|
β μ μ§λ³΄μμ± | βλͺ¨λ λ‘μ§μ΄ Serviceμ μ§μ€λμ΄ μμ΄ μΆμ κ³Ό λ³κ²½μ΄ μ©μ΄νλ€β |
β λλ©μΈ μ°κ³μ± | βκ΅μ‘ μλΉμ€ λλ©μΈμ λ§λ μλ£ μ μ΄ κ΅¬μ‘°κ° μ€κ³μ λ Ήμ μλ€β |
β DB μ λ΅ | βλΆνμν updateκ° μ°¨λ¨λ μ μ΄ λ§€μ° μ’λ€β |
π νμ₯μ± | βλ©ν° λͺ¨λ κ°μ, μΆμ κΈ°λ° νμ΅μλ λμ κ°λ₯ν ꡬ쑰β |
π ν₯ν νμ₯ κ³ν
- Redis μΊμλ‘ μ§λμ¨ Mapμ μΊμ±νμ¬ λν κ°μ λμ
- Kafka κΈ°λ° μ΄λ²€νΈ μ²λ¦¬λ‘ μλ£ λ‘μ§ λΉλκΈ° μ ν
- μ¬μ©μ νλ λ‘κ·Έ ν μ΄λΈ κ΅¬μΆ β νμ΅ λΆμ/μΆμ² κΈ°λ° μ¬μ©
β μμ½
κΈ°λ₯ | μ€λͺ |
---|---|
μ§λμ¨ μ μ₯ | upsert (κΈ°μ‘΄ κ°λ³΄λ€ ν΄ λλ§ μ μ₯) |
μλ£ μ²λ¦¬ | λͺ¨λ μ½ν μΈ μλ£ μ μλ μ μ΄ |
μ§λμ¨ μ‘°ν | Map<contentId, percent> λ°ν ꡬ쑰 |
νΈλμμ | μ€ν¨ μ μ 체 λ‘€λ°±, μμ λμ λ°©μ§ |
ν μ€νΈμ± | λ¨μ ν μ€νΈλ‘ λͺ¨λ λ‘μ§ νλ¦ νμΈ κ°λ₯ |
π§Ύ νκ³
μ΄λ² ProgressServiceλ λ¨μ μ μ₯μ΄ μλ,
νμ΅ μνλ₯Ό μ μ΄νλ ν΅μ¬ μν λ¨Έμ μΌλ‘ μλνκ² μ€κ³νλ€.
- μ μ μκ²λ μ νν μλ£ κ²½νμ,
- μμ€ν μκ²λ νμ₯μ±κ³Ό μμΈ μμ μ±μ,
- κ°λ°μμκ²λ ν μ€νΈ κ°λ₯ν ꡬ쑰μ μ μ§λ³΄μμ±μ μ 곡νλ
ννν λλ©μΈ μ€μ¬ λͺ¨λμ΄ λμλ€.