Retrofit Singleton 패턴을 유지하면서 ApiService를 기능별로 분리하기

2025. 2. 17. 16:35Android/네트워킹

728x90
반응형

Retrofit을 이용하여 API 통신을 하려면 Retrofit 객체를 만들어야 한다.

이 때, 객체에 BaseURL을 주입해줘야 하는데 대부분의 프로젝트에서는 url이 하나로 통일되지는 않을 것이다.

게다가 앱 규모가 커질수록 API 호출 개수는 무수히 많아지는데

이걸 하나의 파일에서 관리하기는 불가능.

Retrofit 객체를 싱글톤으로 유지하면서 APIService를 기능별로 분리하려면 어떻게 해야할까?

1️⃣ API 인터페이스를 기능별로 분리

각 도메인(feature)별로 @Headers를 추가해서 Base URL을 구분하면 된다.

AService

interface AService {
    @GET("endpoint-a")
    @Headers("Base-Url: API_A")
    suspend fun getSomethingA(): Response<AResponse>
}

BService

interface BService {
    @GET("endpoint-b")
    @Headers("Base-Url: API_B")
    suspend fun getSomethingB(): Response<BResponse>
}

CService

interface CService {
    @GET("endpoint-c")
    @Headers("Base-Url: API_C")
    suspend fun getSomethingC(): Response<CResponse>
}

2️⃣ Retrofit을 싱글톤으로 유지하면서 각각의 Service 제공

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(BaseUrlInterceptor()) // Base URL 변경 Interceptor
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("<https://default-api.com/>") // 기본 Base URL (변경 가능)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    fun provideAService(retrofit: Retrofit): AService {
        return retrofit.create(AService::class.java)
    }

    @Provides
    fun provideBService(retrofit: Retrofit): BService {
        return retrofit.create(BService::class.java)
    }

    @Provides
    fun provideCService(retrofit: Retrofit): CService {
        return retrofit.create(CService::class.java)
    }
}

3️⃣ Base URL을 변경하는 Interceptor

Retrofit은 기본적으로 하나의 Base URL을 가지지만, OkHttp의 Interceptor를 사용하면 동적으로 변경할 수 있다.

class BaseUrlInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val originalHttpUrl = originalRequest.url

        // 헤더에서 특정 Base URL 키 가져오기 (예: "Base-Url")
        val baseUrlHeader = originalRequest.header("Base-Url")
        val newBaseUrl = when (baseUrlHeader) {
            "API_A" -> "<https://api-a.com/>"
            "API_B" -> "<https://api-b.com/>"
            "API_C" -> "<https://api-c.com/>"
            else -> originalHttpUrl.toString() // 기본값 유지
        }

        // 새로운 URL을 기반으로 요청 변경
        val newHttpUrl = originalHttpUrl.newBuilder()
            .scheme("https")
            .host(newBaseUrl.removePrefix("https://").removeSuffix("/"))
            .build()

        val newRequest = originalRequest.newBuilder()
            .url(newHttpUrl)
            .build()

        return chain.proceed(newRequest)
    }
}

 

이렇게 작업을 하면 BaseURL 별로 Retrofit 객체를 생성하지 않으면서

기능별로 ApiService 파일을 구분해서 관리할 수 있게 된다.

728x90
반응형