클린 아키텍처로 프로젝트를 진행하면서 겪었던 많은 고민을 공유하기 위해 글을 작성하게 되었습니다. 늘 그랬듯 많은 관심과 지적 부탁드립니다.
안드로이드 앱 개발을 시작하던 초기에, 같이 프로젝트를 진행하면서 멘토였던 선배로부터 디자인 패턴과 함께 아키텍처라는 개념을 처음 접하게 되었습니다.
당시 RecyclerView조차 제대로 이해도 못하는 수준이었고, 모듈을 나눠서 관리하구나! 정도로 이해했습니다. 클린아키텍처의 ㅋ자도 몰랐던 거였죠..
클린 아키텍처에 대한 좋은 글들이 매우 많습니다. 안드로이드에서의 클린 아키텍처를 한마디로 말하자면 관심사의 분리라고 할 수 있습니다.
사실 한마디로 말하기에는 너무 크고 심오한 내용인 것 같습니다.
초기 안드로이드 개발을 시작할때 진행했던 프로젝트의 구조가 위 그림과 같았습니다.
기본적으로 주어지는 app 모듈 내에, domain, Data, Presentation 패키지로 나누었습니다.
Domain Layer를 한번 살펴볼까요?
public class ConvenienceUseCase implements RetrofitOnSuccess {
private ConvenienceMarkerRepository convenienceMarkerRepository;
private ConvenienceFragment convenienceFragment;
private ArrayList<Document> list = new ArrayList<>();
public ConvenienceUseCase(ConvenienceFragment convenienceFragment){
convenienceMarkerRepository = new ConvenienceMarkerRepository();
this.convenienceFragment = convenienceFragment;
}
public void sendLocalName(String Category){
...
먼저 Repository를 멤버변수로 참조하고 있기에 객체 간 결합도가 강한 것을 확인할 수 있습니다.
또한 안드로이드 프레임워크에 의존성이 없어야 하고, Presentation에 대해 몰라야 하는데 Fragment를 알고 있습니다.
부분적인 코드 뿐 아니라, 전체적으로 보더라도 단일 모듈로 클린 아키텍처를 적용하게 되면서, 클린 아키텍처의 약속이 잘 지켜지지 않고 있었습니다.
최근 진행한 프로젝트에서는 위와 같은 구조를 설계했습니다. 먼저 Multi Module로 구성되어 있기 때문에, 각 모듈이 가지는 종속성을 구분할 수 있습니다.
Clean Architecture을 기반으로 설계한 모듈 구조이므로, 아래의 의존성 관계를 가집니다.
Presentation -> Domain <- Data
Domain은 다른 레이어를 몰라야 합니다. 또한 안드로이드에 대한 종속성을 가지면 안됩니다.
Domain 단에 작성한 코드는 당장 다른 플랫폼으로 옮기더라도 사용 가능할 수 있을 정도여야 한다. 따라서 순수한 Java,Kotlin 언어로 작성되어야한다.
interface UserRepository {
...
suspend fun getUser(query: Int): Flow<PagingData<UserModel>>
...
}
Domain/Repository에 있는 UserRepository
인터페이스 입니다.
User의 행동 단위를 모아 인터페이스로 선언하게 되고, Data Layer에서는 이를 구현하여 네트워크 작업이나 DB 작업을 하게 됩니다.
💁♂️혹시 코드에서 이상한 점을 느끼셨나요? 맞습니다. Domain Layer에 안드로이드 라이브러리인 PagingData
가 있습니다.
물론 Data단에서는 PagingData
를 다루고 성공 실패에 대한 로직을 처리하여, Wrapper로 감싸서 전달할 수도 있습니다. 다만 그것은 네트워크 통신을 담당하는 Data Layer에 어울리지 않다고 생각했습니다.
테스트 종속성을 추가하거나, flow로 감싸서 전달하는 방법도 있으나, ViewModel단에서 성공 실패를 처리하기 위해 위 방법을 사용했습니다.
또한 이런 이야기를 들었습니다.
아키텍처는 결국 협업을 위해 존재하는 것이다. 타당한 이유가 있다면, 어느 정도 틀 안에서 같이 일하는 동료들과의 협업을 위해 어떠한 룰을 만들어 정하면 되는 것이다.
안드로이드의 특성상, 아키텍처를 통해 완전히 코드를 분리하는 것은 어렵다고 생각하였기에, 특수한 경우에는 의존성을 주입하기로 결정하였습니다.(ex hilt
)
한번이 어렵지,, 두번은 어렵지 않았습니다.
SkyDoves에서 제공하는 Sandwich의 ApiResponse
를 사용하여 결과 콜백을 처리할 수 있게 하였습니다.
interface UserRepository {
suspend fun login(loginModel: LoginModel): ApiResponse<AuthResponse>
}
loginUseCase(LoginModel(email, password.sha256())).suspendOnSuccess{
...
}
안드로이드 의존성이 아니며 Result class를 따로 만들지 않아도, 거의 모든 통상적인 경우를 처리할 수 있기에 충분히 도입할 만 하다고 생각하였습니다.
ApiResponse is an interface to construct standardized responses from Retrofit calls.
클린 아키텍처는 관심사의 분리를 통해 테스트가 용이하게 하고, 유지보수가 용이할 수 있게 계층을 나누어 코드를 분리하는 것이라고 할 수 있습니다.
코드라는것이 개발자마다 다양하고, 생각하는 기준도 다르기에 각각 다르게 적용될 수밖에 없다고 생각합니다.
물론 Uncle Bob이 의도하는 클린아키텍처의 성격에서 크게 벗어나면 안되겠지만, 협업하는 공동체에서의 룰을 지키며 적절히 사용한다면 되는 것이 아닐까 생각합니다.
긴 글 읽어주셔서감사합니다
어려워요....... 너무 어려워요.... 그래도 클린코드의 ㅋ자는 알것같습니다 ㅋㅋ