103DAYS) [Main-Project] 디렉토리 구조 리팩토링, yml파일 profile 관리

nacSeo (낙서)·2023년 3월 16일
0

현재 코드에서는 controller는 controller만의 역할, service는 service만의 역할을 수행하고 있지 않는 등 뒤죽박죽 섞여서 각자의 역할만 수행하고 있지 않고, 결합도가 너무 높다. 우리가 학습해올 때는 이상적인 모듈화를 위해 응집도는 높게, 결합도는 낮게라는 말을 귀에 딱지가 앉도록 들어왔는데 정작 우리 프로젝트에서는 적용시키고 있지 못한 것이다.
그래서 지난 멘토링 시간에 지적을 받았고, composite 패턴을 사용해서 코드들을 리팩토링해보기로 했다.

현재 비즈니스 로직이 controller에 존재하고 있고, 이 때문에 테스트 시 @WebMvcTest(~.class)로 controller를 테스트를 하여도 문제가 발생할 것이다. 따라서 이를 수정하고, composite 패턴 즉, 각각의 2개의 서비스가 필요하다면 compositeService 하나로 둔 뒤, controller에서 사용할 수 있도록 하는 구조로 리팩토링하였다. 물론, DDD 패턴도 존재하나 이를 사용하기에는 우리 레벨에서 무리가 있다고 판단하였다.
BE 각각 자신이 맡은 파트에서 구조 리팩토링을 진행하여 나는 review와 reviewLike쪽을 리팩토링하였다.

우선, composite_service라는 디렉토리를 하나 만들고, 거기에 ReviewCompositeService와 ReviewLikeCompositeService 클래스들을 생성해주었다.

ReviewCompositeService

@RequiredArgsConstructor
public class ReviewCompositeService {

    private final ReviewService reviewService;
    private final ReviewLikeService reviewLikeService;
    private final MemberService memberService;

    public Review createReview(Review creatingReview, String email){
        Member member = memberService.findLoginMemberByEmail(email);

        creatingReview.setMember(member);

        Review createdReview = reviewService.createReview(creatingReview);

        return createdReview;
    }

    public Review updateReview(Review updatingReview, String email) {
        Member member = memberService.findLoginMemberByEmail(email);

        updatingReview.setMember(member);

        Review updatedReview = reviewService.updateReview(updatingReview, member.getMemberId());

        updatedReview.setReviewCount(reviewLikeService.getReviewLikeCount(updatedReview.getId()));

        return updatedReview;
    }

    public Review getReview(Long reviewId){
        Review foundReview = reviewService.findReview(reviewId);

        foundReview.setReviewCount(reviewLikeService.getReviewLikeCount(reviewId));

        return foundReview;
    }

    public Page<Review> getReviews(int page, int size) {
        return reviewService.findReviews(page-1, size);
    }

    public void deleteReview(Long reviewId, String email) {
        Member member = memberService.findLoginMemberByEmail(email);

        reviewService.deleteReview(reviewId, member.getMemberId());
    }

    public int getReviewLikeCount(Long reviewId) {
        return reviewLikeService.getReviewLikeCount(reviewId);
    }
}

reviewController에서 사용하기 위한 reviewCompositeService에는 ReviewService, ReviewLikeService, MemberService 총 3가지 서비스를 사용하는 로직이 필요로 하므로 3가지를 DI 받았다.

ReviewController

@RestController
@RequestMapping("/reviews")
@RequiredArgsConstructor
public class ReviewController {

    private final ReviewCompositeService compositeService;
    private final ReviewMapper mapper;
    @PostMapping
    public ResponseEntity postReview(@RequestBody ReviewDto.Post postDto, Principal principal) {
        Review creatingReview = mapper.reviewPostDtoToReview(postDto);
        Review createdReview = compositeService.createReview(creatingReview,principal.getName());

        return ResponseEntity.status(HttpStatus.CREATED).location(URI.create("/reviews")).body(mapper.reviewToReviewResponseDto(createdReview));
    }

