반응형
아키텍처는 왜 중요한가? (MVC, MVP, MVVM, Clean Architecture, ViewModel, 모듈) 알아 보자!
아키텍처는 왜 중요한가?
모바일의 특징을 고려
- 네트워크적 특징을 고려
- 사용자 이벤트
- 다른앱들의 실행. 전화 문자 등등
- 반응형 프로그래밍 (코루틴, RX Java 등)
- 이런것들의 동시에 일어난다.
- 빠른 배포가 필요
비지니스의 확장을 위해서
- 모듈화가 필요하고 확장에 유연해야한다.
- 실험적인 기능을 가설을 세우고 특정유저에게 보낸다 → 좋은 아키텍처가 뒷받침
- Network Drivien UI → 비지니스 모델에 따라 지원해야할수도 있음
테스트를 위함
- 확장성이 좋은 앱, 모듈간의 커플링이 낮음 → 결국 테스트하기 좋은 앱
아키텍처는 답이 없음 (중요!)
- 앱에 실질적으로 도움이 되는것을 고민한다.
- 정해진 정답이 있는 것이 아님
- 각 아키텍처별로 몇가지 중요한 원칙에 기반해서 우리의 비지니스 모델에 맞게 구현하면 됨
→ 아키턱처를 이해해야 위와 같은 것들을 지원하기에 유리하다.
→ 즉 앱을 좀 더 효율적으로 만들고, 결과적으로 비지니스의 성공 위해 필요함
좋은 아키텍처와 복잡성
어떤설계가 좋은 설계인가?
- 이해하기 쉬워야한다.
- 버그가 발생했을때 쉽게 파악하고 쉽게 수정할 수 있어야한다.
- 비지니스 요구사항에 맞게 쉽게 변경 가능해야한다.
- 테스트하기 용이한 구조여야 한다.
- 성능을 좋게 만들 수 있어야한다.
좋은 아키텍처의 방해물은 복잡성이다.
- 복잡성이란 시스템을 이해하기 어렵고 수정하기 어렵게 만드는것.
- 복잡하지만 않으면 어떤 구조던지 상관없음
왜 복잡해질까?
- 강한 의존성은 복잡하게 만드는 원인이다. → 최소화 할 필요가 있음
- 불명확성 확실하지 않은 것 → 기술부채
어떻게 복잡성을 낮출수 있을까?
- 추상화
- 알필요가 없는 불필요한 정보를 감춘다.
- 깊은 모듈, 범용 인터페이스
- 캡슐화
깊은 모듈
- 사용자는 인터페이스만 알면 된다.
- 실제 작업은 깊은 모듈에 걸쳐서 일어나지만 공개되지는 않는다.
- 잘 나눠서 깊게 만든다.
얕은 모듈
- 반면에 얕은 모듈은 여러가지 모듈에 걸쳐서 사용자가 계속해서 호출해야함
- 사용자가 많은 것들을 알아야할 필요가 있음
- 결국 의존성 결합도가 강해질 수밖에 없다.
범용 인터페이스
- 범용으로 만들면 지금 비용은 들겠지만 추후 비용은 낮다.
- 특정으로 만들면 지금은 금방해도 추후 비용은 높다.
- 이 메소드가 얼마나 많은 상황에서 사용될수 있는가? → 많다면 범용.
정보 은닉
- 일반적인 케이스를 최대한 심플하게 만든다.
- 불필요한 정보를 감춘다. 퍼블릭, 프라이빗 을 잘 구분해서 사용한다.
- 정보누출 → 같은 정보가 여러곳에서 사용되고 있다.
개방 폐쇄 원칙
- 확장에는 열려있고 변경에는 닫혀있다.
- 데이터구조에 필드를 추가하거나, 함수를 추가한다거나 하는 것에 열려있어야함.
- 멤버변수를 변경한다고 해서 다른 사용자가 영향을 받아서는 안된다.
- 내부코드를 변경한다고 해서 외부(사용하는 측면) 에서 영향을 받아서는 안된다.
글로벌 변수에 의존
- 많은 클래스에 영향을 받게됨
- 변경에 대해서 닫혀있는가? 아니다.
Abstract, Interface
- 사용자에게 필요한 것만 노출해서 폐쇄원칙을 만족시키고
- 구현클래스의 내용도 감춤으로서 수정도 용이해진다.
모뷸단위 개방 폐쇄 원칙
- 의존관계 방향이 보호하려는 컴포넌트를 향하도록 그려진다.
- 왜 RepositoryImpl 을 만드는가? → 의존성 역전원칙과도 관련있음
- 위와 같이 구성하지 않았다면, 유즈케이스가 데이터소스까지 의존성을 계속해서 물고가게됨
MVx
- 어떤 경우이든 모델은 분리된다.
- 뷰의 역할을 할수 있는한 최대한 분리시켜야한다는것은 모두가 알고 있음
MVC
- 개념자체는 위와 같음
- Android 뿐 아니라 여러 플랫폼에서 적용되는 모델
- 그러나 Android 에서는 View 와 Controller 의 분리가 쉽지 않다.
- 결국 Activity 또는 Fragment 가 비대해지는 문제가 생긴다. → 관심사의 분리를 지키지 않았다.
- 위 그림처럼 구성하면 문제를 해결할 수 있다.
- 그러나 뷰 컨트롤러를 어떻게 액티비티의 라이프사이클과 연결할 것인가의 문제가 남는다.
MVP
- IBM 에서 최초 구현, 마틴파울러의 소개로 알려짐
- 안드로이드 초창기부터 전해져온 아키텍처
- 뷰는 비지니스 로직에 관련된 부분을 관여하지 못하도록 분리한다.
- 모든 UI 관련된 비지니스 로직을 프리젠터에서 처리
- 뷰는 프리젠터의 요청에 따라서만 수동적으로 화면의 변화를 처리하게 된다.
- 개방폐쇄의 원칙. 뷰보다 상위 수준인 프리젠터를 뷰의 변경으로부터 보호한다.
- 보호할 수 있는 것은 아래와 같은 컨트랙트 인터페이스 패턴을 이용한다.
- Contract 에 정의한 인터페이스를 통해서 뷰와 프리젠터간의 직접적인 연결을 끊는다.
- 너무 상세한 로직은 뷰가 알고 있는것을 극복하기 위해 UIStateHolder 를 만들어 사용할 수 도 있다.
장점
- 액티비티가 거대해지는것을 막는다.
- 테스트 가능성이 증대된다. 프리젠터는 JVM 내에서 테스트가 가능해짐
- 구조가 직관적이고 컨트랙트만 봐도 금방 구조를 알수 있음
- 반응형 라이브러리를 쓰지 않아도 된다.
단점
- 뷰의 기능이 명령형일때 잘 동작한다.
- 기본적으로 뷰가 프리젠터를 참조하고 있다.
- JetPack Compose 와 같이 선언형뷰에는 적용하기 어렵다.
(참고) 선언형과 명령형
- 명령형은 어떤화면을 보여줘, 어떤 이벤트일떄 어떤 로직을 실행해
- 선언형은 어떤 상태일때 어떤 것을 보여줄지 선언만 해둠
- 그럼 상태가 변경되면 알아서 그상태에 맞는 뷰가 보여진다.
MVVM
- MS 에서 처음 구현함
- 마틴파울러의 프리젠테이션 도메인분리가 시초 (PDS)
- 프리젠터와의 차이점은 뷰모델이 뷰를 제어하지 않는다는점
- 뷰는 뷰모델을 호출하지만 결과는 콜백, 옵저버블형태로 받는다. RX
단방향 데이터 흐름
- 상위객체는 하위객체로부터 상태를 읽을수 없다.
- 뷰는 사용자 입력을 전달만 할뿐 직접적으로 결과를 받지 않음
- 이벤트 형태로 상태 변경을 통보받아 화면을 업데이트한다.
- 뷰모델은 어떤뷰가 이것을 받는지 모른다. 의존하지 않는다.
데이터 바인딩
- 이벤트를 뷰로 바로 적용할수 있는 메커니즘을 제공한다.
- 각종 작은 이벤트에 대한 뷰모델 처리 옵저버의 코드양을 줄일 수 있다.
안드로이드 아키텍처 가이드
https://developer.android.com/topic/architecture?hl=ko
- 안드로이드에서는 권장하는 아키텍처 가이드를 계속해서 업데이트해주고 있음
- 여기서 StateHolders 는 ViewModel 이 될 수 있다.
- 따라서 이 모델은 MVVM 모델에 가깝다고 볼 수 있다. (Google 에서 MVVM 을 언급하지는 않음)
단점
- 반응형에 대한 처리가 필요하다. RX 자바, 코루틴 등
- 뷰모델이 커지는 것을 막을 수 없다.
- 순환적인 이벤트 흐름때문에 발생하는 보일러플레이트 코드들
장점
- 기존의 Activity, Fragment 구조를 유지하면서
- 재사용성과 생산성 높은 아키텍처를 구현할 수 있다.
- 안드로이드의 생존주기 처리에 가장 잘 어울린다.
Viewmodel in AAC
- 생명주기 내에서 설정변경, 프로세스 종료가 일어나도 뷰모델의 내용이 보존되는 구조를 제공한다.
- Flow, LiveData, Compose State 가 보존될수 있도록 하는 SavedStateHandle 을 제공한다.
Pluu Dev - SavedStateHandle을 다뤄봅니다
- Coroutines 가 뷰모델의 생명주기 내에서 동작할수 있는 환경을 지원한다.
- androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
ViewModel 사이의 상태공유
- 로딩같은것을 화면전체에서 처리한다고 하면 액티비티 뷰모델에서 처리할수도 있음
어떻게 잘 구현할 것인가?
- 뷰, 뷰모델을 최대한 분리한다.
- 뷰모델에서 화면로직이 아닌 도메인 로직에 해당되는 부분을 도메인 계층으로 옮긴다.
- 아래코드에서 잘못 구현한것은?
ViewModel 생성자에서 초기화 이상의 일을 해도될까?
- 리스트를 로드한다던가 하는 등의 일을 해도 되나 NO
- 단일책임원칙 위반 init 은 init 일뿐
- 생성자에서 하는것은 좋지 않은 일
- 리스트를 로드할지 안할지는 뷰의 역할
Clean Architecture
모바일 클린 아키텍처 이론은 사실 존재하지 않는 개념
- 클린 아키텍처를 모바일에서 최대한 구현하기 위해서 노력해본 합의의 결과물
- 엉클 밥의 클린아키텍처를 그대로 모바일로 가져갈수는 없음
선을 긋는 기술
- 소프트웨어 아키텍처는 선을 긋는 기술
- 이러한 선을 경계라고 부르며,
- 경계로서 요소를 서로 분리하고, 반대편에 있는 요소를 알지 못하도록 막는다.
- 경계선의 구분 → 단일 책임 원칙
도메인 계층
- 유즈케이스
- 가장 높은 수준의 계층 (과녁의 가운데)
- 비지니스의 요구사항을 구현하고 있음
모바일에서의 그림
- View
- 직접적으로 플랫폼 의존적인 구현
- 사용자 이벤트 UI, Context 등
- Presenter
- OS 렌더링 API 에 의존하지 않음
- 뷰 관점에서의 비지니스 로직을 담당
- UseCase
- 도메인 계층의 비지니스 로직
- 기획자 관점에서 구현이 가능한 로직
- Model
- 도메인 모델
- Translator
- 데이터계층의 엔티티와 도메인의 모델을 변환하는 역할. Mapper
- Repository
- 유즈케이스가 필요로 하는 데이터 처리에 대한 기능을 제공
- 데이터소스를 인터페이스로 참조하기 떄문에 여기서 remote, local, mock 을 전환할 수 있음
- Entity
- 데이터 소스에서 사용되는 데이터를 정의한 모델
3가지 레이어에 속하지 않는 것들
- App, Common 레이어를 만들어서 관리
- Service, Hilt DI, 앱클래스, 등을 관리
참조 : 스터디 그룹
반응형
'안드로이드' 카테고리의 다른 글
[Android] Custom NotificationView(RemoteViews)에 대해 알아 보자 (0) | 2024.03.26 |
---|---|
[Android] 안드로이드 잠금 화면, 슬립 깨우기 (0) | 2024.03.26 |
[Android] 루팅이란? 루팅 체크 방법 (0) | 2024.03.22 |
[Android] AudioFocus 관리, MediaPlayer, AudioManager (0) | 2024.03.20 |
[Android] DiffUtil에 대해 알아보자 (0) | 2024.03.20 |