주특기 입문 개인과제로 익명 게시판 서버를 구현하였다.
과제 시작 전 Use Case Diagram과 API명세서 등을 작성하며 기본적인 CRUD기능의 구현을 목표로 서버 구현의 방향성을 계획하였다.
그리고 RESTful한 API설계를 위해 Controller, Service, Repository 등 분리하여 기능을 분배하였다.
또한 여러가지 방법의 설계에 있어 기술적으로 그 이유를 생각하며 구현하기위해 노력하였다.
package com.project.board.controller;
import com.project.board.dto.PostRequestDto;
import com.project.board.dto.PostResponseDto;
import com.project.board.service.PostService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
// 게시글 전체 출력
@GetMapping("/post")
public List<PostResponseDto> printPostControl() {
return postService.printAllPost();
}
// 선택 게시글 출력
@GetMapping("/post/{postId}")
public PostResponseDto printPostControl(@PathVariable("postId") Long postId) {
return postService.printPost(postId);
}
// 게시글 생성
@PostMapping("/post")
public PostResponseDto createPostControl(@RequestBody PostRequestDto requestDto) {
return postService.createPost(requestDto);
}
// 게시글 수정
@PutMapping("/post/{postId}")
public PostResponseDto updatePostControl(@PathVariable("postId") Long postId, @RequestBody PostRequestDto postRequestDto) {
return postService.updatePost(postId, postRequestDto);
}
// 게시글 삭제
@DeleteMapping("/post") //Request Parm방식
public String deletePostControl(@RequestParam Long postId, String password) {
return postService.deletePost(postId, password);
}
}
우선 Controller 부분을 살펴보면 HTTP 메서드에 대해 공부하던 중 GET과 DELETE 메서드의 경우는 Body를 가지지 않는 방법이 권장된다는 공식문서를 참조하여 GET의 경우 PathVariable, DELETE의 경우 RequestParm 방법으로 데이터를 전송하도록 하였다. POST와 PUT 메서드는 게시글의 데이터를 받아야하기때문에 Id는 PathVairable로 받되 게시글데이터는Body를 갖는 RequestBody 방법을 채용하였다. 다만 DELETE메서드의 경우 password의 정보가 URL에 들어나기때문에 보안성에 취약하다고 생각하기때문에 더 개선된 방법을 찾는다면 코드의 개선을 할 예정이다.
package com.project.board.service;
import com.project.board.dto.PostRequestDto;
import com.project.board.dto.PostResponseDto;
import com.project.board.entity.Post;
import com.project.board.repository.PostRepository;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
// 게시글 저장
public PostResponseDto createPost(PostRequestDto postRequestDto) {
// RequestDto -> Entity
Post post = new Post(postRequestDto);
// Entity ID 부여 및 postList에 저장
Long maxId = postRepository.getPostList().size() > 0 ? Collections.max(postRepository.getPostList().keySet()) + 1 : 1;
post.setId(maxId);
postRepository.getPostList().put(post.getId(), post);
// Entity -> ResponseDto
PostResponseDto postResponseDto = new PostResponseDto(post);
return postResponseDto;
}
// 게시글 출력
public PostResponseDto printPost(Long id) {
// 일치하는 ID 조회
if (postRepository.getPostList().containsKey(id)) {
PostResponseDto postResponseDto = new PostResponseDto(postRepository.getPostList().get(id));
return postResponseDto;
} else {
throw new IllegalArgumentException("선택한 게시글은 존재하지 않습니다.");
}
}
// 게시글 전체 출력
public List<PostResponseDto> printAllPost() {
// Map to List
List<PostResponseDto> resopnseList = postRepository.getPostList().values().stream()
.map(PostResponseDto::new).toList();
// 내림차순 정렬
List<PostResponseDto> resopnseListDesc = resopnseList.stream()
.sorted(Comparator.comparing(PostResponseDto::getDate).reversed()).toList();
return resopnseListDesc;
}
// 게시글 수정
public PostResponseDto updatePost(Long id, PostRequestDto postRequestDto) throws IllegalArgumentException
{
// 해당 게시글이 DB에 존재하는지 확인
if (postRepository.getPostList().containsKey(id)) {
// RequestDto -> Entity
Post post = new Post(postRequestDto);
// Password가 일치하는지 확인
if (Objects.equals(postRepository.findById(id).getPassword(), post.getPassword())) {
// 해당 게시글 수정
post.setId(id);
Date date = new Date(Calendar.getInstance().getTimeInMillis());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
post.setDate(simpleDateFormat.format(date));
postRepository.findById(id).update(post);
// Entity -> ResponseDto
PostResponseDto postResponseDto = new PostResponseDto(post);
return postResponseDto;
} else {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
} else { // 일치하는 ID가 없을때
throw new IllegalArgumentException("선택한 게시글은 존재하지 않습니다.");
}
}
// 게시글 삭제
public String deletePost(Long id, String password) throws IllegalArgumentException
{
// 해당 게시글이 DB에 존재하는지 확인
if (postRepository.getPostList().containsKey(id)) {
// Password가 일치하는지 확인
if (Objects.equals(postRepository.findById(id).getPassword(), password)) {
// 해당 게시글 삭제
postRepository.getPostList().remove(id);
return "삭제되었습니다.";
} else {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
} else { // 일치하는 ID가 없을때
throw new IllegalArgumentException("선택한 게시글은 존재하지 않습니다.");
}
}
}
다음으로 Service 부분을 살펴보면 이전 게시글에서 다룬 Dto를 사용하기위해 게시글 데이터의 추가 및 수정의 경우 해당 데이터를 dto로 받고 Entity로 변환하여 해당 로직을 실행 후 다시 responseDto로 변환하여 클라이언트에게 반환하고있다. Postman을 사용한 API테스트 결과 정상적으로 동작함을 확인했지만 런타임 도중 오류로 인한 각기 다른 상태코드에 따른 반환값설정에 관해 공부가 부족하여 추후에 해당부분을 개선할 예정이다. 또한 이번 코드 구현에 있어서 서버가 DB역할또한 같이 맡게설계하였는데 이는 바람직한 서버구성이라고 생각하지않기떄문에 DB기능 또한 분리하여 JPA를 사용하여 DB를 구현할 예정이다.
짧은 시간동안 배운것을 활용하는 과제였지만 배운것들을 다 사용해서 구현하기는 아직 쉽지 않은것 같다. 튜터님의 코드리뷰와 개선사항을 종합하여 더 좋은 코드를 작성하기위해 노력해야할것같다.