DEV

여기를 눌러보세요!

Published on

Axios 싱글톤 패턴 적용기

Authors
  • avatar
    Name
    Charles

[코드잇] 프론트엔드 단기심화 3기 과정 팀 프로젝트에서 API 호출 방식을 통일하기 위해 Axios를 싱글톤 패턴으로 ApiService에 적용했습니다. 이를 통해 유지보수와 테스트 용이성을 높이고자 했으나, 예상치 못한 의존성 문제가 발생했습니다. 정적 AxiosInstance를 통한 구현은 테스트에서 상태 관리와 조작에 어려움을 초래했습니다. 이러한 문제를 해결하기 위해 진행한 리팩토링과 그 효과를 공유하겠습니다.

싱글톤 패턴을 통한 통일된 API 서비스 구현

초기 코드에서는 정적 AxiosInstance와 accessToken을 통해 싱글톤 패턴을 구현하였습니다. 이러한 방식은 API 호출을 통일하는 데 적합했지만, 테스트에서의 상태 관리가 어려웠습니다. 이를 개선하기 위해 아래와 같은 리팩토링을 진행했습니다.

리팩토링 전

리팩토링 전에는 정적 AxiosInstance와 accessToken을 사용하여 싱글톤 패턴을 구현했으며, API 호출에서 동일한 인스턴스와 토큰을 사용했습니다.

ApiService.ts

export default class ApiService { // 정적 Axios 인스턴스를 통해 싱글톤 패턴 구현 protected static instance: AxiosInstance = axios.create({ withCredentials: true, headers: { 'Content-Type': 'application/json', }, }); // 정적 변수로 액세스 토큰 관리 private static accessToken = ''; constructor() { if (typeof window !== 'undefined') { this.initializeAccessToken(); } this.setupRequestInterceptors(); } private initializeAccessToken() { const storedToken = localStorage.getItem('accessToken'); if (storedToken) { ApiService.setAccessToken(storedToken); } } protected static getAccessToken(): string { return ApiService.accessToken; } static setAccessToken(accessToken: string) { this.accessToken = accessToken; } ... }

문제점

정적 AxiosInstance와 정적 accessToken을 통해 싱글톤 패턴을 구현했습니다. 그러나 이렇게 하면 인스턴스를 공유하는 싱글톤 패턴이 적용되지만, 각기 다른 테스트 환경이나 다양한 의존성 주입을 고려하기 어려웠습니다.

리팩토링 후

Axios 인스턴스를 정적 변수로 정의하는 대신 getInstance 메서드를 통해 싱글톤 패턴을 유지하면서도 인스턴스를 하나만 생성하도록 보장합니다. 액세스 토큰도 정적 변수에서 인스턴스 변수로 관리하도록 변경했습니다. 이로써 모듈성과 테스트 용이성이 개선되었습니다.

ApiService.ts

export default class ApiService { private static isServer = typeof window === 'undefined'; private isRefreshing = false; private refreshSubscribers: Array<(token: string) => void> = []; protected static instance: ApiService; protected axiosInstance: AxiosInstance = axios.create({ withCredentials: true, headers: { 'Content-Type': 'application/json', }, }); protected constructor() { if (!ApiService.isServer) { this.initializeAccessToken(); this.setupRequestInterceptors(); this.setupResponseInterceptors(); } } public static getInstance(): ApiService { if (!ApiService.instance) { ApiService.instance = new ApiService(); } return ApiService.instance; } private initializeAccessToken() { const storedToken = localStorage.getItem('accessToken'); if (storedToken) { this.setAccessToken(storedToken); } } private clearTokens() { localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem('signin-user-data'); this.axiosInstance.defaults.headers.common['Authorization'] = undefined; this.setupRequestInterceptors(); } public setAccessToken(accessToken: string) { const newAuthorization = accessToken ? `Bearer ${accessToken}` : undefined; localStorage.setItem('accessToken', accessToken); this.axiosInstance.defaults.headers.common['Authorization'] = newAuthorization; this.setupRequestInterceptors(); } ... }

리팩토링의 주요 장점

  1. 싱글톤 패턴 유지
    getInstance() 메서드를 통해 인스턴스를 하나만 생성하도록 보장하면서, 다양한 API 호출에서 일관성을 유지할 수 있습니다.
  2. 의존성 주입 가능성
    axiosInstance와 accessToken을 인스턴스 변수로 관리하여, 테스트 환경에서 모킹 및 상태 조작이 용이해졌습니다.
  3. 액세스 토큰 관리
    accessToken을 정적 변수 대신 인스턴스 변수로 관리하여 테스트 환경에서 토큰 초기화 및 제어가 쉬워졌습니다.
  4. 테스트 용이성
    axiosInstance와 accessToken을 인스턴스 내에서 관리하여 각 테스트 사이의 상태 공유를 방지하고, 테스트 환경에서 환경을 쉽게 조작할 수 있습니다.
  5. 캡슐화 및 모듈성 향상
    인스턴스 내 기능을 캡슐화하여 모듈성과 관심사의 분리를 강화했습니다.

결론

싱글톤 패턴을 이전 회사에서 적용해본 경험이 있어서 단기심화 3기 팀 프로젝트를 진행하면서 팀원들에게 싱글톤 실사용기를 공유해주고 싶었고 이전 회사에서는 싱글톤을 사용하면서 테스트를 진행해본 경험이 부족했습니다. 그래서 이번 팀 프로젝트를 통해서 부족한 부분을 채우기 위해서 진행하면서 겪었던 부분을 공유하고 싶었습니다. 그러면서 싱글톤 패턴이 주는 장점과 단점에 대해서 자세히 알게되었고 장단점을 잘 비교해서 사용해야 할 필요성이 있다고 생각이 들었습니다. 그리고 AI 도구(perplexity.ai)를 사용하여 테스트 코드를 작성하는 과정에서 AI 도구를 사용할 때에도 내가 작성한 코드를 가지고 무작정 테스트 코드를 작성하는 방법보다는 내가 작성한 코드를 먼저 검증하고 잘못된 부분 또는 테스트 코드를 하기에 용이한지 먼저 체크를 하고 내가 해당 부분에 대한 이해가 먼저 선행된 이후에 테스트 코드 작성을 요청하는 방식으로 하는게 좋겠다는 생각을 했습니다. 디자인 패턴을 개발에 적용하는게 쉽지는 않지만 많이 사용되는 디자인 패턴을 더 공부해서 실제로 사용해보고 장단점 비교에 대한 글을 작성해보도록 하겠습니다.