[Feat] Application 도메인 구현 - #27
Conversation
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughApplication 도메인이 추가되었습니다. 엔티티와 상태 전이, 지원 처리 서비스 및 REST API를 구현하고, Job의 수락 인원 관리와 낙관적 락 충돌 응답을 추가했습니다. ChangesApplication 도메인 구현
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Worker
participant ApplicationController
participant ApplicationService
participant ApplicationRepository
participant Job
Worker->>ApplicationController: POST /api/applications/jobs/{jobId}
ApplicationController->>ApplicationService: apply(workerId, jobId)
ApplicationService->>ApplicationRepository: 중복 지원 확인
ApplicationService->>Job: 공고 상태 및 모집 인원 확인
ApplicationService->>ApplicationRepository: PENDING 지원 저장
ApplicationService-->>ApplicationController: ApplicationResponse
ApplicationController-->>Worker: ApiResponse<ApplicationResponse>
participant Employer
Employer->>ApplicationController: PATCH /{applicationId}/accept
ApplicationController->>ApplicationService: accept(employerId, applicationId)
ApplicationService->>ApplicationRepository: 지원 조회
ApplicationService->>Job: acceptedCount 증가
ApplicationService-->>ApplicationController: ApplicationResponse
ApplicationController-->>Employer: ApiResponse<ApplicationResponse>
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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: 2
🧹 Nitpick comments (9)
src/main/java/com/ilson/spotwork/domain/application/repository/ApplicationRepository.java (2)
16-20: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win목록 조회 시 N+1 쿼리 발생 가능성.
findByWorkerIdOrderByCreatedAtDesc와findByJobId는job,worker를 fetch join하지 않습니다.ApplicationResponse.from()이 각 항목에서application.getJob().getTitle(),application.getWorker().getNickname()을 호출하므로(LAZY 연관관계), 목록 항목 수만큼 추가 쿼리가 발생합니다.@Query에JOIN FETCH job,JOIN FETCH worker를 추가하는 것을 권장합니다.♻️ 제안
+ `@Query`("SELECT a FROM Application a JOIN FETCH a.job JOIN FETCH a.worker WHERE a.worker.id = :workerId ORDER BY a.createdAt DESC") + Slice<Application> findByWorkerIdOrderByCreatedAtDesc(`@Param`("workerId") Long workerId, Pageable pageable);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/repository/ApplicationRepository.java` around lines 16 - 20, The list queries in ApplicationRepository are vulnerable to N+1 because ApplicationResponse.from() reads application.getJob().getTitle() and application.getWorker().getNickname() on lazy relations. Update findByWorkerIdOrderByCreatedAtDesc and findByJobId to fetch the needed associations eagerly, preferably by adding `@Query` with JOIN FETCH for job and worker so ApplicationResponse mapping does not trigger per-row extra selects.
22-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value주석 처리된 미사용 코드 제거.
Line 23의 커밋된
countByJobIdAndStatus메서드는 실제로 사용되지 않습니다. 필요 없다면 제거하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/repository/ApplicationRepository.java` around lines 22 - 24, The ApplicationRepository interface contains a commented-out unused method declaration that should be removed rather than left as dead code. Delete the commented countByJobIdAndStatus repository method and keep ApplicationRepository limited to actually used repository contracts, checking for any references before removing it.src/main/java/com/ilson/spotwork/common/exception/ErrorCode.java (1)
34-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
INVALID_STATUS_TRANSITION과INVALID_APPLICATION_STATUS가 중복된 개념입니다.동일한 "잘못된 상태 전이" 상황에 대해 두 개의 유사한 에러코드가 존재합니다(Line 34, 36).
Application.transitionTo()는INVALID_APPLICATION_STATUS를,ApplicationService.cancel()의 수동 상태 체크는INVALID_STATUS_TRANSITION을 사용하는 것으로 보여, 클라이언트가 동일한 유형의 오류에 대해 서로 다른 코드를 받게 됩니다. 하나로 통합하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/common/exception/ErrorCode.java` around lines 34 - 36, `ErrorCode` contains two overlapping “invalid status transition” codes, so unify `INVALID_STATUS_TRANSITION` and `INVALID_APPLICATION_STATUS` into a single shared error code. Update the callers in `Application.transitionTo()` and `ApplicationService.cancel()` to use the same `ErrorCode` symbol, and remove the duplicate enum entry so clients receive one consistent code for the same failure case.src/main/java/com/ilson/spotwork/domain/job/entity/Job.java (2)
64-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
@Builder사용 시 필드 초기값이 무시되는 Lombok 특성 주의
@Builder가 적용된 클래스에서private int acceptedCount = 0;처럼 필드 초기화 값을 지정해도,@Builder.Default가 없으면 빌더로 객체를 생성할 때 해당 초기값은 무시되고 자바 기본값(int의 경우 0)이 사용됩니다. 현재는 초기값과 기본값이 동일(0)해서 문제가 드러나지 않지만, 추후 초기값이 바뀌면 조용히 깨지는 함정이 될 수 있습니다.♻️ 제안 diff
+ `@Builder.Default` `@Column`(nullable = false) private int acceptedCount = 0;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/job/entity/Job.java` around lines 64 - 65, `Job` 클래스의 `acceptedCount`는 `@Builder`로 생성할 때 필드 초기화가 무시될 수 있으므로, 현재의 `private int acceptedCount = 0;`를 `@Builder.Default`와 함께 사용하도록 수정하세요. `Job`의 빌더 생성 경로에서 기본값이 항상 유지되도록 `acceptedCount` 선언을 명확히 하고, 나중에 초기값이 변경되어도 빌더가 이를 반영하도록 맞춰주세요.
61-73: 🗄️ Data Integrity & Integration | 🔵 Trivial신규 컬럼 추가에 따른 DB 마이그레이션 영향 검토 필요
jobs테이블에 이미 데이터가 존재하는 상태에서@Version(NOT NULL 암묵적 요구)과acceptedCount(nullable = false)를 추가하면, 기존 로우들의 값이 NULL로 남아있을 경우 첫 업데이트 시 버전 비교(WHERE version = ?)가 실패하거나 NOT NULL 제약을 위반할 수 있습니다. Flyway/Liquibase 등 마이그레이션 스크립트에서 기존 로우에 대한 backfill(version=0,acceptedCount=0) 처리가 되어 있는지 확인이 필요합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/job/entity/Job.java` around lines 61 - 73, Existing rows in jobs may break after adding the `@Version` field and the non-null acceptedCount column in Job. Update the DB migration (Flyway/Liquibase) to backfill all existing jobs with version = 0 and acceptedCount = 0 before enforcing NOT NULL/optimistic locking, and ensure the Job entity’s acceptedCount/default behavior stays aligned with the migration.src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java (4)
82-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win모집인원 초과 검증 로직 중복 (DRY 위반)
job.getAcceptedCount() >= application.getJob().getHeadcount()체크는Job.increaseAcceptedCount()(src/main/java/com/ilson/spotwork/domain/job/entity/Job.java 라인 68) 내부에 이미 동일한 검증이 존재합니다. 여기서 별도로 재검증할 필요 없이job.increaseAcceptedCount()를 먼저 호출해 예외를 위임받고, 성공 시에만application.transitionTo(ACCEPTED)를 호출하는 순서로 단순화할 수 있습니다.♻️ 제안 diff
- if (job.getAcceptedCount() >= application.getJob().getHeadcount()) { - throw new CustomException(ErrorCode.JOB_CLOSED); - } - application.transitionTo(ApplicationStatus.ACCEPTED); - job.increaseAcceptedCount(); + job.increaseAcceptedCount(); + application.transitionTo(ApplicationStatus.ACCEPTED);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java` around lines 82 - 86, In ApplicationService.accept flow, the 모집인원 초과 check is duplicated because Job.increaseAcceptedCount already enforces the same limit. Remove the explicit `job.getAcceptedCount() >= application.getJob().getHeadcount()` guard in the ApplicationService logic, call `job.increaseAcceptedCount()` first so it can throw `CustomException(ErrorCode.JOB_CLOSED)` itself, and only after that succeeds call `application.transitionTo(ApplicationStatus.ACCEPTED)`; use `ApplicationService` and `Job.increaseAcceptedCount` as the key locations when updating the order.
132-150: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win목록 조회 시 N+1 쿼리 발생 가능성
getMyApplications/getApplicants가 호출하는ApplicationRepository.findByWorkerIdOrderByCreatedAtDesc,findByJobId(관련 스니펫 기준 단순 파생 쿼리, fetch join 없음)로 조회한Application목록을ApplicationResponse.from으로 매핑할 때, 각 엔티티의 지연 로딩된job/worker연관관계에 접근하면서 목록 크기만큼 추가 쿼리가 발생할 수 있습니다. 지원자/지원 목록이 많아질수록 성능에 영향을 줄 수 있으니, 해당 리포지토리 메서드에JOIN FETCH를 적용하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java` around lines 132 - 150, The application list queries in ApplicationService#getMyApplications and ApplicationService#getApplicants can trigger N+1 when ApplicationResponse.from touches lazy Application associations. Update the repository methods used here, especially ApplicationRepository.findByWorkerIdOrderByCreatedAtDesc and findByJobId, to fetch the needed relations with JOIN FETCH (or an equivalent entity graph) so job/worker are loaded in the initial query. Make sure the returned Application entities already include the associations required by ApplicationResponse.from to avoid per-row extra queries.
58-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
apply()의 낙관적 락try-catch는 사실상 도달하지 않는 코드일 가능성이 메서드에서
Job은 조회만 하고 변경하지 않으며, 새로 생성되는Application은 이번 요청에서 처음 INSERT되는 엔티티입니다(버전 충돌이 발생할 대상이 없음,ApplicationStatus.java스니펫 기준Application엔티티에@Version이 추가되었다는 근거도 없음). 따라서ObjectOptimisticLockingFailureException을 잡는 이 catch 블록은 실제로 트리거될 가능성이 낮아 보입니다.accept()에서 복사돼 온 방어 코드라면 제거하거나, 실제로 의도한 시나리오(예: 중복 지원 방지)에 맞는 예외 처리로 교체하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java` around lines 58 - 73, `ApplicationService.apply()`의 `ObjectOptimisticLockingFailureException` catch는 현재 흐름에서 거의 발생하지 않는 방어 코드로 보입니다. `apply()`에서는 `Job`을 조회만 하고 새 `Application`을 생성해 `applicationRepository.save(...)`로 INSERT하므로 낙관적 락 충돌 대상이 없고, `Application`에 `@Version` 근거도 없습니다. 따라서 이 `try-catch`를 제거하거나, 정말 필요한 의도가 중복 지원 방지라면 `applicationRepository.save` 전에/후에 실제 중복을 감지하는 예외 처리로 바꾸고 `ApplicationService.apply`, `applicationRepository.save`, `ApplicationResponse.from` 기준으로 흐름을 정리하세요.
114-130: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift지원 취소를 물리 삭제(hard delete)로 처리 - 이력 유실 가능성
cancel()은applicationRepository.delete(application)으로 레코드를 완전히 삭제합니다.ApplicationStatusenum(관련 스니펫 기준)에는CANCELLED상태가 없어, 취소된 지원 이력이 통계/분쟁 대응 등에 필요할 경우 복구가 불가능합니다. 상태 전이 기반의 소프트 삭제(CANCELLED상태 추가 후transitionTo)를 고려해 볼 만합니다. 다만 이는 이미 별도 레이어에서 확정된ApplicationStatus/Application엔티티 설계에 영향을 주므로 팀 논의가 필요합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java` around lines 114 - 130, The cancel() flow in ApplicationService currently performs a hard delete via applicationRepository.delete(application), which removes the application history entirely. Update the logic to use a status-based cancellation path instead: add a CANCELLED state to ApplicationStatus if needed, and switch cancel() to transition the Application entity to that state through its existing status change method (for example, a transitionTo-style domain method) and persist the update. Keep the worker ownership and PENDING-only checks in place, and use the ApplicationService, ApplicationStatus, and ApplicationRepository symbols to locate the affected flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/ilson/spotwork/domain/application/entity/Application.java`:
- Around line 18-30: The Application entity currently has no optimistic locking,
so concurrent status updates can overwrite each other. Add a version field
annotated with `@Version` in Application, or move a shared version field into
BaseEntity if that is the intended common pattern, and ensure Application’s
status transitions use this versioned entity state. Use the Application class
and its BaseEntity inheritance point to place the new version tracking cleanly.
In
`@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java`:
- Around line 76-94: The optimistic-lock handling in ApplicationService.accept
is incomplete because the Job `@Version` conflict may only surface at flush/commit
time and escape the local try-catch. Update the flow around
getApplicationWithEmployerCheck, application.transitionTo, and
job.increaseAcceptedCount so the conflict is forced inside this method by using
an explicit flush/saveAndFlush pattern, or add a GlobalExceptionHandler for
ObjectOptimisticLockingFailureException. Make sure the caught conflict is
consistently mapped to ErrorCode.OPTIMISTIC_LOCK_CONFLICT instead of leaking as
a 500.
---
Nitpick comments:
In `@src/main/java/com/ilson/spotwork/common/exception/ErrorCode.java`:
- Around line 34-36: `ErrorCode` contains two overlapping “invalid status
transition” codes, so unify `INVALID_STATUS_TRANSITION` and
`INVALID_APPLICATION_STATUS` into a single shared error code. Update the callers
in `Application.transitionTo()` and `ApplicationService.cancel()` to use the
same `ErrorCode` symbol, and remove the duplicate enum entry so clients receive
one consistent code for the same failure case.
In
`@src/main/java/com/ilson/spotwork/domain/application/repository/ApplicationRepository.java`:
- Around line 16-20: The list queries in ApplicationRepository are vulnerable to
N+1 because ApplicationResponse.from() reads application.getJob().getTitle() and
application.getWorker().getNickname() on lazy relations. Update
findByWorkerIdOrderByCreatedAtDesc and findByJobId to fetch the needed
associations eagerly, preferably by adding `@Query` with JOIN FETCH for job and
worker so ApplicationResponse mapping does not trigger per-row extra selects.
- Around line 22-24: The ApplicationRepository interface contains a
commented-out unused method declaration that should be removed rather than left
as dead code. Delete the commented countByJobIdAndStatus repository method and
keep ApplicationRepository limited to actually used repository contracts,
checking for any references before removing it.
In
`@src/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.java`:
- Around line 82-86: In ApplicationService.accept flow, the 모집인원 초과 check is
duplicated because Job.increaseAcceptedCount already enforces the same limit.
Remove the explicit `job.getAcceptedCount() >=
application.getJob().getHeadcount()` guard in the ApplicationService logic, call
`job.increaseAcceptedCount()` first so it can throw
`CustomException(ErrorCode.JOB_CLOSED)` itself, and only after that succeeds
call `application.transitionTo(ApplicationStatus.ACCEPTED)`; use
`ApplicationService` and `Job.increaseAcceptedCount` as the key locations when
updating the order.
- Around line 132-150: The application list queries in
ApplicationService#getMyApplications and ApplicationService#getApplicants can
trigger N+1 when ApplicationResponse.from touches lazy Application associations.
Update the repository methods used here, especially
ApplicationRepository.findByWorkerIdOrderByCreatedAtDesc and findByJobId, to
fetch the needed relations with JOIN FETCH (or an equivalent entity graph) so
job/worker are loaded in the initial query. Make sure the returned Application
entities already include the associations required by ApplicationResponse.from
to avoid per-row extra queries.
- Around line 58-73: `ApplicationService.apply()`의
`ObjectOptimisticLockingFailureException` catch는 현재 흐름에서 거의 발생하지 않는 방어 코드로 보입니다.
`apply()`에서는 `Job`을 조회만 하고 새 `Application`을 생성해
`applicationRepository.save(...)`로 INSERT하므로 낙관적 락 충돌 대상이 없고, `Application`에
`@Version` 근거도 없습니다. 따라서 이 `try-catch`를 제거하거나, 정말 필요한 의도가 중복 지원 방지라면
`applicationRepository.save` 전에/후에 실제 중복을 감지하는 예외 처리로 바꾸고
`ApplicationService.apply`, `applicationRepository.save`,
`ApplicationResponse.from` 기준으로 흐름을 정리하세요.
- Around line 114-130: The cancel() flow in ApplicationService currently
performs a hard delete via applicationRepository.delete(application), which
removes the application history entirely. Update the logic to use a status-based
cancellation path instead: add a CANCELLED state to ApplicationStatus if needed,
and switch cancel() to transition the Application entity to that state through
its existing status change method (for example, a transitionTo-style domain
method) and persist the update. Keep the worker ownership and PENDING-only
checks in place, and use the ApplicationService, ApplicationStatus, and
ApplicationRepository symbols to locate the affected flow.
In `@src/main/java/com/ilson/spotwork/domain/job/entity/Job.java`:
- Around line 64-65: `Job` 클래스의 `acceptedCount`는 `@Builder`로 생성할 때 필드 초기화가 무시될 수
있으므로, 현재의 `private int acceptedCount = 0;`를 `@Builder.Default`와 함께 사용하도록 수정하세요.
`Job`의 빌더 생성 경로에서 기본값이 항상 유지되도록 `acceptedCount` 선언을 명확히 하고, 나중에 초기값이 변경되어도 빌더가
이를 반영하도록 맞춰주세요.
- Around line 61-73: Existing rows in jobs may break after adding the `@Version`
field and the non-null acceptedCount column in Job. Update the DB migration
(Flyway/Liquibase) to backfill all existing jobs with version = 0 and
acceptedCount = 0 before enforcing NOT NULL/optimistic locking, and ensure the
Job entity’s acceptedCount/default behavior stays aligned with the migration.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ba4c1335-8528-4403-b427-2da5182c1fc3
📒 Files selected for processing (8)
src/main/java/com/ilson/spotwork/common/exception/ErrorCode.javasrc/main/java/com/ilson/spotwork/domain/application/controller/ApplicationController.javasrc/main/java/com/ilson/spotwork/domain/application/dto/ApplicationResponse.javasrc/main/java/com/ilson/spotwork/domain/application/entity/Application.javasrc/main/java/com/ilson/spotwork/domain/application/entity/ApplicationStatus.javasrc/main/java/com/ilson/spotwork/domain/application/repository/ApplicationRepository.javasrc/main/java/com/ilson/spotwork/domain/application/service/ApplicationService.javasrc/main/java/com/ilson/spotwork/domain/job/entity/Job.java
관련 이슈
closes #23
구현 내용
변경 사항
체크리스트
Summary by CodeRabbit