Skip to content

Commit 740fbbc

Browse files
Merge pull request #7 from EntryDSM/feature/11-layered-to-hexagonal-status
feature/11-layered-to-hexagonal-status
2 parents fdc893d + bba3b85 commit 740fbbc

35 files changed

Lines changed: 1088 additions & 0 deletions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.`in`.web
2+
3+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.AnnounceResultUseCase
4+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.CancelApplicationSubmitUseCase
5+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.StartScreeningUseCase
6+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.UpdateIsPrintsArrivedUseCase
7+
import org.springframework.web.bind.annotation.PatchMapping
8+
import org.springframework.web.bind.annotation.PathVariable
9+
import org.springframework.web.bind.annotation.RequestMapping
10+
import org.springframework.web.bind.annotation.RestController
11+
12+
/**
13+
* 관리자용 상태 관리 REST API 컨트롤러입니다.
14+
* 관리자가 지원자의 상태를 직접 변경할 수 있는 기능을 제공합니다.
15+
*
16+
* @property updateIsPrintsArrivedUseCase 서류 도착 상태 변경 유스케이스
17+
* @property cancelApplicationSubmitUseCase 지원서 제출 취소 유스케이스
18+
* @property startScreeningUseCase 전형 시작 유스케이스
19+
* @property announceResultUseCase 합격 발표 유스케이스
20+
*/
21+
@RestController
22+
@RequestMapping("/admin/status")
23+
class AdminWebController(
24+
private val updateIsPrintsArrivedUseCase: UpdateIsPrintsArrivedUseCase,
25+
private val cancelApplicationSubmitUseCase: CancelApplicationSubmitUseCase,
26+
private val startScreeningUseCase: StartScreeningUseCase,
27+
private val announceResultUseCase: AnnounceResultUseCase
28+
) {
29+
30+
/**
31+
* 지원서 제출을 취소합니다.
32+
* 제출된 지원서를 작성 중 상태로 되돌립니다.
33+
*
34+
* @param receiptCode 접수번호
35+
*/
36+
@PatchMapping("/submitted/{receipt-code}")
37+
fun cancelApplicationSubmit(@PathVariable("receipt-code") receiptCode: Long) {
38+
cancelApplicationSubmitUseCase.execute(receiptCode)
39+
}
40+
41+
/**
42+
* 서류 도착을 확인합니다.
43+
* 등기우편으로 제출된 서류의 도착을 관리자가 확인하여 상태를 변경합니다.
44+
*
45+
* @param receiptCode 접수번호
46+
*/
47+
@PatchMapping("/prints-arrived/{receipt-code}")
48+
fun updateIsPrintsArrivedService(@PathVariable("receipt-code") receiptCode: Long) {
49+
updateIsPrintsArrivedUseCase.execute(receiptCode)
50+
}
51+
52+
/**
53+
* 전형을 시작합니다.
54+
* 서류 검토가 완료된 후 1차 또는 2차 전형을 시작합니다.
55+
*
56+
* @param receiptCode 접수번호
57+
*/
58+
@PatchMapping("/screening/{receipt-code}")
59+
fun startScreening(@PathVariable("receipt-code") receiptCode: Long) {
60+
startScreeningUseCase.execute(receiptCode)
61+
}
62+
63+
/**
64+
* 합격 결과를 발표합니다.
65+
* 최종 전형 결과를 발표하고 상태를 변경합니다.
66+
*
67+
* @param receiptCode 접수번호
68+
*/
69+
@PatchMapping("/announce/{receipt-code}")
70+
fun announceResult(@PathVariable("receipt-code") receiptCode: Long) {
71+
announceResultUseCase.execute(receiptCode)
72+
}
73+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.`in`.web
2+
3+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.GetAllStatusUseCase
4+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.GetStatusByReceiptCodeUseCase
5+
import hs.kr.entrydsm.status.infrastructure.grpc.server.dto.response.InternalStatusResponse
6+
import hs.kr.entrydsm.status.domain.status.application.port.`in`.UpdateExamCodeUseCase
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PathVariable
9+
import org.springframework.web.bind.annotation.PutMapping
10+
import org.springframework.web.bind.annotation.RequestMapping
11+
import org.springframework.web.bind.annotation.RequestParam
12+
import org.springframework.web.bind.annotation.RestController
13+
14+
/**
15+
* 내부 시스템용 상태 조회 REST API 컨트롤러입니다.
16+
* 다른 마이크로서비스에서 지원자의 상태 정보를 조회할 수 있는 기능을 제공합니다.
17+
*
18+
* @property getStatusByReceiptCodeUseCase 접수번호별 상태 조회 유스케이스
19+
* @property getAllStatusUseCase 전체 상태 조회 유스케이스
20+
* @property updateExamCodeUseCase 수험번호 업데이트 유스케이스
21+
*/
22+
@RestController
23+
@RequestMapping("/internal/status")
24+
class InternalStatusWebController(
25+
private val getStatusByReceiptCodeUseCase: GetStatusByReceiptCodeUseCase,
26+
private val getAllStatusUseCase: GetAllStatusUseCase,
27+
private val updateExamCodeUseCase: UpdateExamCodeUseCase
28+
) {
29+
30+
/**
31+
* 접수번호로 상태를 조회합니다.
32+
*
33+
* @param receiptCode 접수번호
34+
* @return 지원자의 상태 정보
35+
*/
36+
@GetMapping("/{receipt-code}")
37+
fun getStatusByReceiptCode(@PathVariable("receipt-code") receiptCode: Long): InternalStatusResponse {
38+
return getStatusByReceiptCodeUseCase.execute(receiptCode)
39+
}
40+
41+
/**
42+
* 모든 지원자의 상태 목록을 조회합니다.
43+
*
44+
* @return 전체 지원자 상태 정보 목록
45+
*/
46+
@GetMapping("/list")
47+
fun getAllStatus(): List<InternalStatusResponse> {
48+
return getAllStatusUseCase.execute()
49+
}
50+
51+
/**
52+
* 수험번호를 업데이트합니다.
53+
*
54+
* @param receiptCode 접수번호
55+
* @param examCode 새로운 수험번호
56+
*/
57+
@PutMapping("/{receipt-code}")
58+
fun updateExamCode(@PathVariable("receipt-code") receiptCode: Long, @RequestParam examCode: String) {
59+
updateExamCodeUseCase.execute(receiptCode, examCode)
60+
}
61+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.domain
2+
3+
import hs.kr.entrydsm.status.domain.status.model.ApplicationStatus
4+
import hs.kr.entrydsm.status.domain.status.model.Status
5+
import org.springframework.data.annotation.Id
6+
import org.springframework.data.redis.core.RedisHash
7+
import org.springframework.data.redis.core.TimeToLive
8+
9+
/**
10+
* Redis 캐시용 상태 엔티티 클래스입니다.
11+
* 상태 조회 성능 향상을 위한 캐시 데이터를 관리합니다.
12+
*
13+
* @property receiptCode 접수번호 (Redis 키로 사용)
14+
* @property applicationStatus 지원 상태
15+
* @property examCode 수험번호
16+
* @property isFirstRoundPass 1차 전형 합격 여부
17+
* @property isSecondRoundPass 2차 전형 합격 여부
18+
* @property ttl Time To Live (캐시 만료 시간, 초 단위)
19+
*/
20+
@RedisHash(value = "status_cache")
21+
class StatusCache(
22+
@Id
23+
val receiptCode: Long,
24+
25+
val applicationStatus: ApplicationStatus,
26+
27+
val examCode: String?,
28+
29+
val isFirstRoundPass: Boolean,
30+
31+
val isSecondRoundPass: Boolean,
32+
33+
@TimeToLive
34+
var ttl: Long
35+
){
36+
companion object{
37+
/**
38+
* Status 도메인 모델로부터 StatusCache 인스턴스를 생성합니다.
39+
*
40+
* @param status 도메인 모델 Status 인스턴스
41+
* @return StatusCache 인스턴스 (TTL 10분으로 설정)
42+
*/
43+
fun from(status: Status): StatusCache {
44+
return StatusCache(
45+
receiptCode = status.receiptCode,
46+
applicationStatus = status.applicationStatus,
47+
examCode = status.examCode,
48+
isFirstRoundPass = status.isFirstRoundPass,
49+
isSecondRoundPass = status.isSecondRoundPass,
50+
ttl = 60 * 10
51+
)
52+
}
53+
}
54+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.domain
2+
3+
import hs.kr.entrydsm.status.domain.status.model.ApplicationStatus
4+
import jakarta.persistence.Column
5+
import jakarta.persistence.Entity
6+
import jakarta.persistence.EnumType
7+
import jakarta.persistence.Enumerated
8+
import jakarta.persistence.GeneratedValue
9+
import jakarta.persistence.GenerationType
10+
import jakarta.persistence.Id
11+
12+
/**
13+
* 지원 상태 정보를 저장하는 JPA 엔티티 클래스입니다.
14+
* 데이터베이스의 status 테이블과 매핑되며, 지원자의 전형 진행 상태를 관리합니다.
15+
*
16+
* @property applicationStatus 현재 지원 상태
17+
* @property examCode 수험번호 (고유값)
18+
* @property isFirstRoundPass 1차 전형 합격 여부
19+
* @property isSecondRoundPass 2차 전형 합격 여부
20+
* @property receiptCode 접수번호
21+
* @property id 데이터베이스 기본키 (자동 생성)
22+
*/
23+
@Entity(name = "status")
24+
class StatusJpaEntity(
25+
@Enumerated(EnumType.STRING)
26+
@Column(name = "application_status")
27+
var applicationStatus: ApplicationStatus = ApplicationStatus.NOT_APPLIED,
28+
@Column(unique = true)
29+
var examCode: String? = null,
30+
var isFirstRoundPass: Boolean = false,
31+
var isSecondRoundPass: Boolean = false,
32+
val receiptCode: Long
33+
) {
34+
@Id
35+
@GeneratedValue(strategy = GenerationType.IDENTITY)
36+
val id: Long = 0
37+
38+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.mapper
2+
3+
import hs.kr.entrydsm.status.domain.status.adapter.out.domain.StatusJpaEntity
4+
import hs.kr.entrydsm.status.domain.status.model.Status
5+
import hs.kr.entrydsm.status.global.mapper.GenericMapper
6+
import org.mapstruct.Mapper
7+
8+
/**
9+
* Status 도메인 모델과 StatusJpaEntity 간의 변환을 담당하는 매퍼 클래스입니다.
10+
* MapStruct를 사용하여 도메인 계층과 인프라스트럭처 계층 간의 데이터 변환을 처리합니다.
11+
*/
12+
@Mapper(componentModel = "spring")
13+
abstract class StatusMapper: GenericMapper<StatusJpaEntity, Status> {
14+
15+
/**
16+
* Status 도메인 모델을 StatusJpaEntity로 변환합니다.
17+
*
18+
* @param model 변환할 Status 도메인 모델
19+
* @return 변환된 StatusJpaEntity
20+
*/
21+
abstract override fun toEntity(model: Status): StatusJpaEntity
22+
23+
/**
24+
* StatusJpaEntity를 Status 도메인 모델로 변환합니다.
25+
*
26+
* @param entity 변환할 StatusJpaEntity (null 가능)
27+
* @return 변환된 Status 도메인 모델 (null 가능)
28+
*/
29+
abstract override fun toModel(entity: StatusJpaEntity?): Status?
30+
31+
/**
32+
* StatusJpaEntity를 Status 도메인 모델로 변환합니다.
33+
* null이 아닌 entity를 받아 null이 아닌 모델을 반환합니다.
34+
*
35+
* @param entity 변환할 StatusJpaEntity (null이 아님)
36+
* @return 변환된 Status 도메인 모델 (null이 아님)
37+
*/
38+
abstract override fun toModelNotNull(entity: StatusJpaEntity): Status
39+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.persistence
2+
3+
import hs.kr.entrydsm.status.domain.status.adapter.out.mapper.StatusMapper
4+
import hs.kr.entrydsm.status.domain.status.adapter.out.persistence.repository.StatusRepository
5+
import hs.kr.entrydsm.status.domain.status.application.port.out.StatusPort
6+
import hs.kr.entrydsm.status.domain.status.model.Status
7+
import org.springframework.stereotype.Component
8+
9+
/**
10+
* 상태 데이터의 영속성 처리를 담당하는 어댑터 클래스입니다.
11+
* 헥사고날 아키텍처의 Driven Adapter 역할을 하며, 도메인 계층의 StatusPort를 구현합니다.
12+
*
13+
* @property statusRepository JPA 기반 상태 데이터 저장소
14+
* @property statusMapper 도메인 모델과 JPA 엔티티 간 변환 매퍼
15+
*/
16+
@Component
17+
class StatusPersistenceAdapter(
18+
private val statusRepository: StatusRepository,
19+
private val statusMapper: StatusMapper,
20+
): StatusPort {
21+
22+
/**
23+
* 상태 정보를 저장합니다.
24+
*
25+
* @param status 저장할 상태 도메인 모델
26+
*/
27+
override fun save(status: Status) {
28+
statusRepository.save(statusMapper.toEntity(status))
29+
}
30+
31+
/**
32+
* 접수번호로 상태 정보를 조회합니다.
33+
*
34+
* @param receiptCode 조회할 접수번호
35+
* @return 조회된 상태 도메인 모델 (없으면 null)
36+
*/
37+
override fun findByReceiptCode(receiptCode: Long): Status? =
38+
statusRepository.findByReceiptCode(receiptCode)?.let { statusMapper.toModel(it) }
39+
40+
/**
41+
* 모든 상태 정보를 조회합니다.
42+
*
43+
* @return 모든 상태 도메인 모델 리스트
44+
*/
45+
override fun findAll(): List<Status> {
46+
return statusRepository.findAll()
47+
.map { statusMapper.toModelNotNull(it) }
48+
}
49+
50+
/**
51+
* 접수번호로 상태 정보를 삭제합니다.
52+
*
53+
* @param receiptCode 삭제할 접수번호
54+
*/
55+
override fun deleteByReceiptCode(receiptCode: Long) {
56+
statusRepository.deleteByReceiptCode(receiptCode)
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.persistence.repository
2+
3+
import hs.kr.entrydsm.status.domain.status.adapter.out.domain.StatusCache
4+
import org.springframework.data.repository.CrudRepository
5+
6+
/**
7+
* 상태 캐시를 위한 Redis 저장소 인터페이스입니다.
8+
* Spring Data Redis를 통해 상태 정보의 캐시 관리를 담당합니다.
9+
*/
10+
interface StatusCacheRepository : CrudRepository<StatusCache, Long>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package hs.kr.entrydsm.status.domain.status.adapter.out.persistence.repository
2+
3+
import hs.kr.entrydsm.status.domain.status.adapter.out.domain.StatusJpaEntity
4+
import org.springframework.data.repository.CrudRepository
5+
6+
/**
7+
* 상태 JPA 엔티티를 위한 저장소 인터페이스입니다.
8+
* Spring Data JPA를 통해 기본 CRUD 작업과 커스텀 쿼리를 제공합니다.
9+
*/
10+
interface StatusRepository : CrudRepository<StatusJpaEntity, Long> {
11+
12+
/**
13+
* 접수번호로 상태 엔티티를 조회합니다.
14+
*
15+
* @param receiptCode 조회할 접수번호
16+
* @return 조회된 상태 엔티티 (없으면 null)
17+
*/
18+
fun findByReceiptCode(receiptCode: Long): StatusJpaEntity?
19+
20+
/**
21+
* 접수번호로 상태 엔티티를 삭제합니다.
22+
*
23+
* @param receiptCode 삭제할 접수번호
24+
*/
25+
fun deleteByReceiptCode(receiptCode: Long)
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package hs.kr.entrydsm.status.domain.status.application.port.`in`
2+
3+
/**
4+
* 합격 결과 발표 유스케이스 인터페이스입니다.
5+
* 최종 전형 결과를 발표하고 상태를 변경하는 기능을 정의합니다.
6+
*/
7+
interface AnnounceResultUseCase {
8+
9+
/**
10+
* 합격 결과를 발표합니다.
11+
* 전형 진행 중 상태에서 합격 여부 확인 상태로 변경합니다.
12+
*
13+
* @param receiptCode 접수번호
14+
*/
15+
fun execute(receiptCode: Long)
16+
}

0 commit comments

Comments
 (0)