iOS/Architecture

iOS: Repository pattern in Swift

HaningYa 2020. 8. 7. 16:21
728x90

[출처]

 

iOS: Repository pattern in Swift

A design pattern to abstract data sources from your application

medium.com

 

Repository Pattern

리포지토리 패턴은 디자인 패턴으로 데이터의 추상화를 제공하여 어플리케이션이 추상화된 인터페이스로 작업할 수 있게 해준다. 

즉 데이터 출처에 관계없이 동일한 인터페이스로 어플리케이션이 데이터를 사용할 수 있게 해준다.

장점으로는

  • 결합도를 낮출 수 있다.
  • 도메인 객체를 persistence ignorant 하게 할 수 있다.
  • 정의된 인터페이스를 구현하는 mock에 DI 할 수 있으므로 테스팅하기 쉽다.

글에서 작성된 예시를 보자면 Tiendo iOS 앱에서 리포지토리 패턴을 사용함으로써 도메인 레이어와 데이터 레이어, 테이터 레이어 안쪽 레이어, 데이터 리포지토리와 데이터 소스를 추상화 할 수 있다.

 

도메인 레이어: RepositoryProtocol 을 통해 도메인 레이어(비즈니스로직) 을 데이터 레이어에서 분리시킬 수 있다.

예를들어

public protocol FavoritesRepository {
    func addFavorites(favorites: [Favorite]) -> Observable<Bool>
}

public class AddFavorites: UseCase {
    typealias T = Bool
    typealias Q = AddFavoritesRequestValues
    let favoritesRepository: FavoritesRepository
    
    public init(favoritesRepository: FavoritesRepository) {
        self.favoritesRepository = favoritesRepository
    }
    
    public func execute(_ requestValues: AddFavoritesRequestValues) -> Observable<Bool> {
        return favoritesRepository.addFavorites(favorites: requestValues.favorites)
    }
}

public class AddFavoritesRequestValues: RequestValues {
    let favorites: [Favorite]
    public init(favorites: [Favorite]) {
        self.favorites = favorites
    }
}

 

도메인 레이어의 UseCase에는 FavoriteRepository 프로토콜을 구현하는 개체의 인스턴스만 있다. 이 객체는 UseCase가 생성 될 때 DI 된다.

유스 케이스는이 프로토콜을 누가 구현했는지 신경 쓰지 않기 위해 유스 케이스는 데이터 저장소에서 추상화된다.

데이터 레이어 에서는

public class FavoritesDataRepository: FavoritesRepository {    
    private let remoteDataSource: FavoritesDataSource
    private let localDataSource: FavoritesDataSource
    
    public init(remoteDataSource: FavoritesDataSource, localDataSource: FavoritesDataSource) {
        self.remoteDataSource = remoteDataSource
        self.localDataSource = localDataSource
    }
    
    public func addFavorites(favorites: [Favorite]) -> Observable<Bool> {
        return remoteDataSource.addFavorites(favorites: favorites).do(onNext: { _ in 
		self.localDataSource.addFavorites(favorites: favorites) })
    }
}

public protocol FavoritesDataSource {
    func addFavorites(favorites: [Favorite]) -> Observable<Bool>
}

public struct FavoritesRemoteDataSource: FavoritesDataSource {    
    public init() {}
    
    public func addFavorites(favorites: [Favorite]) -> Observable<Bool> {
        let observable = Observable<Bool>.create { (observer) -> Disposable in
            do {
                let requestReference = try  Governor.sharedInstance.sessionManager.authRequest(FavoritesApi.addFavorites(favorites.transformToRemoteEntity()).asURLRequest(), responseArray: { (response: DataResponse<[FavoriteRemoteEntity]>) in
                    switch response.result {
                    case .success:
                        Governor.sharedInstance.resetCache()
                        observer.onNext(true)
                        observer.onCompleted()
                    case .failure(let error):
                        observer.onError(error)
                    }
                })
                return Disposables.create(with: { requestReference.cancel() })
            } catch {
                observer.onError(error)
                return Disposables.create()
            }
        }
        return observable
    }
}

public struct FavoritesLocalDataSource: FavoritesDataSource {    
    public init() {}
    
    public func addFavorites(favorites: [Favorite]) -> Observable<Bool> {
        let favoritesLocalEntity = favorites.transformToLocalEntity()
        do {
            try realm.write {
                realm.add(favoritesLocalEntity, update: true)
            }
            return Observable.just(true)
        } catch let error as NSError {
            return Observable.error(error)
        }
    }
}

데이터 레이어에서 FavoritesDataRepository 는 도메인 레이어에 정의된 FavoriteRepository 프로토콜을 준수한다. 

또한 2개의 데이터 소스가 있는데 remoteDataSource 와 localDataSource 이다. (둘다 FavoriteDataSource 프로토콜을 준수한다.)

이것이 FavoritesDataRepository를 데이터 소스로 부터 추상화 한다.

각각의 데이터 소스는 내부적으로 필요한 코드가 구현되어 있다. 예를들어 remoteDataSource의 경우 Alamofire request 가 있으며 localDataSource의 경우 Realm 을 사용하고 있다.

두개의 다른 데이터 소스를 한번에 처리할 수 도 있다. 예를들어 remote datasource 에서 성공적으로 추가가 된 뒤 local datasource 에 저장하고 싶다면 RxSwift 를 사용하여 구현할 수 있다.

 

 


이해한 부분

  • MVC 에서 Model 과 ViewModel 를 떼서 만든 MVVM 이다.
  • MVVM은 Model 과 ViewModel 을 바인딩 해주는 completion handler 같은게 필요한데 이걸 RxSwift를 사용하면 편하게 구현할 수 있다.
  • 이 MVVM 아키텍쳐에서 비즈니스 로직이 있는 ViewModel에 필요한 데이터들을 불러올때 Repository Pattern 을 통해 데이터 레이어를 도메인 레이어(ViewModel?, 비즈니스 로직?)로 부터 추상화 시킬수 있다.
  • 추상화 시킨 데이터 리포지토리를 통해 데이터 소스(local, remote 등등)에 상관없이 도메인 레이어에서 하나의 인터페이스로 데이터를 불러 사용할 수 있게 한 패턴인 것 같다.
  • 모듈의 결합도를 낮춰 테스팅을 쉽게 해주는 장점이 있다.

 

추가로 볼 것들

 

iOS: MVP clean architecture in Tiendeo app

Architecture layer by layer

medium.com

 

iOS: Dependency injection in Swift

Make the code more testable reducing the coupling

medium.com

 

[Design Pattern] Repository패턴이란

[1] Repository 패턴이란 데이터 출처(로컬 DB인지 API응답인지 등)와 관계 없이 동일 인터페이스로 데이터에 접속할 수 있도록 만드는 것을 Repository 패턴이라고 합니다. - viewModel 밑에 Repos..

eunjin3786.tistory.com

 

[Clean Architecture] iOS Clean Architecture + MVVM 개념과 예제

repository패턴이란 iOS-Clean-Architecture-MVVM 코드를 좀 더 자세히 살펴보려고 합니다-!! Clean Architecture and MVVM on iOS 에 이 프로젝트에 대한 설명도 적어두셨더라구요 👍 [1] Clean Architec..

eunjin3786.tistory.com

 

728x90