▩ 목 차 ▩
1. Clean Architecture 란 ?
1-1. Clean Architecture의 필요성
1-2. 안드로이드 Clean Architecture에 대해
1-3. 안드로이드 클린 아키텍처에서 사용되는 계층
1-4. 안드로이드 클린 아키텍처의 계층에서 선언해야 할 것 정리
1-5. 안드로이드 클린 아키텍처에서의 의존성
1-6. 안드로이드 클린 아키텍처에서의 햇갈리는점
1-6-1. Domain 계층과 Data 계층에서 사용하는 Data Model의 같을수도 다를수도 있다 ?
1-6-2. Domain 계층의 Model과 Data 계층의 Model은 어디서 사용되는가 ?
1-6-3. Data 계층의 Model은 어떻게 사용하는가?
1-6-4. UseCase를 사용하는 방법은 해당 기능을 수행하는데 필요한 Repository를 사용하여 원하는 기능을 수행하도록 되는것 이다. 그러면 UseCase를 사용하지 않고 Repository를 바로 사용하면 되는거 아닌가?
2. 멀티 모듈이란 ?
2-1. 멀티 모듈이란 ?
2-2. 안드로이드 스튜디오에서의 멀티 모듈 생성
3. Clean Architecture 파헤치기(feat, Movie App)
요즘 다른 사람들의 프로젝트를 살펴보면 프로젝트 구성이 너무나 희한하게? 이해안가게? 되어 있는것이 대부분이였다.
왜 그러한가 살펴보니 Clean Architecture로 구조화를 했기 때문이다.
다른 사람들의 프로젝트를 잘 파악할 수 있고, 내 프로젝트를 잘 구조화하기 위해선 당연히 Clean Architecture를 알아야 하는 것 이였다.!!! 한번 공부해보자.
[ 프로젝트를 진행하면서 차츰 Clean Architecture의 구조를 적용시킬 예정이므로, 이것을 적용 시키는 과정은 추후에 업데이트 하겠습니다.. ]
■ 1. Clean Architecture 란 ? ■
■ 1-1. Clean Architecture의 필요성
우리가 흔히들 사용하는 어플들을 보게 되면 다양한 기능들이 있기 때문에 복잡도가 굉장히 높다.
복잡도가 높은 어플리케이션을 개발할 때 어떻게하면 유지 보수하기 쉽고 고품질의 코드를 작성할 수 있을까?
==> 새로운 기능이 추가되거나 내부 로직이 변경될 때 유연하게 대처할 수 있도록 구조화를 해야한다. 즉, 다른곳과 얽히지 않게 구조화를 잘 해야한다는 것. 클린 아키텍처를 적용(계층을 분리하여 관심사를 분리하는것) 시키면 해결될 것이다.
구체적인 예를 들어본다면, 어떠한 어플 회사의 어플에서 사용하는 DB가 MySQL이다. 그런데 이 회사가 어쩔 수 없는 사정으로 firbase로 바꿔야한다고 한다. 이미 많은 기능들이 있고 로직들이 얽히고 설킨다면 MySQL에서 firebase로 교체하기엔 쉽지 않을 것이다. 방법이 있다면 MySQL 인스턴스를 전부 firebase 인스턴스로 변경한 뒤, 다른 얽히고 설킨 부분까지 바꿔주면 될 것이다. 물론 얽히고 설킨 부분을 찾고 그것을 일일히 바꿔주는것은 매우 힘든일 이라고 확신한다. 그렇다면 어떻게 하면 이 문제를 조금 더 쉽게 해결 할 수 있을까?
==> 이렇게 매우 힘들게 관련된 로직을 바꾸려고 하기 전에 어플의 구조를 클린 아키텍처의 목표(계층을 나누어 관심사를 분리)대로 구조화를 시키면 될 것이다.
■ 1-2. 안드로이드 Clean Architecture에 대해
[ 안드로이드에서 사용하는 클린 아키텍처와 오리지널 클린 아키텍처는 거의 비슷한데, 안드로이드에서는 Entity를 채택하지 않음 ]
- 안드로이드 클린 아키텍처
- 계층을 나누어서 관심사를 분리하고, 각 분리된 클래스가 한가지 역할만 할 수 있도록 구현하는 방식이다.
- Presentation, Domain, Data 로 3계층 아키텍처 기반이다.
- Presentation -> Domain , Data -> Domain 방향으로 의존성을 가지고 있다.
- 오리지널 클린 아키택처에서는 Entity 개념을 도입하지만 안드로이드 클린 아키택처에서는 Entity 개념을 도입하지 않는다.
그 이유는 오리지날 클린 아키택처의 Entity는 최상위 레벨의 정책을 수행하는 계층인데, 이러한 계층은 서버에서 수행되기 때문이라 생각한다.
■ 1-3. 안드로이드 클린 아키텍처에서 사용되는 계층
1. Presentation Layer (UI Layer)
Data 계층과 Domain 계층에 의존성을 가지고 있는 레이어이다. 화면과 입력에 대한 처리 등 UI와 직접적으로 관련된 모든 것들이 이 레이어에 포함된다.
- UI
UI를 표시하는 부분(Activity, Fragment) - Presenter
UI 업데이트와 관련된 로직을 구현(ViewModel)
UseCase를 사용한다. - DI
- Module
2. Domain Layer
독립적인 계층 및 의존성을 가지고 있는 않은 계층, 다른 계층의 변경이 Domain Layer에 영향을 끼쳐서는 안된다.
비즈니스 로직에 필요한 Data Model와 UserCase와 Repository를 포함한다.
- Usecases
- Repository를 사용한다.
- 서비스를 사용하고 있는 사용자(User)가 해당 서비스를 통해 하고자 하는 것을 의미한다. 즉, 행동들의 최소 단위이며 이름만 보고도 무슨 기능을 수행하는지 알 수 있어야 한다.
- UserCase를 사용하는 이유는 크게 2가지이다.
- 협업성 : ViewModel에서는 해당하는 UseCase를 파라미터로 전달받아 사용하게 되어 ViewModel에서 어떤 일을 하는지 파라미터만 확인해도 알 수 있다. 그렇기에 UseCase 이름은 직관적으로 어떤 것을 수행하는지 알 수 있도록 지어야 한다.
- 의존성 : ViewModel에서 UseCase를 파라미터로 전달받아 사용하게 되는데, 이는 협업뿐만 아니라 의존성을 줄일 수 있는 장점을 가지고 있다. 왜냐하면 만약 UseCase를 사용하지 않고 ViewModel에서 Repository를 전달 받아 사용하게 될때 갑자기 Repository가 수정이 된다면 많은 수정이 이루어져야 할 것이다. 하지만 UseCase를 사용하게 되면 영향 혹은 관계가 있는 UseCase 부분만 수정을 하기 떄문에 의존성이 줄어드는 장점이 있다.
- Ex) 쇼핑몰 어플에 들어가서 사용자는 검색 또는 쇼핑을 할 수 있는 다양한 행동을 수행 할 수 있을 것 이다. 이러한 사용자가 서비스에서 수행하고자 하는 것들이 UseCase라고 할 수 있다.
Usecase 사용 흐름
1. ViewModel에서 UseCase를 호출(실제로 사용하는 ViewModel에서 필요한 기능이 UseCase를 호출)
2. UseCase에서 RepositoryImpl(데이터 계층의 RepositoryImpl를 호출)
3. RepositoryImpl 에서 데이터를 Return(데이터 계층의 RepositoryImpl은 도메인 계층의 Repository 인터페이스를 구현하여 데이터를 return 한다.
4.UseCase에서 return받은 데이터를 ViewModel로 보낸다.
5.ViewModel에서 retrun 받은 데이터를 사용한다. - Model
필요한 데이터 - Repository(Interface)
관련된 행동들을 정의한다.
Domain Layer를 독립적으로 만들기 위해 Repository를 Interface와 구현체로 분리해야한다.
3. Data Layer
Domain 계층에 의존성을 가지고 있는 계층, 말 그대로 Data들을 control 하는 계층(CRUD)이라고 생각하면 편하다.
- DataStore
로컬 DB 또는 REST API 통신과 관련된 내용 - Entity
로컬 DB의 테이블을 만들기 위한 Entity와 서버 통신을 위한 Dto가 포함된다. - Repository(구현체)
Domain Layer의 Repository Interface의 실제 구현을 담당한다.
CRUD(DataSource)를 사용하여 실제 데이터를 가져온다. - Mapper
Entity -> Model, Dto -> Entity, Dto -> Model 등과 같이 데이터들의 형식을 변환한다.
Data 계층 데이터(받아오는 데이터 형태)와 Domain 계층 데이터(실제로 사용하는 데이터)로 변환해주는 클래스
■ 1-4. 안드로이드 클린 아키텍처의 계층에서 선언해야 할 것 정리
- Domain 계층
비즈니스 로직에 필요한 Model과 UseCase - Data 계층
입맛에 맞게 변경하기 전 데이터(API, Local) 모델과 CRUD 하는 역할
Domain의 Model과 서로 형변환 하기 위한 Mapper가 포함 - Presentation 계층
UI(Activity, Fragment) , ViewModel , DI, Module
■ 1-5. 안드로이드 클린 아키텍처에서의 의존성
1. Domain 계층은 의존성이 없다. ==> 자기가 바뀌면 영향을 줄 뿐 다른 사람의 변화에 영향을 받지 않는다.
[ Domain -> X ]
2. Data 계층은 Domain 계층의 변화에만 영향을 받는다. ==> Domain 계층의 클래스를 가져와서 사용한다.
[ Data -> Domain ]
3. Presentation은 Domain 계층과 Data 계층의 변환에 모두 영향을 받는다. ==> Data 계층이 Domain 계층에 의존성을 가지고 있기 때문에 Presentation 계층에서도 Domain 계층에 영향을 받는것이 아니라, Domain 계층에 선언된 UseCase를 Presentation 계층에서 ViewModel에서 직접 사용하기 떄문에 간접이 아니라 직접적으로 영향을 끼친다.
[ Presentation -> Data , Presentation -> Domain ]
■ 1-6. 안드로이드 클린 아키텍처에서의 햇갈리는점
[ 이 파트의 내용은 나의 상황과 너무나 비슷하여 다른 개발 블로거분의 글을 참고하여 작성하였다. ]
■ 1-6-1. Domain 계층과 Data 계층에서 사용하는 Data Model의 같을수도 다를수도 있다 ?
Domain 계층과 Data 계층에서 사용하는 Data Model이 항상 다르기 때문에 Data 계층에서 Mapper 클래스를 만들어 사용하는가 싶었다..
==> 그런데 받아오는 데이터와 실제로 사용하는 데이터의 차이에 따라 같을 수도 다를 수도 있다는 것이다.
간단하게 서버에서 id, title을 받아와 뿌려주는 기능의 앱이 있다고 하고, 받아오는 데이터의 경우의 수가 2가지 버전이 있다.
- 서버 API를 통해 id, title 두 개의 값만을 가져온다고 할 때
Data 계층에서의 Model과 Domain 계층에서는 동일하게 id, title 값을 가지도록 구현하도록 해야한다. 그러면 Mapper 클래스는 노느냐?? 아니다 Mapper 클래스에서는 그냥 그대로 전달해주면 되는 것이였다. - 서버 API를 통해 id,title,content,name 등등의 데이터를 가져온다고 할 때,
Data 계층에서의 Model에서는 API의 응답으로 가져오는 값을 모두 선언해야한다. 하지만 Domain 계층에서는 id, title 두 개의 값만이 필요하기 때문에 Mapper 클래스에서 Data(계층) to Domain(계층)으로 데이터를 넘길 때 필요한 데이터인 id, title만을 추출하여 넘겨주는 작업을 진행해야 한다.
■ 1-6-2. Domain 계층의 Model과 Data 계층의 Model은 어디서 사용되는가 ?
결과만 말해보자면, Data 계층의 Model은 Domain 계층에서만 사용되고, Domain 계층의 Model은 Presentation 계층에서도 사용된다.
==> Domain 계층의 Model은 실제로 사용하는 데이터 클래스라고 할 수 있다. 그렇기에 실제로 사용하는 곳은 UI의 변경에서 사용될 것이다. 그렇기에 Presentation계층에서 Domain 계층의 Model을 실제로 사용하여 동작하는 것을 알 수 있다.
■ 1-6-3. Data 계층의 Model은 어떻게 사용하는가?
Data 계층은 Data를 컨트롤 한다고 생각하면 된다.
DB(Local), 서버 API를 통해서 Data를 CRUD를 통해 필요한 데이터틀 저장하고 Mapper를 통해 Domain 계층의 Model로 데이터를 넘긴다. ( 그 후 Domain 계층의 Model로 사용되기 때문에 Data 계층의 작업은 끝난 것이다. )
■ 1-6-4. UseCase를 사용하는 방법은 해당 기능을 수행하는데 필요한 Repository를 사용하여 원하는 기능을 수행하도록 되는것이다. 그러면 UseCase를 사용하지 않고 Repository를 바로 사용하면 되는거 아닌가?
Repository를 사용하게 되면 수정이 필요할 경우에 많은 부분을 수정해야 할 수 있다.
==> 필요한 부분만 꺼내어 사용하는 UseCase를 사용함으로 변경을 최소화 할 수 있기 때문에 사용한다.
해당 Repository의 함수를 모두 사용한다고 Repository를 그대로 사용하게 되면, 어느 부분에는 UseCase를 사용하고 어느 부분에는 Repository를 사용하게 되어 유지/보수, 협업을 할 때 불필요한 시간이 소요될 가능성이 크기 때문이다.
즉, 일관성을 지켜서 구현하는 것이 위에서 말한 부분에서 이점을 발휘하기 때문에 UseCase를 사용하는 것이 좋다.
■ 2. 멀티 모듈이란 ? ■
[ 클린 아키텍처는 궁극적으로 계층을 나누어 관심사를 분리하는데 이때 계층은 Presentation, Domain, Data 계층이다. 이 계층을 나누기 위해서는 모듈을 여러개로 나누는데 이때 이것을 멀티 모듈이라고 한다. 멀티 모듈에 대해 알아보고 그 후 적용을 해보자. ]
■ 2-1. 멀티 모듈이란 ?
멀티 모듈을 설명하기전에 모듈이 무엇인지를 알아야한다.
모듈이란,
소스 파일 및 빌드 설정으로 구성된 모음이며 이를 통해 프로젝트를 별개의 기능 단위로 분할 할 수 있다. 프로젝트에는 하나 이상의 모듈이 포함될 수 있고, 하나의 모듈이 다른 모듈을 종속 항목으로 사용할 수 있다. 각 모듈을 개별적으로 빌드, 테스트 및 디버그 할 수 있다.
즉, 안드로이드 스튜디오에서 생각을 해본다면 새로운 프로젝트를 만들면 생기는 app도 모듈의 한 종류라고 보면 되는 것이다.
모듈의 종류를 많이 있지만 대표적으로 3가지를 말할 수 있다.
- Application
안드로이드 프로젝트를 만들 때 기본적으로 생성되는 app 모듈은 Application 모듈이다. 빌드의 결과로 APK 파일을 생성한다.
앱을 실행하기 위해선 Application 모듈이 반드시 필요하다. - Android Library
안드로이드 프로젝트에서 지원되는 모든 파일 형식을 포함할 수 있다. 다른 Appication 모듈의 종속 항목으로 추가할 수 있다.
빌드의 결과로는 AAR 파일이 생성된다. - Java or Kotlin Library
Java 혹은 Kotlin으로만 이루어진 모듈이다.
안드로이드 프레임워크로부터 독립적인 기능을 구현할 때 사용한다. 빌드의 결과로는 JaR 파일이 생성된다.
그렇다면 모듈 한 개로 사용하면 되는데 왜 멀티모듈이란 말이 나오고 이걸 적용을 시킬까?
1. 의존성 이점
MVVM 패턴을 적용하여 프로젝트를 구성하였다고 하자. 이때 MVVM 패턴을 적용한 프로젝트라면 ViewModel에서는 Repository를 통해 데이터를 가져오고, Repository에서는 데이터베이스에 접근해서 데이터를 가져오는 규칙을 지켜야하만 한다.
즉, ViewModel <- Repository
Repository <- 데이터베이스 라는 관계를 통해서만 이용을 해야한다는것이다.
하지만 개발자가 실수를 하여 ViewModel에서 Repository를 거치지 안하고 object(싱글턴) 형식인 데이터베이스 인스턴스에 바로 접근하여 가져온다면 MVVM을 적용시켰다고 보기 어려운 것이다.
이러한 문제가 발생되는 이유는 하나의 모듈로 프로젝트가 구성되어 있기 때문에 모든 코드에 접근할 수 있어서 생기는 문제인 것이다.
그렇기에 관심사를 분리하기 위해선 의존성 규칙을 잘 지켜주어야 하는데 이것을 모듈을 여러개로 나눈 멀티 모듈을 사용하여 해결 할 수 있다는 것이다.
[ 멀티 모듈 프로젝트에서는 build.gradle 파일에 사용할 모듈의 의존성을 직접 추가해줌으로써 사용하지 않는 모듈들을 접근조차 할 수 없게 만들 수 있다. ]
2. 빌드 속도 이점
프로젝트의 규모가 커지면 빌드 속도가 늘어나는 것은 당연한 것이다. 하지만 멀티 모듈 구조를 이용한다면 프로젝트를 빌드할 때 변경된 모듈만 빌드하기 때문에 모듈이 많을 수록 빌드 시간이 단축되는 이점을 얻을 수 있다.
3. 코드 재사용성 이점
만약 Application 모듈이 여러 개라면 코드 재사용성에 대해서 이점이 있다.
예를 들어 모듈이 한 개 인 곳과 모듈이 여러 개 인 곳과 프로젝트를 진행한다고 치면,
모듈이 한 개인 곳은 어떠한 곳에서 기술을 사용한다고 치면 그 기술을 사용하는 곳에서 복사 및 붙여넣기를 통해 사용할 곳에다가 코드를 통째로 가져올 것이다. 이때 문제점이 어떠한 기술에 대해 문제점 혹은 바꿔야 할 점이 있어 코드를 수정해야 한다고 햇을때는 이 기술을 사용한 모든곳을 손봐야한다는 단점이 있다.
모듈이 어러 개 인곳은 어떠한 기술을 모듈화를 시키고 그 모듈을 이용한다고 했는데 모듈화 한 기술이 문제점이 생겨 수정이 되어야 한다고 했을때 기술을 모듈화를 시킨곳만 수정하면 이 기술을 사용한 다른곳도 적용이 되기 때문에 문제가 없는것이다.
그렇기에 재사용성 측면에서 이점을 발휘한다.
[ 처음 멀티 모듈을 사용을 할 때 클래스와 디렉토리가 많아지기 때문에 원하는 파일을 찾을때 디렉토리 사이에서 해맬수 도 있다는 것이다. 즉.. 어색하고 어려울 수 있다는 점이다. 물론 멀티모듈을 사용하는 것에 대한 어려운 점도 물론 있지만 이것을 적용을 하여 프로젝트를 만들게 되면 추후에 유지보수 측면과 프로젝트 규모가 커지게 되면 오히려 다른 사람들이 알아보기 쉽다는 장점이 있기때문에 꼭 익숙해져 할 것이다. ]
■ 2-2. 안드로이드 스튜디오에서의 멀티 모듈 생성
안드로이드 스튜디오에서 새로운 모듈을 생성하고 싶다면 아래의 방법을 통해 생성하면 된다.
■ 3. Clean Architecture 파헤치기(feat, Movie App) ■
나는 Clean Arcitecture 구조를 조금 구체적으로 공부하기 위해 적용이 잘 된 프로젝트를 주석을 달면서 파헤쳐보기로 했다.
이 프로젝트는 Movie를 보여주는 App인데, 프로젝트 구조가 깔끔하였고, 복잡하지 않아(영화를 보여주는 기능 뿐)
KennethSS님의 Movie App 프로젝트를 fork를 하여 파헤쳤다.
대략적인 구조는 app모듈, feature모듈, shared모듈(data,domain,remote모듈)이 있었다.
- app모듈에 관해서는,
의존성 주입 라이브러리인 hilt를 사용하기 위한 di 관련 로직들과 ui에 관련된 로직들을 볼 수 있다. 그렇기에 Presentation 계층에 속한다고 볼 수 있다. - Feature모듈에 관해서는,
viewModel에 관련된 로직들이 있어 이것도 Presentation 계층에 속한다고 볼 수 있다. - shared모듈에는 data,domain,remote 모듈이 들어가 있다
- remote 모듈의 살펴보게 되면,
전반적으로 retrofit2와 OkHttp3를 이용을 하여 직접적으로 데이터를 가져오는 부분을 담당한다고 보면 된다. 이 부분은 어떻게 보면 data모듈의 한 일부분으로도 생각 할 수 있다. - data 모듈을 살펴보게 되면,
Repository 인터페이스를 구현한 구현체라고 볼 수 있다. 또한 DB로부터 받아온 데이터 모델을 Mapper를 통해 Domain 계층의 데이터로 넘기는 역할을 하는것을 볼 수 있다. - domain 모듈을 살펴보게 되면,
비즈니스 로직에 필요한(실제로 UI에 적용할) Data Model과 UseCase와 Repository 인터페이스 역할을 하는 것을 볼 수 있다.
- remote 모듈의 살펴보게 되면,
프로젝트의 모듈간 흐름을 알기 위해서 hilt라는 의존성 주입(DI)이라는 라이브러리도 함께 알아야 하는데, 이 부분은 내가 이해한 것을 토대로 엄청 구체적으로 주석처리를 통해 달아놓았다.
[ 주석을 보고 싶은 사람은 아래의 깃허브를 참고해서 읽으면 좋겠다.. 틀린부분도 많이 있을수도 있으니 알아서 잘 추려서 읽어라.. 내가 이해하고 쉽게 정리해서 쓴 것이다.. ]
참고