1. Sample Repository 생성
Sample, SampleDetail 엔티티에 맞춰서 repository를 생성
sample도메인에서는 만들지 않을거지만,추후 복잡한 쿼리는 QueryDsl을 활용하고자 한다
- SampleRepository
package com.woopi.safehome.domain._sample.adapter.outbound.persistence.jpa
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface SampleRepository: JpaRepository<SampleEntity, Long> {
fun findByIsDeletedFalse(): List<SampleEntity>
fun findByIdAndIsDeletedFalse(id: Long): SampleEntity?
}
- SampleDetailRepository
package com.woopi.safehome.domain._sample.adapter.outbound.persistence.jpa
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface SampleDetailRepository: JpaRepository<SampleDetailEntity, Long> {
}
2. PersistencePort, PersistenceAdapter 생성
usecase에서 호출하는 port와 구현체인 adapter를 정의함
- SamplePersistencePort
package com.woopi.safehome.domain._sample.application.port.outbound
import com.woopi.safehome.domain._sample.model.Sample
interface SamplePersistencePort {
/**
* 샘플 리스트 조회
*/
fun findAllSample(): List<Sample>
/**
* 샘플 상세 조회
*/
fun findSampleById(id: Long): Sample?
/**
* 샘플 저장
*/
fun saveSample(sample: Sample): Sample
}
- SamplePersistenceAdapter
Model 쪽을 좀 수정했다, 비지니스를 명확하게 하기위해서 하나의 모델을 CreateModel, UpdateModel 등으로 세분화 하고
그에 따라서 Mapper도 좀 더 명확하게 만들었다.
package com.woopi.safehome.domain._sample.adapter.outbound.persistence
import com.woopi.safehome.domain._sample.adapter.outbound.persistence.jpa.SampleEntity
import com.woopi.safehome.domain._sample.model.Sample
import com.woopi.safehome.domain._sample.model.SampleCreate
import com.woopi.safehome.domain._sample.model.SampleUpdate
object SampleEntityMapper {
fun toModel(entity: SampleEntity): Sample {
return Sample(
id = entity.id!!,
name = entity.name,
code = entity.code,
description = entity.description,
orderNo = entity.orderNo,
)
}
fun toModelList(entities: List<SampleEntity>): List<Sample> {
return entities.map(SampleEntityMapper::toModel)
}
fun toEntity(model: SampleCreate): SampleEntity {
return SampleEntity(
name = model.name,
code = model.code,
description = model.description,
orderNo = model.orderNo,
)
}
fun toEntity(model: SampleUpdate): SampleEntity {
return SampleEntity(
name = model.name,
code = model.code,
description = model.description,
orderNo = model.orderNo,
).apply { id = model.id }
}
}
3.Mapper 생성
객체간 변환을 도와주는 Mapper 클래스를 만들었는데,
2개를 만들었다, EntityMapper, DtoMapper
- SampleEntityMapper
package com.woopi.safehome.domain._sample.adapter.outbound.persistence
import com.woopi.safehome.domain._sample.adapter.outbound.persistence.jpa.SampleEntity
import com.woopi.safehome.domain._sample.model.Sample
object SampleEntityMapper {
fun toModel(entity: SampleEntity): Sample {
return Sample(
id = entity.id,
name = entity.name,
code = entity.code,
description = entity.description,
orderNo = entity.orderNo,
)
}
fun toModelList(entities: List<SampleEntity>): List<Sample> {
return entities.map(SampleEntityMapper::toModel)
}
fun toEntity(model: Sample): SampleEntity {
return SampleEntity(
name = model.name,
code = model.code,
description = model.description,
orderNo = model.orderNo,
).apply {
model.id?.let { this.id = it }
}
}
}
- SampleDtoMapper
4. UseCase 생성
간단하게 CRUD 정도만 메소드를 만들 예정
- SampleUseCase
package com.woopi.safehome.domain._sample.application.port.inbound
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleRequest
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleResponse
interface SampleUseCase {
/**
* 샘플 리스트 조회
*/
fun getSampleList(request: SampleRequest.Search): List<SampleResponse>
/**
* 샘플 상세 조회
*/
fun getSampleDetails(id: Long): SampleResponse
/**
* 샘플 생성
*/
fun createSample(request: SampleRequest.Create): SampleResponse
/**
* 샘플 수정
*/
fun updateSample(request: SampleRequest.Update): SampleResponse
/**
* 샘플 삭제
*/
fun deleteSample(id: Long): SampleResponse
}
- SampleDetailUseCase
package com.woopi.safehome.domain._sample.application.port.inbound
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleDetailRequest
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleDetailResponse
interface SampleDetailUseCase {
/**
* 샘플 상세 리스트 조회
*/
fun getSampleDetailList(request: SampleDetailRequest.Search): List<SampleDetailResponse>
/**
* 샘플 상세 상세 조회
*/
fun getSampleDetailDetails(id: Long): SampleDetailResponse
/**
* 샘플 상세 생성
*/
fun createSampleDetail(request: SampleDetailRequest.Create): SampleDetailResponse
/**
* 샘플 상세 수정
*/
fun updateSampleDetail(request: SampleDetailRequest.Update): SampleDetailResponse
/**
* 샘플 상세 삭제
*/
fun deleteSampleDetail(id: Long): SampleDetailResponse
}
5. Request, Response 생성
클래스가 너무 많이 생성되는 것을 방지하기 위해서,
Request는 Object클래스를 만들고, 안에 payload 역할을 하는 데이터 클래스를 넣었다.
Response는 아직 좀 고민이긴 한데.....일단 조회용 class 하나 만들고나서 나중에 고민하려한다
Object 클래스
- 싱글톤 패턴을 구현하는 특수한 클래스
- 애플리케이션에서 단 하나의 인스턴스만 존재하도록 보장
- 인스턴스화 불가능 (.new 불가)
- Sample Request
package com.woopi.safehome.domain._sample.adapter.inbound.web.dto
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
object SampleRequest {
data class Search(
@Schema(description = "이름", example = "샘플1")
val name: String?,
@Schema(description = "코드", example = "SAMPLE_001")
val code: String?,
)
data class Create(
@Schema(description = "이름", example = "샘플1")
@field:NotBlank(message = "이름은 필수 입니다.")
val name: String,
@Schema(description = "코드", example = "SAMPLE_001")
@field:NotBlank(message = "코드는 필수 입니다.")
val code: String,
@Schema(description = "설명", example = "샘플 설명")
val description: String?,
@Schema(description = "정렬 순서", example = "1")
val orderNo: Int,
)
data class Update(
@Schema(description = "샘플 ID", example = "1")
@field:NotNull(message = "샘플 ID는 필수 입니다.")
val id: Long,
@Schema(description = "이름", example = "샘플1")
@field:NotBlank(message = "이름은 필수 입니다.")
val name: String,
@Schema(description = "코드", example = "SAMPLE_001")
@field:NotBlank(message = "코드는 필수 입니다.")
val code: String,
@Schema(description = "설명", example = "샘플 설명")
val description: String?,
@Schema(description = "정렬 순서", example = "1")
val orderNo: Int,
)
}
- SampleResponse
package com.woopi.safehome.domain._sample.adapter.inbound.web.dto
import io.swagger.v3.oas.annotations.media.Schema
data class SampleResponse(
@Schema(description = "ID")
val id: Long,
@Schema(description = "이름")
val name: String,
@Schema(description = "코드")
val code: String,
@Schema(description = "설명")
val description: String?,
@Schema(description = "정렬 순서")
val orderNo: Int,
)
6. UseCaseImpl 생성
usecaseimpl 혹은 service 만들때,
클래스에 @Transactional(readOnly = true)을 선언하고, 쓰기 작업을 수행해야 하는곳에서만
@Transactional( readOnly = false는 default라 생략) 을 사용했는데,
이유로는
- 코드 간결 및 의미 명확화 (어노테이션 반복 최소화)
- 의도치 않은 데이터 수정 방지 (읽기 작업만 하는 메서드에서 실수로 데이터 변경 방지)
- Master-Slave 구조에서 자동으로 Slave DB로 라우팅하는 설정과 함께 사용하여 효율적인 리소스 관리
- Jpa 환경에서 조회 시 스냅샷을 생성하지도 않고, flush시 스냅샵과 엔티티를 비교하지도 않음, 즉 dirtyChecking을 하지 않음
- 그로 인해 스냅샷에 할당하는 메모리를 절약하고, 비교 연산을 수행하지 않아서, CPU 절약 수행
package com.woopi.safehome.domain._sample.application.usecase
import com.woopi.safehome.domain._sample.adapter.inbound.web.SampleDtoMapper
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleRequest
import com.woopi.safehome.domain._sample.adapter.inbound.web.dto.SampleResponse
import com.woopi.safehome.domain._sample.application.port.inbound.SampleUseCase
import com.woopi.safehome.domain._sample.application.port.outbound.SamplePersistencePort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Transactional(readOnly = true)
@Service
class SampleUseCaseImpl(
private val samplePersistencePort: SamplePersistencePort
): SampleUseCase {
override fun getSampleList(request: SampleRequest.Search): List<SampleResponse> {
return SampleDtoMapper.toResponse(samplePersistencePort.findAllSample());
}
override fun getSampleDetails(id: Long): SampleResponse {
samplePersistencePort.findSampleById(id).let {
return SampleDtoMapper.toResponse(it)
}
}
@Transactional
override fun createSample(request: SampleRequest.Create): SampleResponse {
TODO("Not yet implemented")
}
@Transactional
override fun updateSample(request: SampleRequest.Update): SampleResponse {
TODO("Not yet implemented")
}
@Transactional
override fun deleteSample(id: Long): SampleResponse {
TODO("Not yet implemented")
}
}
'개발일지' 카테고리의 다른 글
| [개발일지_safeHome] 9. swagger 세팅 (0) | 2025.12.28 |
|---|---|
| [개발일지_safeHome] 8. Custom Exception 개발 (0) | 2025.12.28 |
| [개발일지_safeHome] 6.JPA Audit 설정 추가 (0) | 2025.12.15 |
| [개발일지_safeHome] 5. sample 도메인 개발 (모델, 엔티티) (0) | 2025.12.14 |
| [개발일지_safeHome] 4. sample 도메인 추가(로컬 DB 세팅, write/read 세팅, sample 테이블 추가) (0) | 2025.12.10 |