iOS: Repository pattern in Swift
[출처]
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 등등)에 상관없이 도메인 레이어에서 하나의 인터페이스로 데이터를 불러 사용할 수 있게 한 패턴인 것 같다.
- 모듈의 결합도를 낮춰 테스팅을 쉽게 해주는 장점이 있다.
추가로 볼 것들