PROJECT-LOG/likelion.university

[리팩토링] dto를 더욱 적극적으로 사용

HwangJerry 2023. 10. 13. 15:04

data jpa에서는 persistenceContext라는 개념이 존재하고, 엔티티 스냅샷을 비교하여 dirtyChecking을 수행하는 방식으로 운영된다. 따라서 엔티티가 persistence layer 이외에 노출되는 것은 엔티티에 대하여 불필요한 엑세스 포인트를 과하게 노출시키는 행위이며, 만약 협업을 하다가 로직이 꼬이게 되면 의도치 않은 엔티티 수정이 발생될 수 있다. 그 외에도 여러가지의 '안정성'을 위한다는 이유들로 persistence layer 이외의 레이어에서는 dto 사용이 적극 권장된다.

 

가지고 있던 문제는?

멋대플랫폼 아키텍처의 처음 구현 방식에서는 엔티티가 useCase라는 client 객체에서부터 사용되고 있었다. 처음 presentation layer에서 dto로 request를 받지만, 그 요청을 (business layer와 persistence layer를 담당하고 있는) core 모듈로 내려보낼 때 사용하는 dto의 일부 필드값을 Entity로 변환하여 보내는 구조였다.

 

우리의 프로젝트는 멀티모듈 아키텍처를 적용하였다.

  • 요청이 들어오면 controller - usecase - domain Service - repository 로 이어지는 로직이다.
  • 여기서 controller와 usecase는 client 모듈 패키지 아래에, domainService와 repositoryAdaptor는 core 모듈 패키지 아래에 작성되어 있다.
  • 따라서 controller -> usecase로는 request dto를 가지고 전달하고 usecase -> domainService에는 service dto를 가지고 전달한다.

 

기존 코드는 Post와 User와 같은 엔티티가 service dto에 등장하도록 설계되어 있었다. 즉, usecase 수행 중 엔티티가 조회되어 전부 조립된 뒤, domain service로 넘어오는 것이었다.

@Data
@Builder
@AllArgsConstructor
public class PostLikeCreateServiceDto {
    private Post post;
    private User loginUser;
}

 

 

게시글 수정을 예로 들어보겠다.

게시글은 postController에서 updateRequest 명세에 따라 요청을 받는다.

@PatchMapping("/{postId}")
public SuccessResponse<?> updatePost(@PathVariable Long postId, @RequestBody PostUpdateRequestDto request) {
    PostCommandResponseDto response = postUpdateUsecase.execute(postId, request);
    return SuccessResponse.of(response);
}

직관적인 흐름을 위해 useCase라는 객체를 도입하였고, requestDto를 이용하여 core 모듈로 요청을 내리기 위해 가공한 뒤 필요한 메세드를 호출한다.

@UseCase
@RequiredArgsConstructor
public class PostUpdateUseCase {
    ...
    public PostCommandResponseDto execute(Long postId, PostUpdateRequestDto request) {
        return postDomainService.editPost(buildDTO(postId, request));
    }

    private PostUpdateServiceDto buildDTO(Long postId, PostUpdateRequestDto request) {
        return PostUpdateServiceDto.builder()
                .loginUserId(userUtils.getCurrentUserId())
                .post(postAdaptor.findById(postId))
                .title(request.getTitle())
                .thumbnail(request.getThumbnail())
                .body(request.getBody())
                .build();
    }
}

게시글 수정 api는 기본적으로 제목, 내용, 썸네일을 수정할 수 있도록 의도해 두었는데, 수정할 게시글을 usecase에서 postAdaptor를 이용하여 바로 조회한 뒤 post 객체로 보내고 있었다.

 

리팩토링을 왜 결심하였는가?

우리가 구현한 멀티 모듈 구조상, 트랜잭션으로 관리되는 영역은 core 모듈 내의 domainService 객체까지이므로 useCase에서 모든 비즈니스 로직을 처리할수도 없는데도 엔티티가 useCase에서부터 바로 등장하는 것이 맞지 않다고 느꼈다.

 

간단하게 말해서, persistence layer를 의존성으로 가져야 하는 것은 트랜잭션 단위로 로직의 안정석을 관리하는 business layer까지만이라고 판단하였고, usecase에서는 domainService에 id만 넘긴 뒤에 business layer에서 트랜잭션으로 관리하면서 엔티티를 추출하여 수정하기로 결정하였다.

@UseCase
@RequiredArgsConstructor
public class PostUpdateUseCase {
    ...
    public PostCommandResponseDto execute(Long postId, PostUpdateRequestDto request) {
        return postDomainService.editPost(buildDTO(postId, request));
    }

    private PostUpdateServiceDto buildDTO(Long postId, PostUpdateRequestDto request) {
        return PostUpdateServiceDto.builder()
                .loginUserId(userUtils.getCurrentUserId())
                .postId(postId) // 수정
                .title(request.getTitle())
                .thumbnail(request.getThumbnail())
                .body(request.getBody())
                .build();
    }
}

기존에는 id를 넘기지 않았기 때문에 editPost()에서 post 를 조회할 일이 없었지만, 이제는 postAdaptor를 의존성으로 추가하여 관련 로직을 추가하여 아래와 같이 변경되었다.

public PostCommandResponseDto editPost(PostUpdateServiceDto request) {
    Post post = postAdaptor.findById(request.getPostId());
    if (!(post.getAuthor().getId().equals(request.getLoginUserId()))) {
        throw new PostNoAuthorizationException();
    }
    post.edit(request);
    Long savedId = postAdaptor.save(post);
    return PostCommandResponseDto.builder()
            .postId(savedId)
            .build();
}

 

레슨런은 무엇인가?

어떻게 해도 겉으로는 돌아가는 것처럼 보일 수 있을 것이다. 하지만, 레이어를 구분한 구조에서 유지보수성을 올리기 위해서는 각 레이어가 가져야 하는 관심사를 명확하게 인지하고 그에 맞게 짜야 한다는 걸 다시금 새기게 되었다.

 

멀티모듈이 처음에는 어색하여 우선 손 가는대로 짰던 것 같은데, 다시 로직을 눈으로 따라가보면서 이번 기회에 다시금 더 올바른 로직에 대한 고민을 하게 되어 다행이다.