04-12 07:37
Notice
Recent Posts
관리 메뉴

독산구너

리팩토링 본문

카테고리 없음

리팩토링

독산구너 2023. 10. 24. 18:17

1. vo와 entity, dto 구분

기존

id (식별자) 없는 entity를 사용하였다.

이로인해 어떤 필드가 정의되어 있는지, 어디서 수정되었는지, 어디서 생성되었는지 알 수 없는 문제가 발생한다.

위 코드는 youtube 로부터 VideoVo 객체를 받아 video (entity)를 리턴하는 메서드이다. db에 저장하기 전까지 videoId 가 없으므로 videoId는 정의하지 않고 다른 필드를 채웠다.

 

리팩토링

식별자 (id) 가 없이 모든 속성값이 같으면 같은 객체인 VO (value object) 를 생성했다. Video, Playlist, Quiz, Summary, Course, CourseVideo 등의 vo가 생성되었으며, 이를 entity로 바꾸거나 entity에서 vo로 매핑하는 과정은 service 또는 entity 메서드에서 이루어진다.

 

따라서 기존 Video, Playlist, Course, CourseVideo, Lecture 등은 모두 VideoEntity, PlaylistEntity, CourseEntity, LectureEntity .. 가 되고, id 값이 존재하지 않는 vo가 생성되었다.

 

course vo

이때, vo와 entity에는 책임을 부여하여 로직을 수행하게 하였으며, 계층간 데이터 전송을 위한 dto에는 getter, setter만을 허용하였다.

 

2. YoutubeApi 객체를 다른 Service 객체로부터 분리

youtube data api를 통해 받아오는 데이터를 VideoDto, PlaylistDto로 매핑하여 사용하고 있다.

youtube 로부터 video, playlist 정보를 받아오는 방법과 데이터 형태는 변할 수 있다. (카프카 등을 사용하게 될 수도, 또는 youtube data의 형태가 바뀔수도)

이 방법과 데이터 형태가 바뀌더라도, 다른 service 클래스의 코드 수정은 없어야 한다.

 

기존

다음은 검색 조건을 받아 검색 결과를 반환하는 LectureService의 searchByKeyword 메서드이다.

여기서 youtubeApi를 직접 사용하고 있고, youtube data api 가 전달하는 데이터 형태인 SearchVo를 직접 사용하고 있다.

YoutubeApi를 직접 사용하는 Service 클래스들

리팩토링

youtube에서 받아오는 데이터의 형태가 바뀌거나 받아오는 방식이 바뀌더라도 이를 사용하는 service계층의 코드 수정은 없어야 한다.

 

따라서 로직을 수행하고 사용 가능한 Vo로 매핑하는 객체가 필요하며, 이를 YoutubeService로 정의하였다.

@Service
public class YoutubeServiceV2 {

    private final YoutubeApiV2 youtubeApi;

    public YoutubeServiceV2(YoutubeApiV2 youtubeApi) {
        this.youtubeApi = youtubeApi;
    }

    public Playlist getPlaylist(String code) {
        PlaylistDto playlistVo = youtubeApi.getPlaylistDto(PlaylistReq.builder()
                .playlistCode(code)
                .build());

        return playlistVo.toPlaylist();
    }

    public Video getVideo(String code) {
        VideoDto videoVo = youtubeApi.getVideoDto(VideoReq.builder()
                .videoCode(code)
                .build());

        return videoVo.toVideo();
    }

    public PlaylistItemInfo getPlaylistItemInfo(String code, String nextPageToken, int limit) {
        PlaylistVideoDto playlistVideoVo = youtubeApi.getPlaylistVideoDto(PlaylistItemReq.builder()
                .playlistCode(code)
                .nextPageToken(nextPageToken)
                .limit(limit)
                .build());

        return playlistVideoVo.toPlaylistItemInfo();
    }

