ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Remote 레이어란?
    Clean Architecture 2025. 8. 3. 20:06

     

    안녕하세요 벌써 8월이네요! 여러분은 다음 생에 동물로 태어난다면 어떤 동물로 태어나고 싶나요? 저는 로 태어나고 싶은데요. 그중에서도 비둘기도, 참새도 아닌 독수리로 태어나고 싶어요. 하늘을 훨훨 날아다니면서 자유를 만끽하고 싶네요. 오늘은 데이터 레이어(data layer)에 의존하는 원격 레이어(remote layer)에 대해 알아보려고 합니다!

     

    출처: https://github.com/bufferapp/clean-architecture-components-boilerplate?tab=readme-ov-file#Architecture + 직접 그림

     

    1. 원격 레이어란?

    원격 레이어(remote layer)는 원격 서버와 통신하여 데이터를 주고받는 계층입니다. 데이터를 요청하거나, 요청에 대한 응답 처리 및 네트워크 오류 처리를 하게 됩니다. 이때 Retrofit이나 OkHttp같은 네트워크 라이브러리를 사용하여 API 호출을 관리하게 됩니다. 구성요소는 DataSourceImplementation, API Service, Model Mapper, API Factory, Model 이 있습니다. 중요한 구성요소 위주로 하나씩 살펴보겠습니다.

     

    2. 원격 레이어의 구성요소

    2-1. RemoteDataSourceImpl

    internal class UserRemoteDataSourceImpl(
        private val apiService: ApiService,
        private val userMapper: UserMapper
    ): UserRemoteDataSource {
        override fun getUserById(userId: String): UserEntity {
            val remoteUser = apiService.getUserById(userId)
            return userMapper.mapResponseToEntity(remoteUser)
        }
    }

     

    RemoteDataSourceImpl은 데이터 레이어에 정의되어 있는 DataSource Interface를 구현합니다. 인터페이스 내부에 정의되어 있는 메소드는 userId를 가지고 데이터 레이어의 모델(Entity)를 반환하는 함수입니다. 데이터를 받아오기 위해 필요한 첫번째 의존성은 API Service입니다. API Service를 통해 원격 레이어의 모델(Response)을 받아오고, 이를 다시 데이터 레이어에 전달하기 위해 Model Mapper을 사용합니다. 즉, 원격 레이어의 모델(Reponse)를 데이터 레이어의 모델(Entity)로 매핑합니다.

     

    2-2. API Service

    interface ApiService {
        @GET("users/{userId}")
        suspend fun getUserById(@Path("userId") userId: String): UserResponse
    }

     

    ApiService는 인터페이스 형식으로 Retrofit 등을 정의합니다. 이때 suspend 지연 함수를 통해 코루틴을 사용하는 것을 알 수 있습니다. Retrofit의 API Factory에서 실제 구현사항을 만들어줍니다.

     

    2-3. Model Mapper

    internal class UserMapper {
        // remote layer → data layer
        fun mapResponseToEntity(userResponse: UserResponse): UserEntity {
            return UserEntity(
                id = userResponse.id,
                name = userResponse.name,
                address = userResponse.address
            )
        }
        // data layer → remote layer
        fun mapEntityToResponse(userEntity: UserEntity): UserResponse {
            return UserResponse(
                id = userEntity.id,
                name = userEntity.name,
                address = userEntity.address
            )
        }
    }

     

    Model Mapper원격 레이어와 데이터 레이어의 모델을 서로 변환해주는 역할을 합니다. 

     

    3. 테스트 코드 작성

    class UserApiTest {
        @Test
        fun 'API로 유저 정보 가져오기'() {
            val mockWebServer = MockWebServer().apply {
                enqueue(MockResponse().setBody{"...")
            }
            
            val apiService = Retrofit.Builder()
                .baseUrl(mockWebServer.url("...")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)
                
            runBlocking {
                val userResponse = apiService.getUserById("1")
                assertEquals("비교 대상", userResponse.name)
            }
        }
    }

     

    이와 같이 테스트 코드를 작성하여 API가 제대로 작동하는 지 점검할 수 있습니다. runBlocking 코루틴 빌더를 이용하여 코드 블록 수행이 완료될 때까지 다음 코드의 수행을 막는 것을 알 수 있습니다.

    4. @SerializedName 

    @SerializedNameAPI 서버 통신 등으로 응답값을 받아오는 상황에서 필드명을 재정의하는데 도움을 주는 어노테이션입니다. 다음과 같이 데이터 레이어의 모델(엔티티)가 정의되어있다고 가정합시다.

    // data layer model
    data class UserEntity(
        val id: String,
        val name: String,
        val createdAt: LocalDateTime
    )

     

    이에 대해서 원격 레이어의 모델을 정의하는 방법은 다음 두 가지가 있습니다.

    // case1) remote layer의 model 필드 그대로 사용
    data class UserResponse(
        @SerializedName("id")
        val id: String,
        @SerializedName("name")
        val name: String,
        @SerializedName("time_stamp")
        val timeStamp: LocalDateTime
    )
    
    // case2) data layer의 model 필드에 맞게 변경
    data class UserResponse(
        @SerializedName("id")
        val id: String,
        @SerializedName("name")
        val name: String,
        @SerializedName("time_stamp")
        val createdAt: LocalDateTime
    )

     

    첫 번째는, 원격 레이어에서 응답값으로 내려오는 필드명을 그대로 사용하는 경우입니다. 두 번째는, 데이터 레이어에 정의된 모델의 필드명을 반영하여 변경하는 경우입니다. 프로젝트 내에서 일관성만 유지하면 두 방법 중 어떤 방식을 취하든 괜찮습니다. 다만, 두 가지 방법을 한 프로젝트 내에서 같이 쓰는 것은 좋지 않습니다. 

    5. 추가 정리

    첫 번째로, 원격 레이어(remote layer)는 이론적으로는 안드로이드 플랫폼 의존성 없이 사용 가능한 것으로 설명하지만, 실제 프로젝트에서는 그렇지 못한 경우가 많습니다. 즉, 코틀린 모듈로만 만들지 않고 안드로이드 모듈에도 의존하게 됩니다. 두 번째로, 원격 통신을 위한 인증 토큰을 잘 관리해야 합니다. 인증 토큰에 해당하는 쿠키 및 헤더를 Interceptor를 통해 관리할 수 있습니다. 세 번째로, HTTP 상태 코드에 맞는 에러처리 및 메세지 관리를 해야합니다. 요청이 실패했을 때 한번만 다시 재요청할 것인지, 아니면 성공할 때까지 재요청할 것인지에 대한 retry를 설계해야합니다. 마지막으로, 외부 API 키가 노출되지 않게 관리해야하며 데이터를 암호화하는 과정을 거치는 것이 좋습니다.

     

    오늘은 데이터 레이어를 의존하는 원격 레이어(remote layer)의 특징구성요소 중 RemoteDataSourceImpl, API Service, Model Mapper 에 대해 알아보았습니다. 다음에는 데이터 레이어를 의존하는 또 다른 레이어인 로컬 레이어(local layer)에 대해 알아보겠습니다. 이번주도 화이팅하세요! 😊

     

    'Clean Architecture' 카테고리의 다른 글

    Presentation 레이어란?  (8) 2025.08.17
    Local 레이어란?  (4) 2025.08.10
    Data 레이어란?  (2) 2025.07.27
    Domain 레이어란?  (2) 2025.07.20
    클린 아키텍처 파헤쳐보기  (0) 2025.04.20
Designed by Tistory.