본문 바로가기

개발/알파벳-다시만들기

[알파벳] 뱃지 도메인 1차 리팩토링 마일스톤을 끝내며...

반응형

작업 후기를 쓰고 싶은데... 먼가 혼자 주르륵 쓰기 힘들어서 친구랑 대화하는 형식으로 한명 불러오겠습니다...

루나야 도와줘~(동물의 숲에 나오는 토끼 친구입니다...ㅎ)

 

루나🐰 : 뱃지 서비스가 머야?
나 : 뱃지는 학생 뱃지와 랭킹 뱃지 두가지가 있어

나 : 학생 뱃지는 동아리 친구들만 사용하고, 랭킹 뱃지는 코드포스 유저 누구나 사용할 수 있게 계획했어,

      아직 누구에게만 주고 다른 사람은 막는 기능은 없지만...ㅠㅠ

 

루나🐰 : 그래서 뱃지 서비스는 어떻게 돌아가지..?

나 : 유저가 필요한 정보를 담아서 요청하면 뱃지 정보를 전달해줘!, e.g. GET /badge?name=mark

루나🐰 : 그게 끝이야?

나 : 맞아... 특별한 권한없이 누구나..! 사용할 수 있어(학생 뱃지는 안돼구...ㅎ)

 

루나🐰 : 이번에 고쳤다는건 무슨 뱃지야??

나 : 랭킹 뱃지를 고쳤어!

 

루나🐰 : 잘 작동하고 있었는데 멀쩡한걸 뜯은거야?!

나 : 처음 랭킹 뱃지를 만들었을때는 랭킹 정보가 있는 사이트면 전부 뱃지를 제작할 수 있게 생각했거든

나 : 근데 이게 처음에 너무 복잡하게 만들어서 손을 댈수가 없더라구

나 : 뭐하나 고치려면 중국집가서 짜장면 먹다가 부족해서 군만두 시키니까 2시간 걸리는..? 일이 생기더라구(예시가 맞나?!)

 

루나🐰 : 어쩌다 그렇게 복잡하게 구현한거지..? 그냥 처음부터 ""했으면 되잖아~

나 : "처음부터 완벽"한게 정말 말도 안돼게 힘들더라구

나 : 우선 그때가 거의 2년전이거든? 자바라는 언어도 잘 몰랐고, 스프링도 처음이다보니 헤멘 일이 많았어, 전문용어로 "삽질"했다!

나 : 또 막상 구현해야하는 기능이 코드포스 랭킹 뱃지 "하나"다보니 "확장"을 고려하지 않게 돼더라구

나 : 쉽게말해 완벽하게 만들 능력도, 동기도 부족했던거지

 

루나🐰 : 그래서 문제는 뭐였어? 어떤걸 고치고 싶었던거야?

나 : 일단 뱃지 도메인을 분리하고 싶었어, 기존에는 인덱스라는 온갖 "도메인 비빔밥"에 함께 있었거든!

나: 심지어 컨트롤러 레이어에 뱃지를 만드는 구현 코드가 있었어!!

나 : 이건 인덱스 컨트롤러의 소스코드 히스토리거든 방향키 왼쪽을 누르면 맨 뒤에 "비빔밥"을 확인할 수 있어!(심지어 여기는 아직도😭)

 

루나🐰 : 와... 컨트롤러 하나가 500줄이 넘네... 코드에 늘어진 문자열 저거는 도대체 머야?

나 : 뱃지를 그려주는 코드야 우리는 뱃지 그림을 소스코드로 제어하고 있었거든

루나🐰 : 그림을 코드로 관리하다니... 미쳤어~?

나 : 도입 당시에는 단점보다 장점이 많다고 생각했어

나 : 일단 개발자이다보니 읽을만하다는 점, 그림에 있는 특정한 문자는 유저 닉네임으로 대체했어야 했거든

나 : 이런 작업이 코드로 가능하다보니 편하더라구, 진짜 그림이었더라면 그려야 했겠지..?

나 : 일종의 히스토리를 남기는것도 편했던것 같아

나 : 그림의 특정 부분을 바꾸는 경우, 코드로 관리하면 어떤 "라인"에 변경이 있는지 확인 가능했거든

나 : 진짜 그림이라면 "틀린 그림찾기"가 되었을껄..?😎

 

루나🐰 : 근데 뱃지 구현 너무 긴데 가독성을 헤치면서까지 그런 방식을 고집해야했어??

나 : 이게 가슴 아픈점중 하나였지, 당시에는 구현에 급급하다보니 가독성을 그냥 "포기"하게 된것 같아...

나 : 하지만..! 이번 리팩토링에서 방법을 짜냈지!!

루나🐰 : 어떤 방법을?

나 : 구현부를 파일로 추출했어, 그림을 코드로 구현한 장점은 모두 챙기면서 가독성을 크게 헤치는 단점은 가린거지!!

루나🐰 : 흠... 복잡한 내용을 한번 감싸서 읽을만하게 만들다니 단점을 설계로 극복했구나!

나 : 맞아 앞으로도 이런 방식은 자주 나오는 방법이야

