Service - 코드 리팩토링

jihan kong·2022년 9월 21일
0

JUnit5

목록 보기
18/25
post-thumbnail

본 시리즈는 메타 코딩님의 Junit 강의를 학습한 내용을 바탕으로 정리하였습니다.

저번 시간에 수정하던 BookService 를 다시 한번 살펴보자.

BookService

    // 2. 책 목록보기
    public List<BookRespDto> 책목록보기() {
        // 코드 수정
        List<BookRespDto> dtos = bookRepository.findAll().stream()
                .map((bookPS) -> new BookRespDto().toDto(bookPS))
                .collect(Collectors.toList());
        // (..생략)

        return dtos;
    }

위 코드를 수정하기 전의 코드는 다음과 같다.

BookService


    // 2. 책 목록보기
    public List<BookRespDto> 책목록보기() {
        // 코드 수정
        List<BookRespDto> dtos = bookRepository.findAll().stream()
                // .map((bookPS) -> new BookRespDto().toDto(bookPS))
                .map(new BookRespDto()::toDto())
                .collect(Collectors.toList());
		// (..생략)
    }

이렇게하면 원래는 map에서 toDto 를 참조함으로써 Book 이 들어올 수 있게 된다. 그런데 .map(new BookRespDto()::toDto()) 이 부분이 작동하지 않으면서 문제가 발생했던 것이다.

이쯤에서 map의 작동방식을 살펴보면 왜 이런 문제가 생겼는지 알 수 있을 것 같다.


Map의 작동원리

보는 것과 같이 map 메서드는 List 로부터 타입 변환되어 Stream 되고 있는 데이터를 한 건씩 가져와서 처리를 하는 방식이다.

.map(new BookRespDto()::toDto()) 이렇게 map에 참조문법 ( :: ) 이 들어가면 참조되는 메서드( toDto ) 가 map으로 넘어가게 된다.

위 그림과 같이 map 메서드가 실행이 되면 메서드 내부적으로 반복문이 돌면서 map 으로 넘어온 toDto . 즉, 참조한 toDto 메서드를 실행하게 되고


toDto 메서드는 bookRepository.findAll().stream() 을 실행한 결과로 Stream 되어지는 Book 을 받아와서 실행한 후, 가공된 dto 를 다음 그림과 같이 리턴하게 되는 것이다.


2. Stream의 두 번째 Book type

그렇다면 Stream 에 아직 남아있는 두 번째 타입이 들어오게 되면 어떻게 될까?
위의 그림처럼 같은 heap을 공유하게 된다. 더군다나 BookRespDto 에는 다른 생성자가 있는 것도 아니기 때문에 결국 완전히 똑같은 두 개의 dto 만이 생성되는 것이다.

결국 정리하면 new 를 통해 새로운 객체가 생성되는 것도 아니고 한 번 new 로 생성한 객체에 toDto() 메서드만 두 번 실행되기 때문에 동일한 dto 만 두 번 리턴되는 것이다. 그도 그럴 것이 map 은 매개변수로 객체를 한 번만 전달 받는다. 같은 객체에서 같은 메소드가 동작하니 당연히 똑같은 값이 나올 수 밖에 없다.


🛠️ 리팩토링

그렇다면 어떻게 수정하면 좋을까?
Dto 부터 손봐야할 것 같다.

기존의 this 를 리턴하던 코드를 다음과 같이 생성자를 추가하여 @Builder 패턴으로 바꾸자.


BookRespDto.java

package site.metacoding.junitproject.web.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import site.metacoding.junitproject.domain.Book;

@NoArgsConstructor
@Getter
public class BookRespDto {
    private Long id;
    private String title;
    private String author;

    @Builder
    public BookRespDto(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }
}

Book 도 다음과 같이 dtobuilder 를 받는 방식으로 수정해야한다.

Book.java

package site.metacoding.junitproject.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import site.metacoding.junitproject.web.dto.BookRespDto;

@NoArgsConstructor
@Getter
@Entity
public class Book {

 	// ..(생략)

    public BookRespDto toDto() {
        return BookRespDto.builder()
            .id(id)
            .title(title)
            .author(author)
            .build();
    }
}

Service 책 목록보기 (최종)

지금까지 작업한 리팩토링을 토대로 우리는 책 목록보기 메서드를 다음과 같이 만들수 있다.

    // 2. 책 목록보기
    public List<BookRespDto> 책목록보기() {
        List<BookRespDto> dtos = bookRepository.findAll().stream()
                .map(Book::toDto)
                .collect(Collectors.toList());
        return dtos;
    }

기존의 new 를 통해 BookRespDto() 를 생성하고 toDto 를 참조할 바에 어짜피 findAll() 을 하게되면 stream에 Book 이 담기게 될테고 map 입장에서는 Book 을 한 건씩 꺼내오면 되는 것이기 때문에 아예 Book 타입을 넣는 것이 심플하다.

물론 저번 포스팅에서 수정했던 것처럼
.map((bookPS) -> new BookRespDto().toDto(bookPS))
이렇게 처리해도 상관은 없다. 하지만 꺼내오는 것을 타입으로 받아서 메서드로 처리하는 것이 훨씬 깔끔하게 처리할 수 있다는 것이다.


테스트도 잘 동작하는 모습.

profile
학습하며 도전하는 것을 즐기는 개발자

0개의 댓글