Goal: "스프링 부트로 로그인 기능이 없는 나만의 블로그 백엔드 서버 만들기"
1. Entity를 그대로 반환하지 말고, DTO에 담아서 반환해주세요!
2. 프론트엔드와 백엔드가 느슨하게 결합하는 환경이 ”최근에는” 더 일반적이라고 말씀드렸죠? 앞으로 남은 강의 예제와 실습에서는 html/css/js 즉 뷰도 같이 반환 하겠지만, 과제에는 여러분들이 서버 로직에 더 집중하실 수 있도록 JSON을 반환하는 API형태로 진행하려고 합니다.
3. 눈으로 직접 확인 할 수 있었던 view와는 다르게, 여러분들이 과제를 진행하시려면 서버가 반환하는 결과값을 더 쉽게 확인 하실 수 있는 도구가 필요합니다. 바로 PostMan 입니다. 사용법은 아래 페이지에서 꼭 확인해주세요!
1. 아래의 요구사항을 기반으로 Use Case 그려보기
- 손으로 그려도 됩니다.
- [Use Case 설명글](https://narup.tistory.com/70)
2. 전체 게시글 목록 조회 API
- 제목, 작성자명, 작성 내용, 작성 날짜를 조회하기
- 작성 날짜 기준 내림차순으로 정렬하기
3. 게시글 작성 API
- 제목, 작성자명, 비밀번호, 작성 내용을 저장하고
- 저장된 게시글을 Client 로 반환하기
4. 선택한 게시글 조회 API
- 선택한 게시글의 제목, 작성자명, 작성 날짜, 작성 내용을 조회하기
(검색 기능이 아닙니다. 간단한 게시글 조회만 구현해주세요.)
5. 선택한 게시글 수정 API
- 수정을 요청할 때 수정할 데이터와 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
- 제목, 작성자명, 작성 내용을 수정하고 수정된 게시글을 Client 로 반환하기
6. 선택한 게시글 삭제 API
- 삭제를 요청할 때 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
- 선택한 게시글을 삭제하고 Client 로 성공했다는 표시 반환하기
명확히 개념들을 이해한 상태에서 설계를 진행한 것이 아니다보니 부족한 부분들이 많습니다.
(예쁘게 봐주세요.. 꾸벅)
[Use Case Diagram을 그려보았다.]
[API 명세서를 작성해보았다.]
[양식을 작성해보았다.(사실 이제와서 생각해보면 이 내용들이 API 명세서에 담겨야 할 내용들이다.) ]
스프링 입문강의 메모서비스 레파지토리 링크(클론코딩)
나만의 블로그 백엔드 서버 만들기 레파지토리 링크(이번 과제 결과물)
'왜?' Entity가 아닌 Dto로 포장해야 하는지 이유가 궁금했다.
이유를 알아야 목적에 맞게 잘 사용할 수 있다 -> [ how 이전에 why부터 이해하자 ]
그래서 WHY를 찾아보았다 -> 요청과 응답으로 Entity 대신 Dto를 사용하자 - Tecoble
만약 API별로 요청과 응답에 담길 데이터가 달랐다면 Dto가 많~~은게 맞지만, 이번에는 처음이라 그런지 API들이 같은 형식으로 요청, 응답이 이루어졌기에 각각 하나씩만 만들어주었다.
// service.PostService (수정전)
public boolean pwIsValid(String inputPw, String postPw){
return inputPw.equals(postPw);
}
@Transactional
public String deletePost(Long id, String pw) {
Post post = postRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("게시물이 존재하지 않습니다.")
);
if(pwIsValid(pw, post.getPassword())){
postRepository.deleteById(id);
return "삭제에 성공했습니다.";
} return "비밀번호가 다릅니다. 삭제 실패";
기존에 작성했던 비밀번호 검증 로직(위의 코드)은 boolean 값을 반환해주는 방식이었다.
이 비밀번호 검증 로직의 문제점
1. 비밀번호 검증을 Service가 하고 있다.
- 비밀번호를 가지고 있는 것은 Post 객체이다.
- 값을 가지고 있다는 것은 그것에 대한 '역할과 책임'을 가진다는 것이다 -> Post 객체에서 비밀번호 검증을 해야한다.
2. pwIsValid 메서드 자체도 본인의 역할을 다 하지 못하고 있다.
- 위 코드를 보면 pwIsValid 메서드가 boolean 값을 return해주기 때문에 받는 쪽에서 if로 추가적인 작업을 해줘야한다.
- 비밀번호 검증 로직의 역할은 **'비밀번호가 맞다면 다음 로직이 실행되도록 하고, 틀릴 경우에는 다음 로직이 실행되지 못하게 하는 것이다.'
수정한 코드
/// entity.Post (수정 후)
public void validatePassword(String inputPassword) {
if (!inputPassword.equals(this.getPassword())) throw new IllegalArgumentException("비밀번호 불일치");
}
/// service.PostService (수정 후)
@Transactional
public void deletePost(Long id, String inputPassword) {
Post post = postRepository.findById(id).orElseThrow(PostNotExistException::new);
post.validatePassword(pw);
postRepository.deleteById(id);
IllegalArgumentException
를 던져주는 방식으로 비밀번호가 일치하면 '아무것도 하지 않는'방식으로 코드를 작성했다.다시 한번 위의 코드(수정 전)를 살펴보자
비밀번호 검증메서드에서도 return / deletePost 메서드에서도 return아주 return 범벅이었다.
- 요구사항에서 '반환하라'라는 의미를 return으로 받아들인 탓이 컸다.
- 튜터님의 코드리뷰 과정에서 '아무것도 반환하지 않는 것도 응답이다.'라는 배움을 얻고 (수정후) 코드로 발전할 수 있었다.
@Transactional(readOnly = true)
public List<PostResponseDto> getAllPosts(){
return postRepository.findAllByOrderByModifiedAtDesc().stream()
.map(PostResponseDto::new).collect(Collectors.toList());
}
List<Post>
를 List<PostResopnseDto>
로 만드는 과정을 stream으로 구현했다. 심지어 자세히 살펴보면 method reference도 하나 들어가있다. 이어지는 글에서는 이번 개인과제 기간에 접하게 된 다양한 글들을 다시 한번 읽어보며 정리해보고, 약간의 회고도 곁들이려한다. (2부 글 링크)