    @PatchMapping("/{review-id}")
    public ResponseEntity patchReview(@PathVariable("review-id") @Positive Long reviewId,
                                      @RequestBody ReviewDto.Patch patchDto, Principal principal) {
        patchDto.setId(reviewId);

        Review updatingReview = mapper.reviewPatchDtoToReview(patchDto);
        Review updatedReview = compositeService.updateReview(updatingReview, principal.getName());
        ReviewDto.Response response = mapper.reviewToReviewResponseDto(updatedReview);

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping("/{review-id}")
    public ResponseEntity getReview(@PathVariable("review-id") @Positive Long reviewId) {
        Review review = compositeService.getReview(reviewId);
        ReviewDto.Response response = mapper.reviewToReviewResponseDto(review);

        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getReviews(@Positive @RequestParam int page,
                                     @Positive @RequestParam int size) {
        Page<Review> pageReviews = compositeService.getReviews(page, size);
        List<Review> reviews = pageReviews.getContent();

        MultiResponseDto response = new MultiResponseDto(mapper.reviewsToReviewResponseDto(reviews), pageReviews);

        return ResponseEntity.status(HttpStatus.OK).body(response);
    }

    @DeleteMapping("/{review-id}")
    public ResponseEntity deleteReview(@PathVariable("review-id") @Positive Long reviewId, Principal principal) {
        compositeService.deleteReview(reviewId, principal.getName());

        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }

    @GetMapping("/{review-id}/likeCount")
    public ResponseEntity<Integer> getReviewLikeCount(@PathVariable("review-id") Long reviewId) {
        int likeCount = compositeService.getReviewLikeCount(reviewId);

        return new ResponseEntity<>(likeCount, HttpStatus.OK);
    }
}

reviewController에서는 이제 여러 Service를 DI 받을 필요없이 reviewCompositeService만을 DI 받고 있고, Controller단에는 비즈니스 로직이 더 이상 존재하지 않아도 된다.

ReviewLikeCompositeService

@RequiredArgsConstructor
public class ReviewLikeCompositeService {
    private final ReviewLikeService reviewLikeService;
    private final MemberService memberService;

    public ReviewLike doReviewLike(ReviewLike creatingReviewLike, String email) {
        Member member = memberService.findLoginMemberByEmail(email);

        creatingReviewLike.setMember(member);
        Long reviewId = creatingReviewLike.getReview().getId();

        return reviewLikeService.findByMemberIdAndReviewId(member.getMemberId(), reviewId);
    }
}

ReviewLikeController

@RestController
@RequestMapping("/reviewLikes")
@RequiredArgsConstructor
public class ReviewLikeController {

    private final ReviewLikeCompositeService compositeService;
    private final ReviewLikeMapper mapper;

    @PostMapping
    public ResponseEntity doReviewLike(@RequestBody ReviewLikeDto reviewLikeDto, Principal principal) {
        ReviewLike reviewLike = mapper.reviewLikeDtoToReviewLike(reviewLikeDto);
        ReviewLike response = compositeService.doReviewLike(reviewLike, principal.getName());
        if(response.equals(null)) {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else
            return new ResponseEntity<>(mapper.ReviewLikeToReviewLikeResponseDto(response), HttpStatus.CREATED);
        }
    }
}

위의 review와 마찬가지로 reviewLike도 구조를 리팩토링했다.

다른 피드백으로 현재 application.yml 파일을 하나만 운영함으로서 매번 secretkey를 없애고 github에 pr해야하고, test 코드에서도 security 코드때문에 짜는 데에 불편함이 있었다.
application-deploy.yml, application-local.yml 이런 식으로 분리하여 운영한다면 훨씬 편리할 것이고, 현업에서도 이런 식으로 사용한다고 멘토분께서 말씀해주셔서 앞으로 우리 프로젝트에서도 이렇게 yml파일을 분리하여 운영하기로 했다 :)

profile
백엔드 개발자 김창하입니다 🙇‍♂️

0개의 댓글