*Domain model 이랑 DTO 개념이 잘 안잡힌다. 숙련 강의를 다 듣고 복습해보면 괜찮을까?
web application 요구사항 다시 확인
- 유저 혹은 Frontend Application의 요청을 처리하고, 적절한 응답을 줄 수 있어야 합니다.
- 예외 처리를 할 수 있고, 예외가 발생했을 때 적절한 응답을 줄 수 있어야 합니다.
- 인증과 인가 처리를 할 수 있어야 합니다.
- 비즈니스 로직을 처리할 수 있어야 합니다.
- Transaction 관리 전략이 있어야 합니다.
- 스토리지 및 다른 외부 시스템과 통신할 수 있어야 합니다.
어제는 Controller 단을 작성하여 요청을 처리하고 응답을 주는 것을 구현했다.
오늘은 Service Layer 를 작성하여 비즈니스 로직 구현, 트랜잭션 경계 설정, 예외에 대한 처리를 해본다.
Service Layer
1 Interface 생성
2 Service (Interface 구현)
3 Web layer 와 Service layer 의 연결
4 Controller 작성
5 예외처리
6 트랜잭션
7 예외 응답
실무에서 아래와 같이 Service / ServiceImpl 패턴을 항상 적용할 필요 X
1 Interface 생성
interface CourseService {
fun getAllCourseList(): List<CourseResponse>
fun getCourseById(courseId: Long): CourseResponse
fun createCourse(request: CreateCourseRequest): CourseResponse
fun updateCourse(courseId: Long, request: UpdateCourseRequest): CourseResponse
fun deleteCourse(courseId: Long)
}
2 @Service, 구현 클래스로 Interface 구현체 작성
- Service 는 Interface 를 상속받음
- 로직 주석 작성
- Repository를 통해 DB에서 조회할 때는 DTO가 아닌 Domain Model(Entity)를 사용하기 때문에 DTO로 변환해주는 작업 필요
@Service
class CourseServiceImpl: CourseService {
override fun getAllCourseList(): List<CourseResponse> {
// TODO: DB에서 모든 Course 목록을 조회하여 CourseResponse 목록으로 변환 후 반환
TODO("Not yet implemented")
}
override fun getCourseById(courseId: Long): CourseResponse {
// TODO: DB에서 특정 ID 기반으로 Course 조회하여 CourseResponse 로 변환 후 반환
TODO("Not yet implemented")
}
override fun createCourse(request: CreateCourseRequest): CourseResponse {
// TODO: 받아온 request 를 Course Entity로 변환 후 DB 에 저장
TODO("Not yet implemented")
}
override fun updateCourse(courseId: Long, request: UpdateCourseRequest): CourseResponse {
// TODO: DB 에서 CourseId 에 해당하는 Course 를 가져와서 request 기반으로 업데이트 후 DB에 저장,
// CourseResponse 로 변환 후 반환
TODO("Not yet implemented")
}
override fun deleteCourse(courseId: Long) {
// TODO: DB 에서 CourseId 에 해당하는 Course 를 삭제, 연관된 CourseApplication과 Lecture 도 모두 삭제
TODO("Not yet implemented")
}
}
3 Web Layer 와 연결
- Controller 에서 Service 를 사용하려면 주입을 받아야 함
- 이 때 Controller 생성자에서는 Interface 를 받아오느냐 아니면 Service 를 받아오느냐?
- Spring 에서 Interface 를 상속받으면 이 Interface 를 상속받는, @Service 가 붙은 Bean 들을 알아서 찾아준다.
-> Web Layer 는 실질적으로 Service Layer의 구현부를 몰라도 문제없다 -> OCP 원칙!
@RequestMapping("/courses")
@RestController
class CourseController(
private val courseService: CourseService
) { …
4 Controller 작성
- ResponseEntity 형태로 반환
- Status
- GET -> OK
- POST -> CREATED
- PUT -> OK
- DELETE -> NO CONTET
- body
: 인자로 받아온 Service에서 해당하는 메소드 호출
@GetMapping
fun getCoursesList(): ResponseEntity<List<CourseResponse>> {
return ResponseEntity.status(HttpStatus.OK).body(courseService.getAllCourseList())
} ^body 로 DTO 가 담긴다
5 예외처리
:예측하지 않은 동작이 발생했을 때 적절한 Exception을 던진다
이 때 작업 중 일부만 성공하면 안되므로 특정 작업 단위에서 트랜잭션 처리를 해주어야 한다! -> 6
RuntimeException
: application 이 실행될 때 발생되는 예외
domain의 다른 로직에서도 공통으로 발생할 수있으니 domain 하위의 exception pakage를 생성해서 만들었다.
data class ModelNotFoundException(val modelName: String, val modelId: Long): RuntimeException(
"Model $modelName not found with given id: $modelId"
)
6 트랜잭션
@ Transactional
: 지속적인 데이터 저장소에서 수행되는 작업의 논리적인 실행 단위
- 원자성 Atomicity : 모든 작업이 성공하면 커밋되고, 하나라도 실패하면 트랜잭션은 롤백되어야 한다.
- 일관성 Consistency : 트랜잭셩 실행 전후로 일관된 상태를 유지하여야 한다. (데이터베이스의 규칙 등)
- 고립성 Isolation : 하나의 트랜잭션이 실행 중일 때, 다른 트랜잭션에서는 해당 트랜잭션의 변경 내용을 볼 수 없어야 한다.
- 지속성 Durability : 성공적으로 완료되면 그 결과는 영구적으로 저장되어야 한다.
- 일반적으로 메소드에 적용되어 해당 메소드는 한 트랜잭션 내에서 실행된다고 선언
- model 의 상태를 변경하는 CUD 경우 웬만하면 트랜잭션 처리
- DB 관련 작업이기때문에 DB 관련 패키지를 dependencies 에 추가해줘야 한다.
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
@Transactional
override fun createCourse(request: CreateCourseRequest): CourseResponse {
// TODO: 받아온 request 를 Course Entity로 변환 후 DB 에 저장
TODO("Not yet implemented")
}
아직 DB 와 연결하지 않았기 때문에 실행할 수 없다.
DB 를 생성하지 않았으니 h2 라는 저장소를 이용
별도의 설치 필요 X 테스트 할 때 많이 사용한다
implementation("com.h2database:h2")
7 처리된 예외에 응답하기 < Web Layer 영역
(1) Controller 내부
@RequestMapping("/courses")
@RestController
class CourseController(
private val courseService: CourseService
) {
...
@ExceptionHandler(ModelNotFoundException::class)
fun handleModelNotFoundException(e:ModelNotFoundException): ResponseEntity<Unit> {
return ResponseEntity
.status(HttpStatus.NOT_FOUND).build()
}
}
@ExceptionHandler : Spring 에게 어떻게 Exception 을 Handling 할지 알려줌
(2) 개별 Class
@RestControllerAdvice // Exception 전역적 처리
class GlobalExceptionHandler {
@ExceptionHandler(ModelNotFoundException::class)
fun handleModelNotFoundException(e: ModelNotFoundException): ResponseEntity<ErrorResponse> {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ErrorResponse(message = e.message))
}
}
// 에러에 대한 응답 DTO
data class ErrorResponse(
val message: String?
)
각 Controller 마다 중복되는 Exception Handling 을 위해 Exception 을 전역적으로 처리할 수 있도록
@ControllerAdbvice / @RestControllerAdvice 를 새 Class 에 지정
그 외
Lecture / courseApplication Controller 의 Service Layer 작성
Course를 통해서 Lecture/ CourseApplication 구현
이후 각 Controller 에 생성자 주입
차근차근 따라하는데도 헷갈려서 하루가 갔다 🥲
controller 랑 service 작성하는 건 혼자서 해봐야지 흐름이 이해가 더 잘 이해갈 것 같다.
내일 복습하고 Repository 작성까지 해보자
HTTP 책을 아주 살짝 읽어보았다.
그냥 웹이 어떻게 만들어지는건지 나와있나? 했더니 TCP/IP 부터 이게 뭔가 싶었다. 😂나 진짜 아무것도 몰랐구나...
공부할 것들이 끝도 없이, 파면 팔수록 더 나오는데 막막한 것도 있지만 2년 후, 3년 후의 나는 지금이랑 비교도 안될만큼 많은 것들을 알고 있을 거라는 근거 없는 자신감으로 약간 재밌게 느껴지기도 한다.
무엇보다 현대인의 일상에서 뗄래야 뗄 수 없는 인터넷 세상이 어떻게 돌아가는지 안다는 건 진짜 재밌는 일인 것 같다.
'왕초보일지' 카테고리의 다른 글
231222 TIL | (1) | 2023.12.22 |
---|---|
231221 TIL | (2) | 2023.12.21 |
231219 TIL API설계/DTO작성/챌린지반주제 (0) | 2023.12.19 |
키오스크 피드백 반영하기 (0) | 2023.12.18 |
231218 TIL Spring 입문 1주차 강의 (1) | 2023.12.18 |