Repository navigation
add :: 급식 수동 refresh API 추가 - #657
Hidden character warning
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough현재 월의 기존 식사 데이터를 삭제하고 NEIS에서 동일 월의 식사 데이터를 재조회해 그룹·병합한 뒤 트랜잭션 내에서 삭제·저장하는 재시도 스케줄러 엔드포인트 및 관련 포트·서비스·퍼시스턴스·마이그레이션이 추가·수정되었습니다. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant AdminCtrl as AdminController
participant RetrySvc as RetryMealService
participant NeisFeign as NeisMealFeignClientService
participant MealPort as MealPort
participant Repo as MealRepository
participant TxMgr as TransactionTemplate
Client->>AdminCtrl: POST /admin/scheduler/meal/retry
AdminCtrl->>RetrySvc: execute()
RetrySvc->>RetrySvc: 현재 YearMonth 결정
RetrySvc->>NeisFeign: getNeisInfoToEntityByYearMonth(yearMonth)
NeisFeign-->>RetrySvc: 식사 데이터 목록
RetrySvc->>RetrySvc: 날짜·타입별 그룹화 및 메뉴 병합
RetrySvc->>TxMgr: 트랜잭션 시작
RetrySvc->>MealPort: deleteByYearMonth(yearMonth)
MealPort->>Repo: deleteAllByMealDateBetween(start, end)
Repo-->>MealPort: 삭제 완료
RetrySvc->>MealPort: save(Meal) 반복 호출
MealPort->>Repo: 저장
Repo-->>MealPort: 저장 완료
TxMgr-->>RetrySvc: 트랜잭션 커밋
RetrySvc-->>AdminCtrl: 완료
AdminCtrl-->>Client: 200 OK
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/main/kotlin/dsm/pick2024/infrastructure/feign/neis/NeisMealFeignClientService.kt (1)
42-43: 날짜 포맷은DateTimeFormatter.BASIC_ISO_DATE직접 사용을 권장합니다.Line [42]-Line [43]은
toString()후 별도 변환 함수를 거치는데, Java Time 포맷터를 직접 쓰면 더 단순하고 안전합니다.리팩터 예시
import java.time.LocalDate import java.time.YearMonth import java.time.format.DateTimeFormatter @@ class NeisMealFeignClientService( @@ ) { + private val ymdFormatter: DateTimeFormatter = DateTimeFormatter.BASIC_ISO_DATE @@ sdSchoolCode = NeisFeignClientRequestProperty.SD_SCHUL_CODE, atptCode = NeisFeignClientRequestProperty.ATPT_OFCDC_CODE, - startedYmd = changeDateTimeFormat(yearMonth.atDay(1).toString()), - endedYmd = changeDateTimeFormat(yearMonth.atEndOfMonth().toString()) + startedYmd = yearMonth.atDay(1).format(ymdFormatter), + endedYmd = yearMonth.atEndOfMonth().format(ymdFormatter) )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/dsm/pick2024/infrastructure/feign/neis/NeisMealFeignClientService.kt` around lines 42 - 43, The current code calls changeDateTimeFormat on yearMonth.atDay(1).toString() and yearMonth.atEndOfMonth().toString(); instead, format the LocalDate directly using DateTimeFormatter.BASIC_ISO_DATE to avoid fragile toString parsing—replace the expressions used for startedYmd and endedYmd with yearMonth.atDay(1).format(DateTimeFormatter.BASIC_ISO_DATE) and yearMonth.atEndOfMonth().format(DateTimeFormatter.BASIC_ISO_DATE) (removing the changeDateTimeFormat call) so the values are produced reliably from the LocalDate instances.src/main/kotlin/dsm/pick2024/domain/meal/persistence/repository/MealRepository.kt (1)
9-9: 삭제 건수 반환으로 운영 가시성을 높여주세요.
deleteAllByMealDateBetween메서드를Long을 반환하도록 변경하면 실제 삭제된 레코드 수를 추적할 수 있습니다. Spring Data JPA의deleteAllBy*메서드는 관례상Long을 반환하며, 이를 통해 로그나 메트릭에 기록하면 장애 분석과 모니터링이 용이해집니다. 어댑터 레벨에서 반환값을 캡처하여 로깅하는 것도 고려해주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/dsm/pick2024/domain/meal/persistence/repository/MealRepository.kt` at line 9, 메서드 deleteAllByMealDateBetween의 반환형을 void에서 Long으로 변경하여 삭제된 레코드 수를 반환하도록 수정하세요: MealRepository 인터페이스의 fun deleteAllByMealDateBetween(start: LocalDate, end: LocalDate): Long으로 시그니처를 바꾸고, 이 값을 호출하는 어댑터/서비스 레이어에서 (예: MealRepository를 사용하는 클래스) 반환값을 받아 로깅/메트릭에 기록하도록 캡처 및 전파 처리하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.kt`:
- Around line 31-33: The code in RetryMealService that computes cal via
mealsOfType.map { it.cal }.get(0) is non-deterministic when multiple cal values
exist; replace this with an explicit rule: collect distinct cal values from
mealsOfType (e.g., val cals = mealsOfType.map { it.cal }.distinct()) and then
either enforce a single value by calling single() and throw a clear exception
(including mealType/date context) if multiple are present, or apply a
deterministic aggregation rule (e.g., min/max/average) and document that choice;
update any surrounding logic that assumes a single cal accordingly.
- Around line 22-24: The current logic in RetryMealService
(mealPort.deleteByYearMonth followed by
neisMealFeignClientService.getNeisInfoToEntityByYearMonth) can wipe a month's
data if NEIS returns an empty list; change the sequence to first call
neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth),
validate the result (non-null and non-empty) and only then call
mealPort.deleteByYearMonth(currentMonth) and persist the new data (save via the
existing save method), otherwise log/raise/skip without deleting; ensure this
runs inside the same transaction/context used by the service so the delete+save
remain atomic.
- Around line 18-24: The execute() method currently opens a `@Transactional`
context before calling
neisMealFeignClientService.getNeisInfoToEntityByYearMonth, which performs
external I/O; move the FEIGN call outside the transaction and only wrap the
database operations in a transaction. Concretely: call
YearMonth.from(TimeUtils.nowLocalDate()) and invoke
neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth) first
(no `@Transactional`), then start a new transaction around
mealPort.deleteByYearMonth(currentMonth) and mealPort.save/saveAll (or whatever
persistence methods you use) so that only the DB write section runs in the
transactional scope; adjust or create a private `@Transactional` method (e.g.,
saveMealsTransactional) that accepts the fetched mealForSave and performs
mealPort.deleteByYearMonth and the save calls.
---
Nitpick comments:
In
`@src/main/kotlin/dsm/pick2024/domain/meal/persistence/repository/MealRepository.kt`:
- Line 9: 메서드 deleteAllByMealDateBetween의 반환형을 void에서 Long으로 변경하여 삭제된 레코드 수를
반환하도록 수정하세요: MealRepository 인터페이스의 fun deleteAllByMealDateBetween(start:
LocalDate, end: LocalDate): Long으로 시그니처를 바꾸고, 이 값을 호출하는 어댑터/서비스 레이어에서 (예:
MealRepository를 사용하는 클래스) 반환값을 받아 로깅/메트릭에 기록하도록 캡처 및 전파 처리하세요.
In
`@src/main/kotlin/dsm/pick2024/infrastructure/feign/neis/NeisMealFeignClientService.kt`:
- Around line 42-43: The current code calls changeDateTimeFormat on
yearMonth.atDay(1).toString() and yearMonth.atEndOfMonth().toString(); instead,
format the LocalDate directly using DateTimeFormatter.BASIC_ISO_DATE to avoid
fragile toString parsing—replace the expressions used for startedYmd and
endedYmd with yearMonth.atDay(1).format(DateTimeFormatter.BASIC_ISO_DATE) and
yearMonth.atEndOfMonth().format(DateTimeFormatter.BASIC_ISO_DATE) (removing the
changeDateTimeFormat call) so the values are produced reliably from the
LocalDate instances.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a92ffc6b-acfb-4719-b51d-a2516bb0faca
📒 Files selected for processing (9)
src/main/kotlin/dsm/pick2024/domain/admin/presentation/AdminController.ktsrc/main/kotlin/dsm/pick2024/domain/meal/persistence/MealPersistenceAdapter.ktsrc/main/kotlin/dsm/pick2024/domain/meal/persistence/repository/MealRepository.ktsrc/main/kotlin/dsm/pick2024/domain/meal/port/in/RetryMealUseCase.ktsrc/main/kotlin/dsm/pick2024/domain/meal/port/out/DeleteMealPort.ktsrc/main/kotlin/dsm/pick2024/domain/meal/port/out/MealPort.ktsrc/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.ktsrc/main/kotlin/dsm/pick2024/global/security/path/SecurityPaths.ktsrc/main/kotlin/dsm/pick2024/infrastructure/feign/neis/NeisMealFeignClientService.kt
| mealPort.deleteByYearMonth(currentMonth) | ||
|
|
||
| val mealForSave = neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth) |
There was a problem hiding this comment.
빈 응답 시 월 데이터가 통째로 유실될 수 있습니다.
현재 순서(삭제 → 외부 조회)는 NEIS가 일시적으로 빈 리스트를 반환해도 커밋 시 해당 월 데이터가 비는 결과를 만들 수 있습니다. 조회 결과를 먼저 검증한 뒤 삭제/저장을 수행하도록 순서를 바꾸는 게 안전합니다.
안전한 처리 순서 제안
`@Transactional`
override fun execute() {
val currentMonth = YearMonth.from(TimeUtils.nowLocalDate())
-
- mealPort.deleteByYearMonth(currentMonth)
-
- val mealForSave = neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth)
+ val mealForSave = neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth)
+ require(mealForSave.isNotEmpty()) { "NEIS 응답이 비어 있어 급식 재동기화를 중단합니다." }
+
+ mealPort.deleteByYearMonth(currentMonth)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| mealPort.deleteByYearMonth(currentMonth) | |
| val mealForSave = neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth) | |
| val mealForSave = neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth) | |
| require(mealForSave.isNotEmpty()) { "NEIS 응답이 비어 있어 급식 재동기화를 중단합니다." } | |
| mealPort.deleteByYearMonth(currentMonth) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.kt` around
lines 22 - 24, The current logic in RetryMealService (mealPort.deleteByYearMonth
followed by neisMealFeignClientService.getNeisInfoToEntityByYearMonth) can wipe
a month's data if NEIS returns an empty list; change the sequence to first call
neisMealFeignClientService.getNeisInfoToEntityByYearMonth(currentMonth),
validate the result (non-null and non-empty) and only then call
mealPort.deleteByYearMonth(currentMonth) and persist the new data (save via the
existing save method), otherwise log/raise/skip without deleting; ensure this
runs inside the same transaction/context used by the service so the delete+save
remain atomic.
There was a problem hiding this comment.
이 부분은 오히려 도메인의 이해가 필요한 부분인 것 같아.
예를 들어 급식이 있는 날이었는데, 휴교 같은 이유로 인해서 급식이 안 나오는 날로 바뀔 수 도 있어.
그런데 빈 리스트가 왔다고 해서 변경하지 않는 건 오히려 잘못된 행동일 수 있는거지
There was a problem hiding this comment.
@1210thatman 좋은 도메인 설명 감사합니다. 말씀하신 것처럼 휴교·급식 미제공 등으로 인해 실제로 빈 리스트가 NEIS의 정상적인 응답일 수 있으므로, 빈 응답을 이유로 삭제를 막으면 오히려 DB에 오래된 데이터가 남아 잘못된 정보를 제공하게 되는군요. 현재의 "삭제 → 조회 → 저장" 순서가 도메인 의도에 맞는 올바른 구현임을 확인했습니다. 제 제안을 철회합니다.
📝 Learnings were identified and not saved (knowledge base disabled). Enable
| mealMap.forEach { (mealType, mealsOfType) -> | ||
| val menu = mealsOfType.map { it.menu }.filterNotNull().distinct().joinToString("||") | ||
| val cal = mealsOfType.map { it.cal }.get(0) |
There was a problem hiding this comment.
칼로리(cal) 선택 규칙이 비결정적입니다.
동일 날짜/식사타입 그룹에 cal 값이 여러 개면 첫 값만 저장되어 데이터 불일치가 숨겨질 수 있습니다. 단일 값 검증(불일치 시 예외) 또는 명시적 선택 규칙을 두는 것을 권장합니다.
검증 로직 예시
- val cal = mealsOfType.map { it.cal }.get(0)
+ val calCandidates = mealsOfType.mapNotNull { it.cal }.distinct()
+ require(calCandidates.size <= 1) { "동일 mealDate/mealType에 cal 값이 여러 개입니다." }
+ val cal = calCandidates.firstOrNull()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| mealMap.forEach { (mealType, mealsOfType) -> | |
| val menu = mealsOfType.map { it.menu }.filterNotNull().distinct().joinToString("||") | |
| val cal = mealsOfType.map { it.cal }.get(0) | |
| mealMap.forEach { (mealType, mealsOfType) -> | |
| val menu = mealsOfType.map { it.menu }.filterNotNull().distinct().joinToString("||") | |
| val calCandidates = mealsOfType.mapNotNull { it.cal }.distinct() | |
| require(calCandidates.size <= 1) { "동일 mealDate/mealType에 cal 값이 여러 개입니다." } | |
| val cal = calCandidates.firstOrNull() |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.kt` around
lines 31 - 33, The code in RetryMealService that computes cal via
mealsOfType.map { it.cal }.get(0) is non-deterministic when multiple cal values
exist; replace this with an explicit rule: collect distinct cal values from
mealsOfType (e.g., val cals = mealsOfType.map { it.cal }.distinct()) and then
either enforce a single value by calling single() and throw a clear exception
(including mealType/date context) if multiple are present, or apply a
deterministic aggregation rule (e.g., min/max/average) and document that choice;
update any surrounding logic that assumes a single cal accordingly.
There was a problem hiding this comment.
이건 그냥 넘어가자 이 pr과는 관련 없는 내용이야
There was a problem hiding this comment.
@1210thatman 알겠습니다, 이 PR의 범위와 직접적인 관련이 없으니 넘어가겠습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.kt`:
- Around line 33-34: The multiline if/else in RetryMealService (the branch that
returns Meal(mealDate = date, mealType = mealType, menu = menu, cal = cal) else
null when menu.isNotEmpty()) lacks curly braces and causes ktlint to fail;
update that conditional to use explicit braces for both the if and the else so
the block is clearly multiline (i.e., wrap the true branch in { ... } and the
else branch in { ... }) within the function in RetryMealService.
- Around line 39-41: The refresh block inside RetryMealService.execute() is not
serialized and can interleave deleteByYearMonth/current-month reinsertions
causing duplicates or rollbacks; to fix, introduce a month-scoped lock around
the transactionTemplate.execute { mealPort.deleteByYearMonth(currentMonth);
mealsToSave.forEach { mealPort.save(it) } } (e.g., synchronized per-month key, a
distributed lock like `@SchedulerLock/ShedLock`, or a named ReentrantLock map
keyed by currentMonth) and/or enforce DB-level integrity by adding a unique
constraint on MealJpaEntity for (mealDate, mealType) and corresponding
repository handling in MealRepository to prevent duplicates on concurrent saves.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5405855b-7749-4780-bd9c-3f0c6dcb0b94
📒 Files selected for processing (2)
src/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.ktsrc/main/kotlin/dsm/pick2024/domain/meal/service/SaveMealService.kt
✅ Files skipped from review due to trivial changes (1)
- src/main/kotlin/dsm/pick2024/domain/meal/service/SaveMealService.kt
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/kotlin/dsm/pick2024/domain/meal/entity/MealJpaEntity.kt`:
- Around line 14-18: The composite unique constraint (mealDate, mealType) is
ineffective because mealType is still nullable; update the V4 migration to alter
tbl_meal making mealType NOT NULL (e.g. ALTER TABLE ... MODIFY COLUMN `mealType`
TEXT NOT NULL), change the MealJpaEntity mapping for the mealType field to
nullable = false in its `@Column/`@Table mapping, and modify the getMealType()
method in MealJpaEntity to remove null returns by either throwing a clear
exception or returning/deriving a valid MealType default so the entity never
persists null mealType values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 04efe193-f098-4533-bf2b-d99e226fb003
📒 Files selected for processing (3)
src/main/kotlin/dsm/pick2024/domain/meal/entity/MealJpaEntity.ktsrc/main/kotlin/dsm/pick2024/domain/meal/service/RetryMealService.ktsrc/main/resources/db/migration/V4_add_unique_constraint_to_meal_date_and_type.sql
✅ Files skipped from review due to trivial changes (1)
- src/main/resources/db/migration/V4_add_unique_constraint_to_meal_date_and_type.sql
| @Entity | ||
| @Table( | ||
| name = "tbl_meal", | ||
| uniqueConstraints = [UniqueConstraint(columnNames = ["mealDate", "mealType"])] | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Entity nullability check =="
rg -n -C2 'mealType|nullable = true|nullable = false' src/main/kotlin/dsm/pick2024/domain/meal/entity/MealJpaEntity.kt
echo
echo "== Migration nullability / column definition check =="
rg -n -C3 'tbl_meal|mealType|meal_type|NOT NULL|NULL|UNIQUE|uk_meal_date_type' src/main/resources/db/migration
echo
echo "== Upstream mapping where mealType can become nullable =="
rg -n -C3 'mealType' src/main/kotlin/dsm/pick2024/infrastructure/feign/neis src/main/kotlin/dsm/pick2024/domain/meal/serviceRepository: DSM-PICK/PiCK_CORE_SERVER
Length of output: 12909
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== getMealType() function definition =="
rg -n -A10 'fun getMealType' src/main/kotlin/dsm/pick2024/infrastructure/feign/neis
echo
echo "== MealType enum definition =="
rg -n -B2 -A8 'enum class MealType|object MealType' src/main/kotlin/dsm/pick2024/domain/meal
echo
echo "== Check if migration altered column definition =="
cat src/main/resources/db/migration/V4_add_unique_constraint_to_meal_date_and_type.sqlRepository: DSM-PICK/PiCK_CORE_SERVER
Length of output: 1496
(mealDate, mealType) 유니크 제약이 mealType = NULL 케이스를 완전히 방지하지 못합니다.
Line 17의 복합 유니크 제약이 추가되었으나, Line 26-27에서 mealType이 nullable = true인 상태로 남아있습니다. SQL의 UNIQUE 제약은 NULL 값을 서로 비교하지 않으므로 (mealDate, NULL) 조합이 여러 번 삽입될 수 있어, 의도된 무결성 보장이 실패합니다. 마이그레이션 V4에서 제약만 추가하고 컬럼을 NOT NULL로 변경하지 않아 불완전합니다.
getMealType() (라인 129)이 알 수 없는 식사 코드에 대해 null을 반환할 수 있으므로, 컬럼을 NOT NULL로 수정하려면 동시에 getMealType() 오류 처리를 보완해야 합니다.
🔧 필요한 수정
- 마이그레이션에 NOT NULL 추가:
ALTER TABLE `tbl_meal` MODIFY COLUMN `mealType` TEXT NOT NULL;- 엔티티 업데이트:
`@Enumerated`(value = EnumType.STRING)
-@Column(nullable = true, columnDefinition = "TEXT")
-val mealType: MealType?,
+@Column(nullable = false, columnDefinition = "TEXT")
+val mealType: MealType,getMealType()예외 처리 추가 필요 (null 반환 제거)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/kotlin/dsm/pick2024/domain/meal/entity/MealJpaEntity.kt` around
lines 14 - 18, The composite unique constraint (mealDate, mealType) is
ineffective because mealType is still nullable; update the V4 migration to alter
tbl_meal making mealType NOT NULL (e.g. ALTER TABLE ... MODIFY COLUMN `mealType`
TEXT NOT NULL), change the MealJpaEntity mapping for the mealType field to
nullable = false in its `@Column/`@Table mapping, and modify the getMealType()
method in MealJpaEntity to remove null returns by either throwing a clear
exception or returning/deriving a valid MealType default so the entity never
persists null mealType values.
There was a problem hiding this comment.
NEIS API 스펙상, 1,2,3 이외의 값이 올 확률이 없다고 판단해서 저렇게 로직 작성했는데, 어떻게 생각해?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -A15 'fun getMealType' src/main/kotlin/dsm/pick2024/infrastructure/feign/neis
Length of output: 823
@1210thatman 맞는 말이에요 — NEIS API 스펙이 안정적이라면 실용적으로는 큰 문제가 없습니다. 다만 현재 구조에는 한 가지 아쉬운 점이 있어요.
현재 getMealType()의 else -> null 분기는 예상치 못한 값이 들어와도 조용히 null을 반환합니다. 만약 API 응답에서 예상 밖의 코드가 오더라도 아무런 에러 없이 mealType = null인 레코드가 저장될 수 있고, 그 경우 유니크 제약도 무력화됩니다.
// 현재
private fun getMealType(mealCode: String): MealType? = when (mealCode) {
"1" -> MealType.BREAKFAST
"2" -> MealType.LUNCH
"3" -> MealType.DINNER
else -> null // 조용히 null 반환 → 추적 불가
}"스펙상 1,2,3만 온다"는 확신이 있다면, 오히려 그 가정을 코드에 명시하는 게 더 좋습니다:
// 권장
private fun getMealType(mealCode: String): MealType = when (mealCode) {
"1" -> MealType.BREAKFAST
"2" -> MealType.LUNCH
"3" -> MealType.DINNER
else -> throw IllegalArgumentException("알 수 없는 NEIS 식사 코드: $mealCode")
}이렇게 하면:
- 반환 타입이
MealType(non-null) 이 되어,MealJpaEntity의mealType도 자연스럽게nullable = false로 변경할 수 있고, - 유니크 제약
(mealDate, mealType)이 의도대로 완전히 동작하며, - 혹시 API 스펙이 변경되거나 예외 케이스가 생겼을 때 즉시 인지할 수 있습니다.
결론적으로, 현재 로직이 틀렸다기보다는 가정이 코드에 드러나지 않아 나중에 추적하기 어렵다는 게 핵심입니다. 어떻게 생각하세요?
급식을 수동으로 refeash하는 API를 추가했습니다.
Summary by CodeRabbit