나 : 트레이드 오프라는 말 많이들 하잖아 사실 나는 두마리 토끼를 잡을 수 있다고 생각하거든

나 : 그러려고 공부하는거지 하하...😆😆😆

 

루나🐰 : 그래서 다음으로 고친건 뭔데?

나 : 레이어 아키텍쳐를 헤치는 부분을 고쳤어, 컨트롤러에 존재하는 로직을 서비스 레이어로 하나 내리는 작업을 했지

루나🐰 : 그냥 컨트롤러에 로직이 있으면 안돼?

나 : 음.. 우선 그대로 두면 가독성이 너무 떨어지는 것같아 컨트롤러는 유저의 요청을 받는 역할이잖아

나 : 뱃지 로직까지 처리하면 역할이 커지는 문제가 생기는거지

나 : 또 우리는 스프링을 사용하고 있었는데 이 친구의 장점이 개발자에게 어느정도 "사용 패턴"을 강제한다같거든

나 : 어노테이션 붙히면서 애써 지키려는걸 정면으로 위반하는? 느낌인거지~

루나🐰 : 그럼 왜 레이어 하나만 내린거야? 어차피 그런건 도메인에 있어야하는 로직이잖아?

나 : 한번에 뒤엎는건 역시 너무 어렵더라구...🤔🤔
나 : 문제를 쪼개서 깨보려고 그렇게 접근했어🛠

 

루나🐰 : 서비스 레이어 리팩토링은 어떻게 진행했어?

나 : 서비스에서도 도메인으로 분리할만한 내용을 분리했어

나 : 다만 이번에는 "확장"을 고려해서 조금 기술이 들어갔지...😎😎

루나🐰 : 오... 드디어 다른 랭킹 사이트를 염두해둔 구현을..?

나 : 맞아 뱃지를 만들여면 랭킹 정보를 웹사이트에서 가져와야 하거든

나 : "외부 랭킹 사이트와의 접점에 인터페이스"를 하나 뚫었어, 외부 인프라를 한번 감싼거지

루나🐰 : 그 유명한 "헥사고날 아키텍쳐"?!?!

나 : 그치.. 어뎁터, 어플리케이션, 도메인 구조를 적용해서 더이상 직접 외부 사이트에서 요청을 받지 않아

나 : 대신에 인터페이스를 이용해서 레이팅 정보를 받아오지, 이 인터페이스 구현체는 어뎁터에 위치해

나 : 서비스 레이어에서는 각각의 웹사이트별로 어떻게 응답을 받아오는지에 대한 구현은 전혀 몰라!

    package uhs.alphabet.badge.domain; // 도메인은 외부에 어떤 의존성도 가지지 않는다
    public class RankedBadge {
        // 구현
    }

[나 : import문을 살펴보면서 의존성을 끝어냈지... 아이고 눈이야~]

 

루나🐰 : 들을땐 그럴싸한데... 이게 가능해?

루나🐰 : 뱃지 만들어달라는 요청이 왔을때 A라는 랭킹 사이트와 B라는 랭킹 사이트 어디에서 랭킹정보를 가져와야 하는지 어떻게 구분해?

나 : 컨트롤러에서 요청이 들어오면 요청 자체에 어디 랭킹 정보에 대한 요청인지 인식표를 박아!

나 : 이후 서비스에서는 요청에 박힌 인식표를 Map의 키로 사용해서 알맞는 어뎁터를 가져오는거지 🍊🍋

 

루나🐰 : 도메인으로 로직도 이동했고, 인프라도 감싸서 확장성도 챙겼으면 이제 리팩토링은 끝난거야?

나 : 이제 기왕 건드린김에 랭킹 뱃지 도메인의 테스트도 손봐야지!

루나🐰 : 테스트를? 여기는 그냥 돌아가는지만 테스트하면 되는거 아니야? 잘 돌아가는데 뭘 고쳐..?

나 : 음.. 우선 테스트가 너무 느렸던것 같아 의존성을 필드 인젝션했거든

나 : 그래서 스프링 컨텍스트를 테스트마다 올려야해서 단위테스트가 너무 느리더라고

나 : 테스트 용이하게 전부 생성자 주입으로 변경했고, 여러가지 "테스트 더블"을 이용해서 다양하게 테스트 했어

루나🐰 : 테스트 더블은 어떻게 이용했어?

나 : 음...🤔🤔 우선 스파이라는 친구가 있거든 애는 실행되었는지 안돼었는지 플래그를 갖고 있어

나 : 서비스 클래스를 테스트한다 하면 레포지토리 스파이를 통해 서비스가 실제로 레포지토리를 실행시켰는지 테스트했어!

나 : 그 외에도 더미나 스텁등으로 테스트할 친구들을 편하게 생성한것 같아!

나 : 사실 대부분은 하나의 테스트에서 하나만 테스트하게 변경한거지만..😅😅

(여러개 있으니까 그냥 복잡하더라고, 테스트가 쉬워야 테스트지..ㅋㅋ)

 

루나🐰 : 소문으로는 그런거 직접 만들었다는데... 왜 모키토 안쓰고 직접 만들었죠?!