    public SearchInfo getSearchInfo(String keyword, String nextPageToken, int limit, String filter) {
        SearchDto searchVo = youtubeApi.getSearchDto(SearchReq.builder()
                .keyword(URLEncoder.encode(keyword, StandardCharsets.UTF_8))
                .filter(filter)
                .limit(limit)
                .pageToken(nextPageToken)
                .build());

        return searchVo.toSearchInfo();
    }
}

이 객체에서는 YoutubeApi를 사용하여 이를 Video, Playlist, SearchInfo 등의 Vo로 매핑한다. 다른 Service 계층에서는 이 Vo를 사용하게 된다.

만약 youtube 로부터 데이터를 가져오는 방식이 바뀌거나, 데이터의 형태가 바뀌게 되면 이 YoutubeService 코드만 수정하게 된다.

나름 확장성 증가

 

바뀐 다이어그램은 다음과 같다. (추가적으로 video, playlist 객체를 제공하는 VideoService, PlaylistService 클래스를 생성했다.

 

3. 다형성 활용

강의 등록에서는 playlist, video 두 형태의 컨텐츠 등록이 가능하다. 또한, quiz에서는 주관식, 객관식, TF 가 있다.

기존 코드에서는 이 케이스별로 다른 코드를 작성하였는데, 이로인해 코드의 복잡성이 커지고 Service 계층에서의 책임이 집중되게 된다.

 

다음은 playlist 를 통해 코스등록하는 메서드, video를 통해 코스등록하는 메서드이다.

이때의 문제점은, 같은 코드가 반복되고 변경에 취약하게 된다는 점이다.

 

리팩토링

Content(추상클래스), Video, Playlist, PlaylistWithItem(재생목록에 포함되는 video 포함), PlaylistItem(video 상속, 재생목록의 index 속성 포함) 클래스를 만들었으며, Content 전달을 통해 강의 등록이 가능하게 되었다.

바뀐 코드는 다음과 같다.

강의 검색에서 영상/재생목록의 상세정보를 받아오는 getContentDetail에서도 Content 추상 클래스 전달을 통해 하나의 메서드로 합칠 수 있었다.

 

Quiz

quiz 클래스도 추상클래스로 설정하여 주관식, 객관식 TF문제로 구체화 하게 하였다.

다음과 같이 Quiz 클래스 타입으로 getQuiz 리턴값을 설정할 수 있었다.

 

4. 책임 주도 설계

기존에는 service 계층에서 모든 로직을 순차적으로 수행했다. 이는 객체지향적으로 개발하지 않고 하나의 Service에서 너무 많은 책임을 가지는 문제점을 만들었다. 이로인해 service의 코드길이가 600줄이 넘기도 했다.

 

기존

controller에서 전달한 메시지를 수행하기 위한 모든 로직을 하나의 service에서 수행하였고, 다음과 같이 복잡한 코드가 발생했다.

다음은 강의자료를 리턴하는 getMaterials 메서드에서 퀴즈 관련 책임까지 가져 길이가 길어진 예시이다.

다음은 코스등록을 위해 playlist 를 불러오는 책임을 CourseService에게 부여하여 코드가 복잡하게 길어진 예시이다.

 

리팩토링

quiz, playlist, video 관련 로직을 수행하는 QuizService, PlaylistService, VideoService 클래스를 만들었으며, 다른 Vo 에게 책임을 부과하기도 하였다.

 

다음은 강의 등록 조건 (일정관리 유무, 주차, 일평균 목표학습시간 등) 객체가 직접 Course와 CourseVideo 객체를 생성하는 책임을 부여받은 모습이다. (기존 CourseService 책임)

다음은 Course 객체가 영상 순서가 바뀌거나 영상이 추가되거나 삭제되었을 때, 코스 영상의 주차를 재설정하는 reschedule 하는 책임을 부여받은 모습이다. (기존 CourseService 책임)

다음은 CourseVideo가 영상 시청 상태를 리턴하는 책임을 부여받은 모습이다 (기존 materialService 책임)

 

 

그리고 공통적으로, 반복적으로 사용되는 메서드는 Helper 클래스를 만들어 책임을 부과하였다.