본문 바로가기

IT/안드로이드 관련

[안드로이드] Clean Architecture

소개

안녕하세요 YTS 입니다. 오늘은 많이 부족하지만

Clean Architecture라는 주제를 가지고 글을 한번 적으려고 합니다.

Clean Code!

우선 Clean Code란 무엇일까?

결국 원작자의 의도가 무엇이며 코드를 볼 때

얼마나 가독성이 좋은지에 대한 여부가 가장 중요하다고 생각합니다.

즉 같이 협업하는 팀원들이 이해하기 쉽게 작성하는 코드인 것 이죠.

Clean Code에 정리는 아래의 주소로 이동하시면 남갯이 잘 정리해놨습니다.

순서대로 잘 참고하시면 될것같아요!

 

https://namget.tistory.com/entry/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-2%EC%9E%A5-%EC%9D%98%EB%AF%B8-%EC%9E%88%EB%8A%94-%EC%9D%B4%EB%A6%84?category=786713

 

Clean  Architecture

Clean  Architecture

 Clean Architecture는 UI와 Database를 분리하여, 외부적인 설정에는 독립적이며 프레임워크에 의존적이지 않은 공통적인 코드를 짤 수 있다고 생각합니다.

 

 각 레이어들은 (Presentation, Data, Domain)에 의존성은 안쪽으로만 향하도록 구현하여야 합니다.

각 레이어에 대한 의존성을 낮춤으로써, 아래와 같은 장점이 있습니다.

  • 결합성이 낮아지며,

  • 각 레이어의 쉬운 테스트

  • 구조를 파악하면 쉬운 유지보수

간단한 예로 다른 Android App 개발자가 UI 수정을 위해 변경되어야 하는 부분은 Presentation Layer쪽을 파악하고 수정하면 됩니다.

반대로 Network 통신부분의 쿼리나 헤더가 변경됐다면 Data Layer쪽을 파악하고 수정하면 되겠지요.

결국 Clean Architecture도 코드를 잘 분리하기위한 기법인 것 같습니다.

 

 필자는 MVVM 기준으로 작성하겠습니다.Model + View + ViewModel로 구성된 MVVM 패턴은 Clean Architecture에서 Model은 Domain Layer와Data Layer부분을 의미합니다.

 

 그렇다면 그러한 Layer들은 뭐냐?

위에 이미지를 보면 총 3가지의 Layer로 나뉘어져있습니다.

사용자에게 보여지는 Presentation Layer

순수한 Entity와 Data Layer와 Presentation Layer를 연결해주는 UseCase를 갖고있는 Domain Layer

네트워크와 로컬 데이터를 가져오는 Data Layer로 구분되어 있습니다.

 

이제 각 레이어를 좀 더 자세히 살펴보도록 하겠습니다.

Domain 레이어

 Domain 레이어는 Android 프레임워크에 의존하지 않는 순수한 Java 혹은 Kotlin 모듈입니다.

Domain 레이어는 Entity, Repository(행동들을 담고있는), usecase(행동들의 최소 단위)를 담고있습니다.

UseCase

class LoginUseCase(
    private val userRepository: UserRepository
) {

    fun login(loginId: String, passwd: String): Observable<Any> {
        return userRepository.login(loginId, passwd)
    }

}

 실제 사용자가 하는 일련의 행동들을 나열하는 UseCase입니다. (위의 예제는 로그인을 하는 UseCase)

UseCase는 정말 작은 단위로 나뉠수 있습니다.

예를 들면 사용자를 가져오는 UseCase, Token을 가져오는 UseCase, 로그인을 하는 UseCase와 같이 클래스의 양은 많아지는 단점이 있지만 전체적인 코드 파악과 의존성이 낮아지므로 유지보수에 용이해지는 장점이 있습니다.

마지막으로 Usecase를 구성할때는 어떤 데이터베이스를 사용했는지에 대한 고민을 하지 않고 Domain 레이어에서 정의한 적당한 Repository를 주입하여 구현하므로 코드의 흐름을 파악하기 쉬워집니다.

Repository

interface UserRepository {

    fun getLoginUser(): Observable<User?>

    fun saveUser(user: User)

    fun login(loginId: String, passwd: String): Observable<Any>

}

   Domain레이어에서 User와 관련된 행동을 Interface로 정의한 UserRepository입니다.

여기서 Respository는 Data 레이어와의 연결점이 됩니다. 

Entity

