디자인 패턴을 살펴보던 도중,
Repository Pattern을 적용해본적은 있지만 정리를 하지 않았던 것을 발견하여 간단하게 정리를 하면서 글을 작성해보고자 한다.
Clean Architecture 예제를 확인해보면 repository를 적용했다는 것을 확인할 수 있으니, 해당 샘플을 Repository Pattern의 예제로 보면 될 것으로 보인다.
우선,
Repository Pattern에 대하여 검색하면 다음과 같은 이미지를 어렵지 않게 볼 수 있다.
이 이미지로 확인할 수 있는 구조가 Repository Pattern을 아주 잘 설명한 것이기 때문이다.
Repository Pattern은
Data의 출처에 관계 없이 동일한 인터페이스로 데이터에 접근할 수 있도록 하는 패턴이다.
즉, DataSource를 캡슐화 시킨다.
이미지를 보고 다시 생각해보자.
ViewModel에서 직접 Data에 접근하여 데이터를 가져오는 것이 아니라, ViewModel에서는 Repository만 접근을 하게 된다.
Repository에서 Remote Data인지 Local Data인지 필요한 데이터를 가져와 ViewModel에게 전달해준다.
즉, Data Layer가 캡슐화가 되고 ViewModel이 포함되어 있는 Presentation Layer에서 Data Layer를 직접 호출하지 않고 Repository를 통해서만 접근이 가능하게 되는 것이다.
이처럼 Data Layer를 캡슐화 시키는 것이 Repository 패턴의 주된 목적이며, 캡슐화를 통해 얻을수 있는 장점은 다음과 같다.
- Data Layer에 대한 의존성을 줄일 수 있다. 즉, Data Layer와 Presentation Layer 간의 Coupling이 줄어든다.
- Presentation Layer에서 Data Layer에 직접 접근하지 않으므로, 새로운 Data의 추가가 쉽다.
- Presentation Layer에서는 Repository에 데이터 요청만 하면 되므로, 일관된 인터페이스로 데이터를 요청할 수 있다.
- Unit Test를 통한 검증하기가 쉬워진다.
이것들을 제외한 다양한 장점들이 있겠지만, 필자가 생각했을 때 Repository를 사용하는 가장 큰 이유는 위의 4가지 이유라고 생각한다.
하지만, 아무래도 하나의 허브를 추가하는 것과 마찬가지이기 때문에
관리가 필요한 코드와 파일들이 증가한다.라는 단점이 존재한다.
그렇다면,
Repository Pattern가 적용된 것을 확인해보도록 하자.
해당 예제는 이전에 작성해두었던 Clean Architecture 샘플 예제를 확인하면 된다.
Repository Pattern은 위에서 언급했다시피 ViewModel에서 Data를 호출하는 부분에서 사용하는 것이다.
즉, ViewModel에서 부터 시작하여 확인을 하면 된다.
ViewModel에서 데이터를 호출 할 때, Repository Pattern을 사용한다.
하지만, 예제를 보면 ViewModel에서 바로 Data를 호출하는 것이 아니라 UseCase를 사용하고 있다.
UseCase를 사용하는 이유에 대해서는 여기서 확인하면 될 것이다.
위의 이유 때문에 UseCase를 사용하는 것이기 때문에, 이것을 제거하고 Repository를 사용하는 것이 아니라 UseCase 안에서 Repository를 호출하도록 구현하면 되는 것이다.
class GetMoviesUseCase(private val repository: MovieRepository) {
operator fun invoke(
query: String
): Flowable<List<Movie>> = repository.getSearchMovies(query)
fun getFlowData(
query: String
): Flow<List<Movie>> = repository.getSearchMoviesFlow(query)
}
이처럼 UseCase에서 Repository를 호출하도록 하자.
UseCase는 Domain Layer에 존재하기 때문에 해당 Repository의 Interface는 Domain Layer에 존재하지만, 실제로 데이터는 Data Layer에 존재하기 때문에 Interface의 구현부는 Data Layer에서 작성이 되어야 한다.
여기서, 필자는 왜 구현부와 Repository가 다른 부분에 선언하여 사용하는지에 대해 한번에 이해를 하지 못하였다.
따라서, 이렇게 구현되는 이유에 대해서 조금 더 생각해보자.
Clean Architecture 구조에서 Domain Layer는 참조를 당할 뿐, 다른 계층을 참조하지 않는다.
따라서 Repository가 Domain Layer외에 존재하게 되면 해당 모듈을 참조 받아야하는데, 상호참조가 발생하기 때문에 다른 모듈에 선언할 수 없다.
그렇기 때문에 호출해야하는 UseCase가 위치하는 모듈인 Domain Layer에 Repository가 존재해야 한다.
다음으로 Repository의 구현부는 Data Layer에 존재해야 하는데, 그 이유는 Repository가 Domain에 존재해야 하는 이유를 생각해보면 간단히 알 수 있다.
구현부는 직접 데이터를 가져와서 Control해야하는데, Domain Layer에서는 Data Layer에 있는 데이터를 가져올 수 없으며 그 반대는 가능하다.
따러서, Data Layer에 선언하여 Domain Layer에 존재하는 Repository를 상속 받아서 사용하도록 구현을 해야하는 것이다.
Repository의 구현부가 작성된 부분을 보면 다음과 같다.
해당 Package 내부에 Local과 Remote의 하위 패키지가 존재하는 것을 볼 수 있다.
이처럼, ViewModel이 Repository를 호출하였을 때(해당 예제에서는 ViewMode -> UseCase -> Repository 순으로 호출된다) ViewModel에서는 알 수 없지만 Repository에서는 Local과 Remote 중 필요한 데이터를 알아서 가져올 수 있도록 구현을 해주면 되는 것이다.
따라서 Repository를 구현한 부분에서는 Local과 Remote의 DataSource를 모두 사용할 수 있게 구현한다.
class MovieRepositoryImpl(
private val movieRemoteDataSource: MovieRemoteDataSource,
private val movieLocalDataSource: MovieLocalDataSource
) : MovieRepository {
...
}
Repository를 상속받으면서, 2가지 DataSource를 DI로 주입받아서 사용하게 되어있다.
2가지 DataSource를 사용하여 원하는 데이터를 구하도록하여 Return 시켜 ViewModel에서 사용할 수 있도록 한다.
여기까지 구현을 확인하였을 때 다시 Repository 패턴에 대하여 생각해보자.
ViewModel에서는 직접적으로 DataSource에 접근하지 않고 Repository를 통해 DataSource에 접근하여 데이터를 가져온다.
Usecase를 사용하고 있으므로, Usecase에서 Repository를 통해 DataSource에 접근하여 데이터를 가져온다.
이 때 Repository를 호출하는 UseCase는 Repository에서 어떠한 데이터를 사용해서 가져오는지 알 필요 없으며, 원하는 데이터만 Return받으면 된다.
이제 다시 처음으로 돌아와, ViewModel을 살펴보자.
ViewModel에서 데이터를 가져와서 UI에 뿌려주는 부분을 보면 단순히 UseCase를 호출하여 return된 값으로만 UI를 뿌려주고 있다.
여기서 Repository의 구현부분을 어떻게 변경하여도 Return 타입에 문제가 발생하지 않으면 빌드 시 오류는 발생하지 않으며, 정상적인 데이터만 return 해준다면 구현 부분은 어떤게 추가/삭제 되어도 영향이 없다.
이처럼,
Presentation Layer와 Data Layer간의 의존성이 낮아지도록 하여 다양한 이점을 얻을 수 있는 디자인 패턴이 Repository Pattern이라고 할 수 있다.
Repository Pattern에 대해서 정리하면서, 필자도 다시 한번 생각을 정리할 수 있었다.
각 계층별로 의존성을 줄여준다. 라는 것에 대한 개념이 확실하게 잡히지 않았었는데, 다시한번 공부하고 글을 작성하며 정리를 하다보니 개념이 확실하게 잡힌 것 같다.
역시 계층별로 의존하지 않고 독립적으로 실행될 수 있는 환경이 예제를 작성할때도, 다양한 테스트를 할 때도 편한 것 같다는 생각이 든다.
해당 게시글에 사용한 예제는 Github에 올려두었다.
https://github.com/HeeGyeong/CleanArchitectureSample
'Android > Architecture' 카테고리의 다른 글
[Android] MVI Pattern을 적용해보자. (2) | 2022.05.03 |
---|---|
[Android] MVI Pattern ? (0) | 2022.04.30 |
[Android] Modular Architecture 예제 (0) | 2022.02.20 |
[Android] Modular Architecture 개념 정리 (0) | 2022.02.16 |
[Android] Coordinator Pattern (0) | 2022.02.15 |