본문 바로가기
우아한테크코스 4기/레벨4

리팩터링(1) - 필요성과 계획 수립

by 나는후니 2022. 9. 24.
리팩터링(2) - 문제 해결하기
리팩터링(3) - 하다보니 보이는 것들

 

안녕하세요.

이번 시리즈는 우아한테크코스 쿠폰 문화를 관리하는 땡쿠의 코드를 리팩터링하며 겪은 경험을 정리하고자 작성하였습니다.

 

땡쿠 서비스는 쿠폰을 주고 받고, 예약을 요청하고, 예약을 승인하면 만남이 생성되는 주요 흐름이 있고 부가적으로 재미요소인 마음 보내기라는 기능이 있습니다.

모든 상호작용 과정에는 알람이 생성되고, 우아한테크코스 슬랙 그룹으로 알람을 전송하게됩니다.

그 중 저는 기존의 알람서비스를 리팩터링하였는데요.

이번 포스팅은 리팩터링의 필요성과 계획 수립에 대해 작성해보겠습니다.

필요성과 코드 이해

사실 가장 먼저 물음을 가져야 할 부분은 리팩터링하는 이유였습니다. 이미 서비스에 배포도 되어있고 문제 없이 잘 굴러가고 있었기 때문입니다. 빠르게 새로운 기능을 덧붙이고 발전하는 과정에서 리팩터링은 잠깐 미뤄둬도 괜찮지 않을까? 하는 생각을 가진 사람도 있을 것입니다.

하지만 저는 두 가지 이유로 리팩터링의 필요성을 느꼈습니다.

추가 기능에 알람이 적용될 때

마음 도메인을 개발하는 과정에서 기존에 존재하는 알람 서비스를 이용해야 했습니다. 하지만 다른 코드들을 분석해보니 위 그림과 같은 형태였습니다.

  1. 각 도메인에서 알람의 전송 대상을 각각 찾는다.
  2. 각 도메인에서 알람 메시지를 만든다.
  3. 각 도메인의 application layer에서 알람을 호출한다.
// ReservationService
private void sendMessage(final Member member, final Coupon coupon, final Reservation reservation) {
  // (1)
  Member sender = getMember(coupon.getSenderId());

  // (2)
  Message message = ReservationMessage.of(member.getName(), 
                                          sender.getEmail(),
                                          reservation.getTimeUnit().getDate(), 
                                          coupon.getCouponContent());
  // (3)
  alarmSender.send(message);
}    

위 코드에서 볼 수 있다시피, 각 도메인에서 알람을 전송하기 위한 추가적인 로직을 꽤 많이 수행하고 있었습니다.

일단 기존 알람과 동일한 형태로 마음 도메인의 알람을 개발했으나,

(1) 코드를 볼 때 도메인 로직에 집중하기 힘들었고

(2) 알람 메시지를 리팩터링하기 위해 여러 도메인을 살펴야 하는 것이 번거로웠습니다.

(3) 또, application layer의 한 트랜잭션 내에서 호출하다 보니, 알람이 실패하면 기존 성공한 로직까지 롤백되는 문제점도 발생할 수 있겠다고 생각했습니다.

성능

슬랙으로 알람을 전송하려면, 슬랙에서 제공하는 user의 token값이 필요합니다. 하지만 저희는 Google Oauth를 이용하고 있었기 때문에 특정 워크스페이스의 token값을 DB에 저장하고 있을 수 없었습니다. 따라서 매번 알람이 발생하면 Slack workspace에 요청하여 모든 유저의 token을 가져왔고 email 주소로 token을 저장해두었습니다.

public class InMemorySlackUserRepository {

    private final Map<String, String> store;
    private final SlackClient slackClient;
    public String findUserToken(final String email) {
        String userToken = store.computeIfAbsent(email, slackClient::getUserToken);
        if (userToken == null) {
            throw new InvalidAlarmException(ErrorType.NOT_FOUND_SLACK_USER);
        }
        return userToken;
    }
}

하지만 문제는, 저희 서비스는 쿠폰을 여러명에게 전송할 수 있다는 것이죠.

만약 30명의 회원에게 동시에 쿠폰을 보낸다면, 알람을 위한 토큰을 저장하는 과정만 30회 진행되는 것입니다. 현재 woowacourse 워크 스페이스에는 328명의 사용자가 있는데 쿠폰을 보내기 위해 Http 요청을 보내고 328명의 json 응답 값으로 객체를 로딩하여 한 명씩 저장하는 과정이 큰 성능 저하 / 메모리 부족으로 이어졌습니다.

리팩터링 계획 수립

리팩터링을 하는 데 있어 가장 중요한 과정입니다. 필요성과 코드에 대한 이해를 마쳤으면 리팩터링을 계획합니다. 계획에 앞서 먼저 문제 상황을 재정의하는 시간을 가지면 좋습니다.

문제상황

  • 도메인 코드와 알람 코드가 혼재되어 있는 코드를 이해하는 것이 어렵다.
  • 메시지를 만드는 방식이 여러 도메인에 산발적으로 퍼져있다. (변경의 전파 알람 -> 도메인)
  • 알람을 위한 회원 토큰을 조회할 때 반복적으로 api 요청을 보내는 현상

문제 상황을 정의해보고 현재 개발 일정과 서비스의 규모 등 다양한 현실 상황을 고려하여 적절 수준의 리팩터링 계획을 세워봅니다. 저는 일정과 제 기술력을 중심으로 리팩터링 계획을 세웠습니다.

계획

  • 각 도메인 코드와 알람 코드를 도메인 이벤트 구조를 이용하여 분리한다.
  • 메시지를 만드는 코드를 한 데 모으고 전략패턴으로 관리한다.
  • 회원 토큰을 application이 최초 실행되는 시점에 1회 전체 로딩해서 캐싱한다.

추가적으로 마음 도메인을 개발할 때 알람에 대한 내용이 문서로 공유되지 않아 매번 최초에 개발한 크루와 소통하는 과정이 반복되었기 때문에 이 비용을 줄이기 위해 Wiki에 문서화 계획도 함께 세웠습니다.

정리

팀의 코드를 개선하는 것은 정말 어려운 일입니다. 이미 여러 도메인에서 사용되는 코드를 다른 기능을 지속적으로 개발함과 동시에 리팩터링하는 것은 달리는 기차의 바퀴를 갈아 끼는 것만큼 어렵다고 비유되기도 하죠. 그만큼 리팩터링의 계획 수립은 정말 중요하다고 생각합니다.

 

먼저 리팩터링의 필요성을 발견해야 하고, 그 필요성이 지금 당장 진행되어야 하는 것인가에 대한 고민도 있어야 합니다. 또, 다른 팀원들이 충분히 공감하고 이해할 수 있어야 하고 그러기 위해서는 문제상황에 대한 정의와 명확한 계획 수립이 선행되어야 하겠죠. (물론 저는 팀원들을 잘 만나서 ...)

 

다음 포스팅에서는 리팩터링 - 문제 해결하기라는 주제로 글을 적어보겠습니다.

감사합니다.