Clean Architecture + MVVM에대한 글을 읽다보면 아무 지식 없이는 이해할 수 없는 부분이 있다.
Entity, Usecase, Repository등 단어자체의 개념적인 이해가 정확히 정리되어있지 않다보니 완벽하게 이해할 수 없다.
이런 용어들을 정리한 블로그를 너무 찾기 힘들기 때문에 이에대한 용어의 정리가 필요하다고 느껴 정리해보려한다.
혹시 틀린점이 있다면 정중히 댓글 부탁드립니다.
Entity
- Entity( = Enterprize Business Rules): Actor가 필요로 하는 데이터의 모델을 의미한다.
- 특정 '도메인'에서 사용되는 struct 모델
- ex) Actor가 필요로 하는 Movie와 MoviewPage에 대한 Entity
struct Movie: Equatable, Identifiable {
typealias Identifier = String
enum Genre {
case adventure
case scienceFiction
}
let id: Identifier
let title: String?
let genre: Genre?
let posterPath: String?
let overview: String?
let releaseDate: Date?
}
struct MoviesPage: Equatable {
let page: Int
let totalPages: Int
let movies: [Movie]
}
대충 이렇게 정리할 수 있을 것 같다. 다른 디자인 패턴에서의 Model? 정확히 설명하면 뷰에서 사용할 데이터가 아닌 외부 || 내부에서 가져오게되는 원시 데이터를 "Entity"라고 정리하면 될 것 같다.
위에서 설명한 것중에 특정 "도메인" 이라는 용어가 나왔다. 그럼 도메인은 또 뭘까
도메인을 알기 전에 UseCase가 뭔지 알아야 할 것 같다. 왜냐하면 Domain은 Use Case와 Entity를 합쳐 Domain레이어라고 부르기 때문!
Use Case
서비스를 사용하고 있는 사용자(User)가 해당 서비스를 통해 하고자 하는 것을 의미한다.
어느 블로그에서 Use Case를 이렇게 설명하고 있다.
블로그라는 서비스가 있다고 가정해보자.. 사용자는 블로그에 들어와서 보고싶은 게시글을 "검색"할 수 있고, "댓글"을 남기거나, "공유" 를 하는등 다양한 행동을 수행할 수 있다. 이러한 사용자가 서비스에서 수행하고자 하는 것들이 UseCase라고 할 수 있다.
또는
Actor가 Entity를 원하는데, 이 값은 계산되거나 특정 로직에 의해서 얻어지므로 Actor가 원하는 Entity를 얻어내는 "로직"?
ex) Actor가 원하는 Entity인 MoviePage를 얻기 위해서 필요한 "로직"
그냥 비즈니스 로직 말하는거..? 같은데? 라는 생각이 든다. 그래서 알아보니 Clean Architecture에서의 Use Case는 MVVM패턴에서의 Service와 유사한 역할을 수행한다고 한다. 결국엔 핵심 비즈니스 로직을 처리한다. 라는 의미인것 같다.
기존의 설명하던 영화검색 앱에서는 그럼 Use Case는 "영화검색"과 "내가 최근에 검색한 최근 검색 영화"를 가져오는 UseCase가 있다.
그렇다면 결국 Entity는 "영화검색"과 "내가 최근에 검색한 영화목록"을 가져오기위해 필요한 struct Model인 것이다.
protocol SearchMoviesUseCase {
func execute(
requestValue: SearchMoviesUseCaseRequestValue,
cached: @escaping (MoviesPage) -> Void,
completion: @escaping (Result<MoviesPage, Error>) -> Void
) -> Cancellable?
}
final class DefaultSearchMoviesUseCase: SearchMoviesUseCase {
private let moviesRepository: MoviesRepository
private let moviesQueriesRepository: MoviesQueriesRepository
init(
moviesRepository: MoviesRepository,
moviesQueriesRepository: MoviesQueriesRepository
) {
self.moviesRepository = moviesRepository
self.moviesQueriesRepository = moviesQueriesRepository
}
func execute(
requestValue: SearchMoviesUseCaseRequestValue,
cached: @escaping (MoviesPage) -> Void,
completion: @escaping (Result<MoviesPage, Error>) -> Void
) -> Cancellable? {
return moviesRepository.fetchMoviesList(
query: requestValue.query,
page: requestValue.page,
cached: cached,
completion: { result in
if case .success = result {
self.moviesQueriesRepository.saveRecentQuery(query: requestValue.query) { _ in }
}
completion(result)
})
}
}
struct SearchMoviesUseCaseRequestValue {
let query: MovieQuery
let page: Int
}
도메인
도메인은 엔티티와 Usecase를 포함하는 영역이라고 이해하면 좋을 것 같다.
정리를 해보자면 앱의 기능, (기획의) 요구사항을 개발하는 영역이라고 한다. 예를 들어 택시 앱이라면 도메인은 기사님께 콜을 요청하고, 탑승하고, 요금을 지불하는 과정을 포함한다고한다.
결국 도메인이란 "사용자가 이용하는 앱의 기능, 회사의 비즈니스 로직을 정의하고 있는 영역" 이라고 이해할 수 있다.
Repositories
Clean Architecture에서 Repositories는 외부 데이터 소스 또는 데이터 저장소와의 인터페이스를 제공하는 인터페이스이다. Repositories는 도메인 레이어에 속하며, 애플리케이션의 데이터 접근과 관련된 기능을 추상화한다.
Repositories는 데이터의 CRUD 작업을 정의하게되고, UseCase나 도메인 객체들은 Repositories를 통해 데이터에 접근하여 필요한 작업을 수행하게 된다.
아래는 구현 예시를 들어보자면 아래와 같다.
예를 들어 영화 검색 앱을 만들고 있다고 가정해보자. 사용자는 특정 키워드로 영화를 검색할 수 있고 검색 결과를 화면에 표시하게 될 것이다.
이 때 영화 데이터를 어떤 방식으로 가져오고 저장할지는 앱 개발자에게 달려있다.
protocol MoviesRepository {
func searchMovies(withKeyword keyword: String, completion: @escaping (Result<[Movie], Error>) -> Void)
func saveMovie(_ movie: Movie, completion: @escaping (Result<Void, Error>) -> Void)
}
여기서 중요한건 이런 프로토콜 정의까지가 Domain레이어에 속한다는 것이다.
만약 Domain내부에 DataRepository가 존재해서 Damain이 Data Repository를 활용해 데이터를 가져오게 된다면,
의존성 방향이 Domain레이어 ➡️ Data Repository레이어 가 될 것이다.
이런 의존성 방향을 전환하기 위해 Data Repository가 Domain을 활용한다면 Domain레이어 ⬅️ Data Repository 레이어가 될 것이다.
Presenter
- Entity 데이터를 표현(present) 하는데 필요한 계층
- Coordinator
- View
- ViewModel
- Behaviors: 특정 View의 event에 관해 적용되는 UI