나 : 하... 이게 나도 처음에는 그냥 라이브러리 써서 편해지고 싶었거든

나 : 우리는 랭킹 웹사이트를 빈으로 만들어서 List<RankingSite> 이런식으로 서비스 클래스에서 주입받고 있었어

나 : @PostConstruct 어노테이션을 이용해서 프라이빗한 init메소드에서 리스트의 내용을 Map에 옮겨주는 작업을 하고 있었거든

나 : 그런데 @PostConstruct 이거는 빈으로 등록된 이후에 동작하는거라 초기화 메소드가 안돌아가더라구..

나 : 그래서 초기화 메소드의 접근 제어자를 패키지 프라이빗으로 상향시켰지... 캡슐화를 깨는거라 눈물났어😭😭

루나🐰 : 다들 그렇게 하고 그냥 final 박잖아~ 스파이 직접 만든 이유나 말해줘~

나 : 앗..! 그건 그냥 별거 없어

나 : 랭킹 사이트 객체를 mock()해서 넣으니까 List에서 Map으로 변환할때 그냥 null이 들어가더라고

나 : 원래 만드는거 좋아해서 그냥 랭킹 사이트 인터페이스를 구현한 테스트 더블을 만든거지...(최근에 필터체인도 만들어 봤는데..ㅎㅎ😎)

    @PostConstruct
    final void init() {
        webSiteMap = webSiteList.stream().collect(Collectors.toMap(
            RankWebSite::getFrom,
            webSite -> webSite,
            (oldSite, newSite) -> newSite
        ));

        badgeFileMap = badgeFileList.stream().collect(Collectors.toMap(
            RankedBadgeFile::getFrom,
            file -> file,
            (oldFile, newFile) -> newFile
        ));
    }

[나 : 이렇게 List에 담긴 친구들을 Map으로 옮겼어]

 

루나🐰 : 마지막으로 서비스랑 도메인에 시를 썼다는 말이 있는데 이건 무슨 일이야?

우리의 목표!

나 : "읽을만한 코드"가 우리 리팩토링의 최우선 목표였잖아

나 : 시작할때는 코드매트릭스 기준 뱃지 도메인 복잡도 점수 10점 이하가 목표였거든

나 : 근데 하면 할수록 배우는게 늘어가더라고, 멘토님께서 추천해주신 "클린 코드"라는 책에서도 그 기준을 찾아봤어

루나🐰 : 간단히 말해 "책 읽듯이 읽히는 수준"까지 했다 이거지?

나 : 맞아..ㅋㅋ 정확히는 도메인의 퍼블릭 메소드까지는 "비개발자가 읽어도 이해되는 수준"으로 만들었지

루나🐰 : 새로운 기능이 추가되는것도 아니고 왜 그렇게 했어? 시간이 남아 돌아??

나 : 우리가 지금 개발하는건 오픈소스잖아 학교 동아리 웹사이트기도 하고

나 : 누구나 개발에 참여할 수 있고, 학교가 있는 한 계속 유지되야하는 특징이 있거든

나 : 그래서 높은 수준의 가독성을 챙기려고 노력했어(외계어 적혀 있으면 거기에 기여 못하지...)

나 : 하면서 느낀건데 코드 퀄리티가 높으니까 기능 추가도 수월해졌고,

나 : 테스트 짜기도 쉬워지고, 테스트 많으니까 변경하기도 두렵지 않고, 선순환이 이어지더라구

 

루나🐰 : 마지막으로 그렇게 아름답다고 난리치던 코드~

    public String requestRankedBadge(final RankedBadgeRequest request) {
        RankWebSite webSite = getWebSite(request);
        RankedBadgeFile rankedBadgeFile = getRankedBadgeFile(request);
        RankedBadgeData badgeData = RankedBadgeData.of(request, webSite);
        return RankedBadge.getBadge(rankedBadgeFile, badgeData);
    }

 

나 : 처음에 무지 길었는데 감격스러워...😆🥳🤩

 

루나🐰 : 소감도 한번 말해줘!

나 : 이번에 리팩토링을 진행하면서 여러가지로 느낀점이 많은데 이 글을 통해 다들 나와 같은 감정을 공유했으면 좋겠어...

나 : 조잡해서 글에는 담지 못한 여러가지 내용들도 있어(토스팀의 테스트 커버리지 100%에 영감받은..ㅋㅋ)

나 : 도메인으로 나눈 부분을 레이어드 아키텍쳐로 끌고갈지 풀지못한 고민도 있어

(현재는 뱃지 밑에 어뎁터,어플리케이션,도메인 바꾼다면 어뎁터 밑에 뱃지, 어플리케이션 밑에 뱃지...)

나 : 부하를 부어보고나 이런 재밌는것도 못해봐서 아쉬운점도 있어

나 : 그래도 꾸준히 이어가는 서비스인만큼 앞으로도 어떻게 문제를 해결해가는지 응원해주세요!!🔥🔥🔥

 

소중한 시간에 긴 글 읽어주셔서 감사합니다🙇🏻‍♂️

반응형