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

리팩터링(2) - 문제 해결하기

by 나는후니 2022. 9. 24.

지난 글에서 리팩터링의 필요성을 직접 느끼고 문제 상황을 정의한 경험을 공유했습니다.

이번 글에서는 문제를 어떻게 해결해나갔는지에 대해 작성해보겠습니다.

1. 각 도메인 코드와 알람 코드 분리

리팩터링하기 이전의 코드도 의존관계는 단방향으로 잘 흐르고 있었습니다. 각 도메인에서 알람을 참조하는 형태로 이뤄졌고, 알람은 외부 도메인에 대한 정보가 전혀 없었습니다. 덕분에 의존성에 대한 문제는 전혀 존재하지 않았습니다. 다만 각 도메인에서 알람을 전송하기 위한 코드들이 많이 있었고 이를 도메인에서 떼어내기 위해 여러 고민을 했고 최종적으로 도메인 이벤트라는 방법을 선택하게 됐습니다. 도메인 이벤트를 사용하면 다음과 같은 장점이 있었습니다.

  1. 절차지향의 코드를 객체지향 코드로 전환할 수 있다.
  2. 외부 패키지와의 의존성을 완벽히 끊어낼 수 있다.
  3. 트랜잭션 관리, 비동기 등 다양한 기능을 제공하여 원하는 대로 설정할 수 있다.

덕분에 application layer에서 절차적으로 작성되었던 코드는 사라지고 아래와 같이 도메인에서 알람 이벤트를 발행할 수 있게 됐습니다.

그리고 AlarmEventListener가 발행한 이벤트를 읽어 알람을 전송하는 역할을 맡게 됐습니다.

이 때, 트랜잭션 커밋 이후에 발생하는 이벤트로 분리하여 기존 트랜잭션에는 영향을 주지 않게끔 만들 수 있었습니다.

이 전환이 리팩터링의 첫 단계였는데, 모든 내용을 한 번에 이벤트로 전환하게 되면 팀원들이 이벤트를 이해하기에도, 코드리뷰를 하기에도 어려울 것이라 판단하여 Coupon 전송에만 알람 이벤트를 적용하고 먼저 리뷰를 받았습니다. (그럼에도 불구하고 pr의 크기가 컸습니다 ㅠㅠ)

참고 : refactor: 쿠폰 전송에 대한 알람 기능을 도메인 이벤트로 발행한다

최초에 일부만 변경하고 팀원들에게 리뷰를 받으니 저도 제 코드에 대해 다시 한 번 고민해볼 수 있었고 팀원들의 좋은 피드백을 받아 코드를 개선할 수 있었습니다.

2. 메시지를 만드는 폼을 전략패턴으로 관리

기존에는 각 알람을 만드는 패키지에 메시지 폼을 만드는 객체가 있었습니다. 이 부분도 잘 캡슐화가 되어있어서 크게 문제가 되지는 않았지만 각 도메인에서 어떤 메시지를 사용해야 할 지 선택하고, 리팩터링해야 하는 부분이 조금 불편할 수 있겠다는 생각이 들었습니다. 사용자(Alarm에 요청을 보내는 도메인) 입장에서 어떻게 하면 편리함을 유지할 수 있을까라는 관점으로 다가가 문제를 해결하고자 하였습니다. 결론적으로 알람은 대상과 원하는 내용만 전달받으면 되고 메시지를 만들어주는 것은 Type만 전달 받으면 정해진 format에 따라 알람에서 만들어주는 방향으로 생각했습니다. 덕분에 다음과 같은 이점을 얻을 수 있었습니다.

  1. Alarm을 사용하는 도메인은 메시지를 만들기 위해 고민하지 않아도 된다.
  2. 각 도메인에 산발적으로 퍼진 메시지 포맷을 알람 패키지로 이동시킬 수 있다.
  3. 나중에 다양한 포맷이 생겨 DB에 폼을 저장해두어도 가져와서 사용하기 편한 형태가 된다.

이제 위 이미지처럼 한 지점에서 관리할 수 있게 됐습니다. 각 전략들이 명확하게 하나의 책임을 가졌다는 것과, 공통 분모를 부모 전략클래스로 분리할 수 있다는 장점을 느꼈습니다. 분명 트레이드 오프가 있지만, 현재 상황과 미래(Form을 위한 데이터가 생길 경우)를 대비했을 때 기존보다 나은 방식이 될 수 있다고 판단하여 리팩터링 했습니다.

참고 : refactor: 전체 프로젝트에 알람 이벤트를 적용한다. #376

3. 회원 토큰 캐싱

매 요청마다 382명의 회원을 외부 api로 조회하고, 한명씩 자료구조에 저장해두는 방식은 성능, 메모리 이슈로 운영에 배포할 수 없었습니다. 따라서 spring-boot-starter-cache의존성을 추가하여 ConcurrentCacheManager로 최초 스프링 실행 시에 전체 데이터를 캐시에 넣어두도록 수정하였습니다.

이 때, ApplicationListener를 이용하여 Spring Boot가 실행될 때 단 한 번 실행되는 ApplicationReadyEvent를 기준으로 모든 슬랙 유저정보를 캐시에 담아두도록 하였습니다.

만약 cache-hit일 경우 캐시에서 값을 가져오고 cache-miss의 경우에는 슬랙에서 한 번 살펴보는 방식(Look aside)을 선택하였습니다.

참고 : refactor: 슬랙 유저 조회시 캐시를 적용하고, 알람 채널을 변경한다. #380

4. Wiki에 공유

결국 이 리팩터링은 알람 서비스를 사용할 땡쿠 팀 개발자를 위한 작업이었습니다. 고로 이 api를 잘 사용할 수 있도록 정리해두어야 다음에 다른 팀원이 알람 서비스를 만들 때, 소통하는 비용이 줄어들 것이라고 생각했습니다.

그래서 저는 도메인 개발 후 Alarm 적용 방법에 대해 간단하게 위키에 정리해두었습니다. 아직까지 알람 서비스를 추가적으로 개발한 팀원은 없지만 알람 서비스를 리팩터링한 제가 아닌 다른 팀원들이 알람을 리팩터링 & 사용할 때 큰 어려움 없이 사용할 수 있을 것으로 기대됩니다.

정리

리팩터링을 하면서, 내가 리팩터링하는게 과연 더 좋은 방법일까? 를 많이 고민했던 것 같습니다. 때로는 그 고민이 리팩터링하기 두렵다는 마음으로 이어지기도 합니다. 하지만 리팩터링을 하다보면 느끼는 부분들이 많습니다. 저는 이런 느낌을 받았던 것 같아요.

  1. 지금까지 내가 코드를 잘 짜고 있던게 아닐지도 모르겠다.
  2. 이렇게 코드에 대해 진지하게 고민할 수 있을까?
  3. 리팩터링을 하다 보니까 조금 더 개선할 수 있겠다는 생각이 드네
  4. 아, 테스트 코드 잘못 짰다..

자신에게 조금 부끄러운 감정들을 느꼈는데요. 그런데 지금 생각해보니 이런 감정들 덕분에 더 성장할 수 있었고, 리팩터링을 할 수 있다는 용기와 애초부터 코드 작성에 더 많은 고민을 하게되는 힘이 생긴 것 같네요.

 

다음 포스팅에서는 리팩터링 - 하다보니 보이는 것들 에 대해 작성해보겠습니다.

감사합니다.