ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Data 레이어란?
    Clean Architecture 2025. 7. 27. 19:40

     

    안녕하세요🙌 오늘 UMC x 구름톤 유니브에서 주관한 컨퍼런스에 참여하기 위해 공덕역 창업허브센터에 다녀왔어요. 연사님들로부터 30분씩 총 아홉 분의 강연을 들었는데요. SDK, Coil 라이브러리 외에도 "여러분의 아키텍처 안녕하신가요?" 라는 주제로 밥아저씨의 클린 아키텍처를 강연해주시더라고요. 레이어의 개수보다 의존 관계 및 방향이 중요하다는 점, 엔티티와 유즈케이스를 외부 관심사로부터 분리해야 한다는 점을 말씀하셨습니다. 또한 안드로이드는 compose, hilt/koin(DI), flow, coroutine 등 정형화된 구조를 딥다이브하는게 좋다고 하셨는데요. 공부하는 데에 있어 힘들지만 좋은 동기부여가 될 수 있었던 유익한 시간이었습니다.

    프론트엔드 컨퍼런스

     

     오늘은 저번에 이어 클린 아키텍처의 도메인 레이어를 의존하는 데이터 레이어(data layer)에 대해 알아보겠습니다!

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

    1. 데이터 레이어의 역할

    데이터 레이어(Data layer)는 저번에 살펴보았던 도메인 레이어(Domain layer) 에 필요한 데이터를 제공하는 역할을 합니다. 도메인 레이어를 의존하긴 하지만, 비즈니스 로직(도메인)은 관심사가 아니며 오직 데이터 전달에만 집중을 하는데요. 이때, 데이터 연결을 위해 레포지토리 패턴(Repository Pattern)을 사용합니다. 여기서는 데이터를 저장하고, 수정하거나 삭제하는 등 CRUD 작업을 수행합니다.

     

    2. 데이터 레이어의 구성요소

    위에 제시한 구조를 더 간략화해서 아래의 구조를 기반으로 소개해볼려고 합니다.

    데이터 레이어(출처: 직접 그림)

     

    데이터 레이어의 구성요소로 첫 번째는 Repository Implementation가 있습니다. 도메인 레이어에 정의된 Repository Interface 를 구현하고, DataSource(Local, Remote)를 의존하여 도메인에 필요한 데이터를 제공합니다. 두 번째는 DataSource Interface 로 LocalDataSource, RemoteDataSource 두 종류로 구성됩니다. 세 번째로 Model은 데이터 레이어에 정의된 DTO로 엔티티(Entity)라고도 불립니다. 마지막으로 Model Mapper는 도메인 레이어와 데이터 레이어의 모델을 형변환하는 역할을 합니다. 

    2-1. RepositoryImpl (= Repository Implementation)

    RepositoryImpl 을 구현할 때는 두 가지 케이스가 있는데 하나씩 살펴보겠습니다.

    // case1) RemoteDataSource 의존하는 경우
    class UserRepositoryImpl(
        private val userRemoteDataSource: UserRemoteDataSource, // interface
        private val userMapper: UserMapper
    ): UserRepository {
        override fun getUserById(userId: String): User {
            val remoteUser = userRemoteDataSource.getUserById(userId) // entity
            return userMapper.mapEntityToDomain(remoteUser)
        }
    }

     

    첫 번째는 DataSource 중 RemoteDataSource 만 의존하는 경우입니다. 여기서 RepositoryImpl 은 도메인에 정의된 Repository Interface 를 구현합니다. 오버라이딩 함수를 통해 얻어지는 userId 값을 가지고 User 라는 도메인 레이어의 모델(Model) 을 반환해야하는 상황입니다. 데이터를 얻기 위해 RemoteDataSource 인터페이스를 의존하게 되고, 내부의 메소드에 userId 을 전달하면서 원하는 값을 얻어옵니다. 하지만 이때 값은 데이터 레이어에서만 사용할 수 있는 모델(entity)이기에, Model Mapper 또한 의존하여 도메인이 원하는 모델로 바꿔서 반환합니다.

    // case2) RemoteDataSource, LocalDataSource 둘 다 의존하는 경우
    class UserRepositoryImpl(
        private val userLocalDataSource: UserLocalDataSource,
        private val userRemoteDataSource: UserRemoteDataSource
        private val userMapper: UserMapper
    ): UserRepository {
        override fun getUserById(userId: String): User {
            val localUser = userLocalDataSource.getUserById(userId) // entity
            if(localUser != null) return userMapper.mapEntityToDomain(localUser)
            // localUser가 null인 경우 (=기존에 저장된 유저 정보가 없는 경우)
            val remoteUser = userRemoteDataSource.getUserById(userId) // entity
            userLocalDataSource.saveUser(remoteUser) // 캐싱(caching) 작업
            return userMapper.mapEntityToDomain(remoteUser)
        }
    }

     

    두 번째는 DataSource 중 LocalDataSource, RemoteDataSource 둘 다 의존하는 경우입니다. 먼저, LocalDataSource로부터 기존에 유저 정보가 저장되어있는지 확인합니다. 이미 있다면, 캐싱된 유저 정보를 mapper 를 통해 형변환하여 도메인에 전달합니다. 만약 없다면 RemoteDataSource 에서 유저 정보를 가져오게 됩니다. 이후 LocalDataSource를 통해 내부에 캐싱(caching)을 하고나서 도메인에 마찬가지로 형 변환하여 전달하게 됩니다.

     

    다시 정리하면 첫 번째 케이스데이터를 가져올 때 단순히 Remote에서 가져오는 것입니다. 이때는 Local의 캐싱을 사용하지 않기 때문에, 가져올 때까지 원형 프로그래스바와 같은 UI로 사용자에게 지연(delay)을 알려야합니다. 두 번째 케이스사용자에게 먼저 캐싱된 Local을 먼저 보여주고나서 Remote을 통해 최신의 정보로 동기화하는 것인데요. 첫 번째 경우와 달리, 프로그래스바를 띄워줘야 하는 지연 상황은 발생하지 않지만, 두 가지 상황을 고려해야 합니다. 

    만약 local과 remote의 데이터 필드 값이 다르다면 어떻게 처리해야 할까?
    만약 local과 remote의 데이터 구조 자체가 다르다면 어떻게 처리해야 할까?

     

    2-2. DataSource Interface

    DataSource Interface 는 RemoteDataSource, LocalDataSource 두 가지로 구성됩니다.

    interface UserRemoteDataSource {
        fun getUserById(userId: String): UserEntity
    }
    
    interface UserLocalDataSource {
        fun saveUser(user: UserEntity)
        fun getUserById(userId: String): UserEntity?
    }

     

    Remote 와 다르게 Local 같은 경우에는 기존에 저장된 정보가 없을 수 있기 때문에(정보 삭제 및 처음 시작할 경우) 물음표(?) 를 통해 nullable 처리를 해야합니다. 이때 반환값은 엔티티(entity)로 데이터 레이어의 모델입니다.

    2-3. Model Mapper

    마지막으로 서로 다른 레이어의 모델을 형변환해주는 model mapper 입니다.

    internal class UserMapper {
        // data layer → domain layer
        fun mapEntityToDomain(userEntity: UserEntiy): User {
            return User(
                id = userEntity.id,
                name = userEntity.name,
                address = userEntity.address
            )
        }
        
        // domain layer → data layer
        fun mapDomainToEntity(user: User): UserEntity {
            return UserEntity(
                id = user.id,
                name = user.name,
                address = user.address
            )
        }
    }

     

    model mapper는 데이터 레이어의 모델(entity)을 도메인 모델로 바꿔주거나, 그 반대의 역할을 수행합니다. 근데 왜 model mapper 가 도메인 레이어와 데이터 레이어 중 왜 꼭 데이터 레이어에 존재해야 하는걸까요? 의존성 방향을 보면 데이터 레이어가 도메인 레이어를 바라봅니다. 이때 데이터 레이어는 엔티티와 도메인 레이어의 모델 둘 다 알지만, 도메인 레이어는 엔티티 모델을 알지 못합니다. 따라서, 두 모델을 알고 있는 데이터 레이어에 model maper 을 두는 것입니다. 추가적으로 알아볼 부분은 클래스 앞의 접근 범위 제한자입니다. internal 은 같은 모듈 내에서만 접근할 수 있게 허용하는데요. 클린아키텍처의 각각의 계층이 모듈로 분리된다는 점을 고려하면 이해할 수 있습니다. 

     

    3. 추가적인 부분

    첫 번째로, 도메인에 전달할 데이터를 위해 remoteDataSource를 사용할 경우 서버 오류, 매핑 오류 등이 발생할 수 있습니다. 이때, 데이터에 대한 Loading, Success, Error 상태를 관리하여 여러 상황에 적절하게 대처해야합니다. 두 번째로, 지난번에 도메인 레이어에서 언급한 유즈케이스처럼, 레포지토리 또한 단일 책임 원칙(SRP)을 준수해야 합니다. 여러 개의 기능을 하나의 레포지토리 안에 설계해서는 안 되며, UserRepository, OrderRepository, PaymentRepository 처럼 각각 하나의 책임만 갖도록 분리해야 합니다. 세 번째로, 데이터를 가져오는 부분이다보니 반응형 프로그래밍이 필요한데요. 이를 위해 RxJava나 순수 코틀린 라이브러리인 Flow를 사용하여 비동기 데이터 스트림을 처리해야합니다. 마지막으로, 도메인과 마찬가지로 데이터 레이어도 특정 플랫폼(안드로이드)에 의존하지 않고 순수 코틀린 언어로 구현할 수 있습니다.

     

    오늘은 도메인 레이어 의존하는 데이터 레이어의 역할과 구성요소인 RepositoryImpl, DataSource Interface, Model Mapper, 그리고 그 외의 추가적인 부분에 대해 알아보았습니다. 다음에는 DataSource와 관련하여 Remote 데이터 레이어에 대해 알아보겠습니다. 읽어주셔서 감사합니다!

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

    Local 레이어란?  (4) 2025.08.10
    Remote 레이어란?  (6) 2025.08.03
    Domain 레이어란?  (2) 2025.07.20
    클린 아키텍처 파헤쳐보기  (0) 2025.04.20
    클린 아키텍처 소개  (0) 2025.04.06
Designed by Tistory.