이전글
코드리뷰
- 메서드 반환 타입을 Void로 변경
- 메서드 구현시 Setter -> Builder 활용
- ErrorCode Message 활용
- UserRole을 변경하는 Method 변수명 변경
- PostReadResponse에 of()메서드 생성
리팩토링
메서드 반환 타입을 Void 변경
로직을 바꾸다 보니 서비스에서 반환되는 엔티티가 사용되지 않는 상황이 발생하였습니다.
기존에는 서비스에서 반환받은 엔티티의 정보를 return 하도록 로직을 구성하였으나, Response 하면서 전달되는 Key의 이름이 달라져 요구사항과 다르게 반환되었기 때문입니다.
요구사항에 맞춰 Response 되는 body의 Key 이름을 맞추다보니 굳이 Enity로 서비스에서 받아온 정보를 저장할 필요가 없어졌습니다.
이에 따라 Post 엔티티로 반환받던 Service의 메서드를 Void 형식으로 변환하였습니다.
이전 코드(PostController)
public Response<PostResponse> modifiedPost(@PathVariable Integer postId,
@RequestBody PostModifyRequest dto,
Authentication authentication) {
String userName = authentication.getName();
Post post = postService.modifyPost(postId, dto, userName);
return Response.success(new PostResponse("포스트 수정 완료", postId));
}
Post타입의 post 변수가 사용되지 않고 있습니다.
변경된 코드(PostController)
public Response<PostResponse> modifiedPost(@PathVariable Integer postId,
@RequestBody PostModifyRequest dto,
Authentication authentication) {
String userName = authentication.getName();
postService.modifyPost(postId, dto, userName);
return Response.success(new PostResponse("포스트 수정 완료", postId));
}
Post 객체를 제거하고, 바로 수정 메서드를 실행시켰습니다.
이전 코드(PostService)
public Post modifyPost(Integer postId, PostModifyRequest dto, String userName) throws SNSAppException {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, "토큰이 존재하지 않습니다.");
}
throw new SNSAppException(INVALID_PERMISSION, "사용자가 권한이 없습니다.");
}
modifyPostTitleAndBodyAndLastModifiedAt(dto, post);
postRepository.save(post);
return post;
}
private void modifyPostTitleAndBodyAndLastModifiedAt(PostModifyRequest dto, Post post) {
post.setTitle(dto.getTitle());
post.setBody(dto.getBody());
post.setLastModifiedAt(LocalDateTime.now());
}
변경된 코드(PostService)
public void modifyPost(Integer postId, PostModifyRequest dto, String userName) throws SNSAppException {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, "토큰이 존재하지 않습니다.");
}
throw new SNSAppException(INVALID_PERMISSION, "사용자가 권한이 없습니다.");
}
modifyPostTitleAndBodyAndLastModifiedAt(dto, post);
postRepository.save(post);
}
private void modifyPostTitleAndBodyAndLastModifiedAt(PostModifyRequest dto, Post post) {
post.setTitle(dto.getTitle());
post.setBody(dto.getBody());
post.setLastModifiedAt(LocalDateTime.now());
}
반환 타입을 Post에서 Void로 변경하였습니다.
메서드 구현시 Setter -> Builder 활용
JPA를 사용하여 수정 기능을 구현할 경우 객체의 값을 수정시킨 후, 다시 Save를 하면 Insert 쿼리가 아닌 Update 쿼리를 진행하게 됩니다. 그렇기 때문에 Set 등을 이용하여 객체의 값을 변경시켜야 하는데, Set을 사용하는 데 거부감이 들어 다른 방법이 있을까 고민을 많이 하게 되었습니다.
- 새로운 Post 객체를 만들어서 기존의 post정보와 request정보를 섞어서 save를 할까?
- Set 사용을 피하기 위해 새로운 객체를 만드는 것이 효율적일까?
- Base Entity에 있는 날짜 정보들이 Builder를 통해서 생성이 안되는데 정보 이전이 가능할까?
사실 두번 째 고민 때문에 새로운 객체를 만들어 해결하는 것이 어렵다고 생각했는데, 코드리뷰를 통해 모두 해결할 수 있다는 이야기를 듣고 리팩토링을 시작하였습니다.
Base Entity에서 사용했던 @LastModifiedDate의 역할이 Entity의 값을 변경할 때 시간이 자동적으로 저장되는 것이었습니다.
그래서 request dto에 toEntity() 메서드를 생성하여 request 값을 기반으로 새로운 엔티티를 만든 후, save를 진행했습니다.
이전 코드(PostModifyRequest)
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class PostModifyRequest {
private String title;
private String body;
}
변경된 코드(PostModifyRequest)
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class PostModifyRequest {
private String title;
private String body;
public Post toEntity(Integer postId, User user){
return Post.builder()
.id(postId)
.title(this.title)
.body(this.body)
.user(user)
.build();
}
}
toEntity 메서드를 만들어 Builder 패턴으로 엔티티를 새롭게 만들면서 기존 Post 엔티티에 있는 Set 메서드를 모두 삭제하였습니다.
이전 코드(PostSerivce)
public void modifyPost(Integer postId, PostModifyRequest dto, String userName) throws SNSAppException {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, "토큰이 존재하지 않습니다.");
}
throw new SNSAppException(INVALID_PERMISSION, "사용자가 권한이 없습니다.");
}
modifyPostTitleAndBodyAndLastModifiedAt(dto, post);
postRepository.save(post);
return post;
}
private void modifyPostTitleAndBodyAndLastModifiedAt(PostModifyRequest dto, Post post) {
post.setTitle(dto.getTitle());
post.setBody(dto.getBody());
post.setLastModifiedAt(LocalDateTime.now());
}
변경된 코드(PostSerivce)
public void modifyPost(Integer postId, PostModifyRequest dto, String userName) throws SNSAppException {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, "토큰이 존재하지 않습니다.");
}
throw new SNSAppException(INVALID_PERMISSION, "사용자가 권한이 없습니다.");
}
postRepository.save(dto.toEntity(postId, post.getUser()));
}
set을 사용하지 않게 되면서 modifyPostTitleAndBodyAndLastModifiedAt메서드를 제거하였습니다.
toEntity() 메서드가 Post 엔티티를 반환하므로 save의 매개변수에 바로 대입하였습니다.
ErrorCode Message 활용
전역 예외를 사용하면서 SNSAppException
이라는 클래스를 만들어 예외가 발생하면 커스텀한 예외 정보를 반환받을 수 있도록 구현하였습니다. SNS AppException은
ErrorCode와 Message를 담을 수 있는 객체입니다.
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class SNSAppException extends RuntimeException{
private ErrorCode errorCode;
private String message;
...
}
예외를 발생시키면서 생성자 주입을 통해 해당 에러와 메시지를 함께 전달하고 있었는데, 메시지를 직접 입력하다 보니 ErrorCode 내의 Message의 역할이 무의미해지는 상황이 발생하였습니다.
그래서 메세지를 직접 입력하는 것이 아닌 Enum클래스인 ErrorCode의 Message를 적극 활용하는 방향으로 전환하였습니다.
ErrorCode Enum Class
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum ErrorCode {
DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "이미 존재하는 사용자입니다."),
USERNAME_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자가 존재하지 않습니다."),
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "패스워드가 잘못되었습니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 토큰입니다."),
INVALID_PERMISSION(HttpStatus.UNAUTHORIZED, "사용자가 권한이 없습니다."),
POST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 포스트가 존재하지 않습니다.."),
DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "DB에러"),
NOT_EXIST_TOKEN(HttpStatus.UNAUTHORIZED,"토큰이 존재하지 않습니다." ),
UNKNOWN_ERROR(HttpStatus.BAD_REQUEST, "알 수 없는 에러가 발생하였습니다."); ;
private HttpStatus status;
private String message;
}
이전 코드(PostSerivce)
public void deletePost(Integer postId, String userName) {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, "토큰이 존재하지 않습니다.");
}
// user가 찾아지지 않는다면 삭제할 수 없다.
User user = userRepository.findByUserName(userName)
.orElseThrow(
() -> new SNSAppException(USERNAME_NOT_FOUND, "일치하지 않은 회원 입니다.")
);
// post가 찾아지지 않는다면 삭제할 수 없다.
Post post = postRepository.findById(postId)
.orElseThrow(
() -> new SNSAppException(POST_NOT_FOUND, "해당 포스트가 없습니다.")
);
// User가 관리자가 아닌데, User와 Post를 작성한 User가 다르면 삭제할 수 없다.
if (!user.getUserRole().equals(ADMIN) && !user.getId().equals(post.getUser().getId())) {
throw new SNSAppException(INVALID_PERMISSION, "사용자가 권한이 없습니다.");
}
// Post를 찾는 findById 메서드를 사용하였으므로 deleteById가 아닌 Delete 사용.
postRepository.delete(post);
}
변경된 코드(PostSerivce)
public void deletePost(Integer postId, String userName) {
if (userName == null) {
throw new SNSAppException(NOT_EXIST_TOKEN, NOT_EXIST_TOKEN.getMessage());
}
// user가 찾아지지 않는다면 삭제할 수 없다.
User user = userRepository.findByUserName(userName)
.orElseThrow(
() -> new SNSAppException(USERNAME_NOT_FOUND, USERNAME_NOT_FOUND.getMessage())
);
// post가 찾아지지 않는다면 삭제할 수 없다.
Post post = postRepository.findById(postId)
.orElseThrow(
() -> new SNSAppException(POST_NOT_FOUND, POST_NOT_FOUND.getMessage())
);
// User가 관리자가 아닌데, User와 Post를 작성한 User가 다르면 삭제할 수 없다.
if (!user.getUserRole().equals(ADMIN) && !user.getId().equals(post.getUser().getId())) {
throw new SNSAppException(INVALID_PERMISSION, INVALID_PERMISSION.getMessage());
}
// Post를 찾는 findById 메서드를 사용하였으므로 deleteById가 아닌 Delete 사용.
postRepository.delete(post);
}
new SNSAppException(USERNAME_NOT_FOUND, "일치하지 않은 회원입니다.")
-> new SNSAppException(USERNAME_NOT_FOUND, USERNAME_NOT_FOUND.getMessage())
모든 예외 처리 부분에서 ErrorCode의 Message를 활용하여 직접 입력하는 것보다 메시지가 통일될 수 있도록 변경하였다.
직접 입력하게 되면 로직 중 받아올 수 있는 정보를 메시지로 넘길 수 있었으나, 요구사항으로 넘어온 ErrorCode Enum Class를 최대한 변경 없이 사용하는 것에 초점을 맞췄다.
다음글
'프로젝트 > Archive' 카테고리의 다른 글
[06] Comment Api 개발 - 1 (1) | 2023.01.04 |
---|---|
[05] 리팩토링 - 2 (0) | 2022.12.29 |
[04] Post Test 코드 작성 - 2 (Service Test) (0) | 2022.12.27 |
[04] Post Test 코드 작성 - 1 (Controller Test) (0) | 2022.12.27 |
[04] User Test 코드 작성 (0) | 2022.12.26 |