data class User(
    var loginID: String,
    var passwd: String? = "",
    var adminCd: Int? = null,
    var adminNm: String? = null,
    var approve: Int? = null,
    var useType: Int? = null,
    var agentCd: String? = null,
    var loginChk: String? = null,
    var loginCnt: String? = null,
    var d2dPopup: String? = null,
    var pwdExpDt: String? = null,
    var roles: String? = null,
    var role: String? = null,
    var token: String? = null,
    var hphoneNo: String? = null
)

 Entity는 프레임워크와 의존성을 가지면 안됩니다. 즉 다른 프로젝트에서도 동일하게 사용할 수 있는 클래스를 작성합니다.

로그인에서 사용한 Domain 레이어 부분입니다.

ex) 난 로그인할거야! 라고하면 ViewModel에서 LoginUseCase를 주입받아 사용하면 되고, User 정보를 가져올꺼야! 하면 GetUserUseCase를 주입받아 사용하면된다. 이런식으로 한눈에 코드가 구조파악이 된다.

Data 레이어

 Data 레이어는 Domain 레이어에서 설계한 Repository를 실제 구현(Network 통신 / 로컬데이터)하며,

Data를 가져오기위한 Remote API, LocalCache(Room, Sharedpreferences), Entity와 Data Model의 Mapper 같은 클래스도 위치 합니다.

여기서 Data Source에서 프레임 워크에 의존성이 생기게 됩니다.

 UserRepository를 상속받아 정의한 모습입니다.

저는 Remote API는 Retrofit2를 사용하였으므로 Retrofit을 주입받아 login을 구현했습니다.

LoginResponse으로 User 데이터뿐만 아니라 Token 값도 넘어오므로 그에 맞게 데이터를 넘겨 구현했습니다.

 

 저는 데이터베이스로 Room을 사용했습니다. 실제 로그인후 유저정보를 저장하고 불러오는부분을 처리했습니다.

여기서 중요한점은 각 데이터 모델과 도메인 엔티티의 변환 작업입니다.

 Domain 레이어에선 데이터 모델을 모르므로, 리턴타입은 엔티티로 Mapping을 시켜줘야합니다.

간단하게 Kotlin의 확장 함수 기능으로 Model to Entity / Entity to Model을 구현했습니다.

 로그인에서 사용한 Data레이어 부분입니다.

Repository, DataSource, Data Model, Mapper등으로 구성된 모습입니다.

Presentation 레이어

마지막으로 프리젠테이션 레이어는 UI 레벨의 레이어고 Android 의존성이 높습니다.

저는 MVVM 패턴에 따라 View+ViewModel의 구조로 구현 했습니다.

View (Activity / Fragment)에선 UI와 관련된 부분을 처리하며 ViewModel에서 Domain 레이어의 알맞는 UseCase를 꺼내와 사용합니다.

DI(의존성 주입)은 Koin을 사용하여 구현 했습니다. Repository, UseCase, ViewModel 등과 같은 의존성주입을 담당하는 라이브러리 입니다

그렇다면 왜 ViewModel을 사용할까요?

ViewModel은 Android 공식 문서에

라이플 사이클을 고려하여 UI를 관리합니다. 데이터 화면 회전과 같은 구성 변경이 일어나도 데이터를 보관하고 유지합니다.

위와 같은 이유하나만으로도 사용이유는 충분하다고 봅니다. +LIVE DATA

LoginViewModel에선 login기능 로그인을 했던 유저데이터, 세션을 확인하기위한 Token을 가져오기 위해 UseCase를 주입하여 구현했습니다.'

즉 ViewModel에선 알맞은 UseCase를 꺼내와 사용하면 끝!

위와 같이 로그인이 필요한 시점에서 LoginUseCase를 이용하여 구현한 부분

 

결론

 이러한 구조를 지향한다면 변화의 대한 위치가 명확해지므로 생산성과 유지보수를 증대 할 수있습니다.  저는 좋은 코드의 구조가 좋은 제품을 이어진다고 생각하기 때문에 이런 아키텍쳐를 적용하여 구현했습니다.

또한 진입 장벽보다 유연성이 높습니다.

향후 확장 가능하고 추가적인 GS프로그램에 모바일 버전이 나온다면 

Domain레이어와 Data레이어를 그대로 이용하고 추가적인 Presentation 레이어만 디자인 가이드에 맞게 구현하면 됩니다.

 

위와 같은 장점으로 잘 분리된 아키텍쳐의 중요성을 한번 더 깨닫습니다.

 

최대한 알맞은 형식의 아키텍쳐를 구현하려고 노력했습니다. 제가 틀린 부분도 분명 존재할 수 있습니다.
또한 비판이 아닌 더 나은 방향의 토론이라면 적극 환영입니다!