STEP 1 피드백
read.me 구체적으로 작성, commit message 의미 있는 코멘트, '클린코드', 강의 클론 코딩, dirty checking
=>
- read.me : 줄이 자꾸 이상해져서 헤맸는데 마크다운문법이란게 따로 있단다...3, 4단계까지 다 하고 난뒤에 정리해야겠다.
- commit 코멘트 : 기능단위별로 푸쉬하고싶은데 자꾸 이것저것 눈에 보이는걸 건들다보니까 잘 안된다.
- step2 클린코드 : 챌린지 반 2주차 세션 라이브 코딩을 보면서 뭔가 더 알아보기 쉽게 작성하고 싶다는 마음이 들었는데 어떻게 손을 대야할지 모르겠다.
@Transactional
override fun updateCard(cardId: Long, request: UpdateCardRequest): CardResponse {
val card = cardRepository.findByIdOrNull(cardId) ?: throw ModelNotFoundException("Card", cardId)
when(request.status) {
CardStatus.TRUE.name -> card.isCompleted()
CardStatus.FALSE.name -> card.isNotCompleted()
}
card.title = request.title
card.content = request.content
card.name = request.name
return card.toResponse() // update 시 영속성 컨텍스트에 대해 dirty checking 일어나므로 save 할 필요 X
}
서비스단에서 할일의 상태변경을 아래와 같이 card entity의 메소드로 줄였다.
@Service
class CardServiceImpl( ... ) {
...
@Transactional
override fun updateCard(cardId: Long, request: UpdateCardRequest): CardResponse {
val card = cardRepository.findByIdOrNull(cardId) ?: throw ModelNotFoundException("Card", cardId)
card.isCompletedOrNot(request, card)
card.title = request.title
card.content = request.content
card.name = request.name
return card.toResponse() // update 시 영속성 컨텍스트에 대해 dirty checking 일어나므로 save 할 필요 X
}
...}
@Entity
calss Card( ... ) {
...
fun isCompletedOrNot(request: UpdateCardRequest) {
when(request.status) {
CardStatus.TRUE.name -> status = CardStatus.TRUE
CardStatus.FALSE.name -> status = CardStatus.FALSE
}
}
...
}
비밀번호 검사 메소드를 만들어서 댓글의 수정, 삭제 서비스단에 반영했다.
@Service
class CardServiceImpl( ... ) {
...
@Transactional
override fun updateComment(cardId: Long, commentId: Long, request: UpdateCommentRequest): CommentResponse {
val card = cardRepository.findByIdOrNull(cardId) ?: throw ModelNotFoundException("Card", cardId)
val comment = commentRepository.findByIdOrNull(commentId) ?: throw ModelNotFoundException("Comment", commentId)
// 비밀번호 검사
if(comment.isNameAndPasswordInCorrect(request)) throw UnauthorizedAccess()
comment.content = request.content
return comment.toResponse()
}
...}
@Entity
class Comment( ... ) {
...
fun isNameAndPasswordInCorrect(request: CheckRequest): Boolean {
return request.name != this.name || request.password != this.password
}
...}
서비스단에서 기능이 명확하게 보이게 하기 위한 메소드를 생각하다보니 그럼 그렇게 되면 Entity에 많은 메소드들이 몰려있어도 되는걸까하는 고민이 들었다.
할일 생성과정에서 request의 값을 저장하는 것도 메소드로 만들 수 있고
업데이트할 때 값을 할당하는 것도 메소드로 만들 수 있고 얼마든지 바꿀 수 있는데 더 나은 코드는 뭘까 .!.!
- 클론 : 개인과제 다 끝낸 후 한 번 더 해보기
- dirty checking : 영속성 컨텍스트, update 시 save 함수 불필요
STEP2 QnA
- 처음 설계한 API 명세서에 변경사항이 있었나요? 변경 되었다면 어떤 점 때문 일까요? 첫 설계의 중요성에 대해 작성해 주세요!
- ERD 와 DTO 를 수정했다. 컬럼 변경사항이 있었기 때문.
- 첫 설계를 통해 내가 개발하고자 하는 어플리케이션의 전체적인 흐름을 파악할 수 있다.
2. ERD를 먼저 설계한 후 Entity를 개발했을 때 어떤 점이 도움이 되셨나요?
- 내가 지금 작성하고 있는 Entity 와 다른 Entity 와의 관계를 인지하며 작성할 수 있다.
3. 만약 댓글이 여러개 달려있는 할일을 삭제하려고 한다면 무슨 문제가 발생할까요? Database 테이블 관점에서 해결방법이 무엇일까요?
- 테스트 해봤을 때 응답도 성공적으로 되고 DB내부에서도 할일과 댓글이 잘 삭제되었다.
- 할일과 댓글의 관계설정에서 cascade 를 통해 영속성을 전파하고 orphanRemoval 을 통해 부모와의 연결이 끊긴 댓글을 삭제하도록 설정했기 때문인 것 같다.
@OneToMany(mappedBy = "card", fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true)
val comments: MutableList<Comment> = mutableListOf()
4. IoC / DI 에 대해 간략하게 설명해 주세요!
IoC : 객체가 제어의 권한을 다른 곳에 위임한다. spring에서는 빈으로 등록된 객체들을 IoC 컨테이너가 관리한다.
DI : 의존성을 주입하는데 자체적으로 생성하는 것이 아니라 외부에서 주입하므로써 객체 간 결합도를 낮춘다.
STEP 3
1. 할 일 목록에서 작성일 기준 오름차순, 내림차순 정렬 기능 추가
api를 요청할 때 정렬 기준(오름차순, 내림차순)을 포함하기
정렬 기준을 통해 정렬한 할 일 목록 반환하기
2. 할 일 목록에 작성자 기준 필터 기능 추가
api를 요청할 때 작성자 이름을 포함하기
작성자 이름이 일치하는 할 일 목록 반환하기
목록을 불러올 때 정렬기준과 작성자이름을 request로 받고 서비스단의 목록 조회 로직을 수정한다.
이때 쿼리메소드를 활용해야하는건가?
기본값은 어떻게 설정해놓는거지? => name 의 경우 일치하지 않으면 조회가 안되야 올바른 서비스다 !
override fun getAllCards(request: GetCardListRequest): List<CardResponse> {
var response: List<CardResponse> = listOf()
if(request.orderBy == CardOrderBy.DESC.name) {
response = cardRepository.findAllByName(request.name).sortedByDescending { it.createdAt }
} else {
response = cardRepository.findAllByName(request.name).sortedBy { it.createdAt }
}
return response
}
쿼리메소드로 이름반환은 하겠는데 request로 받아온 정렬은 잘 모르겠다. 그래서 if 문으로 돌리니 이렇게 지저분한데 어떻게 고쳐야 할지 모르겠다....
CardResponse의 status 가 set이 안된다는 오류가 떴었다.
=> enum 클래스인 status의 타입을 클래스로 선언
data class CardResponse(
val id: Long,
val status: CardStatus,
val title: String,
val content: String?,
val createdAt: String,
val name: String
)
*기존 단건 카드 조회 시
override fun getCard(cardId: Long): CardWithCommentResponse {
val card = cardRepository.findByIdOrNull(cardId) ?: throw ModelNotFoundException("Card", cardId)
val comments = commentRepository.findByCardId(cardId).map { it.toResponse() }
return CardWithCommentResponse(card.toResponse(), comments)
}
card 따로 comment 따로 불러왔었는데 이 둘은 관계설정을 해놨기 때문에 card.comment 로 불러올 수 있다고...한다...😰
fetchtype 도 그냥 아~나중에 불러오는 거구나 했는데 이런 때에 쓰일 수 있다고 한다.
override fun getCard(cardId: Long): CardWithCommentResponse {
val card = cardRepository.findByIdOrNull(cardId) ?: throw ModelNotFoundException("Card", cardId)
return CardWithCommentResponse(card.toResponse(), card.comments.map { it.toResponse() })
}
CardResponse 를 상속받아서 댓글이랑 같이 반환하는 형태로 어떻게 만들 수 있는지 나아아아중에 고민해보기
3. 할 일 작성, 수정시 validation 추가
- 제목 1자 이상 200자 이내 검사
- 본문 1자 이상 1000자 이하 검사
- 조건 불충족 시 기능 실패 응답
Spring boot Validation
복잡한 검증 로직을 어노테이션으로 해결할 수 있다.
의존성
implementation ("org.springframework.boot:spring-boot-starter-validation")
Controller 의 @RequestBody 에 @Validated 선언
해당 DTO 에 제약조건 @field: 로 명시
class CardController(...) {
...
@PostMapping
fun createCard(@Validated @RequestBody createCardRequest: CreateCardRequest): ResponseEntity<CardResponse> {
return ResponseEntity.status(HttpStatus.CREATED).body(cardService.createCard(createCardRequest))
}
...}
data class CreateCardRequest(
@field:Size(min = 1, max = 200, message = "제목은 1자 이상 200자 이하여야 합니다.")
val title: String,
)
@RequestParam 과 @Pathvariable 은 Controller 에 @Validated 선언하고 제약조건 추가
파리미터가 유효하지 않을 때
@RequestBody MethodArgumentNotValidException 에러
@RequestParam/@PathVariable ConstraintViolationException 에러
제목과 내용을 공백으로 제출해봤다.
기능실패 400 코드와 메시지가 잘 반환되는데 원래 이렇게 기나?! 이런 경우 친절한 에러메시지를 작성하기 위해 커스텀한다고 하는데 정확히 어떤 형식으로 하는지 잘 모르겠다.
Spring Data JPA
getById
@Override
public T getById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
LAZY 형태로 값을 가져온다. == 일단 proxy로 만든 다음 실제 사용할 때 DB에 접근한다.
=> 없는 데이터를 조회하면 실제로 접근할 때 EntityNotFoundException 에러가 발생한다.
findById
실제로 DB에서 값을 찾아오기 때문에 객체가 없다면 Null 을 반환한다.
STEP4
1. 할 일 카드 목록 api의 응답에, 연관된 댓글 내용을 추가해주세요.
할 일 목록과 댓글 목록을 효율적으로 가져와서 매칭하려면 어떻게 해야 할까요?
N + 1 query 문제 알아보기
2. 할 일 카드 목록 api에 pagination 기능을 추가해주세요.
사용자가 많아져서 할 일 카드가 너무 많아지면 어떤 일이 벌어질까요?
offset 기반 pagination과 cursor 기반 pagination에 대해 알아보기
3. 회원가입, 로그인 기능을 추가해주세요.
로그인 한 사용자가 자신의 할 일, 댓글만 수정, 삭제할 수 있게 해주세요.
인증, 인가에 대해 알아보기: 요청한 사용자가 누구인지, api를 호출할 권한이 있는지를 어떻게 알 수 있을까요? basic authentication과 bearer authentication에 대해 알아보기
basic auth에 비해 token 기반 auth가 가지는 장점이 무엇일까요?
목록에 댓글도 같이 가져오려면 오늘 만든거 결국은 수정해야 하는건가 😰😰😰
CardWithCommentResponse를 List로 만들어주면 되려나
'왕초보일지' 카테고리의 다른 글
240105 TIL | Todo과제 (1) | 2024.01.05 |
---|---|
240104 TIL | (2) | 2024.01.04 |
240102 TIL | (0) | 2024.01.02 |
231230 | TIL (0) | 2023.12.30 |
231229 TIL | 스프링 부트 3 기본 지식 (2) | 2023.12.29 |