Android

[안드로이드] 아키텍처 원칙

나맘임 2023. 5. 9. 02:14

아키텍처 원칙에 관하여

 

아키텍처란?

시스템의 구조, 행위, 더 많은 뷰를 정의하는 개념적 모형이다. 시스템 목적을 달성하기 위해 시스템의 각 컴포넌트가 무엇이며 어떻게 상호작용하는지, 정보가 어떻게 교환되는지를 설명한다.

출처 : 위키 백과


 

아키텍처는 어떤 시스템의 구조라고 할 수 있다.

 

안드로이드에선 아키텍처는 앱이 확장과 견고성을 유지하고 쉽게 테스트할 수 있도록 정의하는 것이 주요 목표이다.

 

즉, 아키텍처는 앱의 부분과 그 각 부분에 필요한 기능 간의 경계를 정의한다.

 

위 주요 정의를 지키기 위해 다양한 방법이 있지만 안드로이드 공식 문서에선 다음과 같은 원칙을 준수하라고 적혀있다.

 


1.  관심사 분리

컴퓨터 프로그램을 구별된 부분으로 분리시키는 디자인 원칙으로, 각 부문은 개개의 관심사를 해결한다.

출처 : 위키 백과

 

 

가장 중요한 원칙으로 쉽게 말해, 어느 한 클래스는 주된 한 기능만 가지고 있는 것을 의미한다.

 

처음 공부시에 생각보다 지키기 어려운 원칙으로

 

한 액티비티나 프래그먼트에 모든 코드를 작성했을 때 관심사 분리에 실패했다고 말할 수 있다.

 

UI 기반 클래스는 UI 및 운영체제 상호작용을 처리하는 로직만 포함해야한다.

 

이러한 클래스를 최대한 가볍게 유지하므로써 생명 주기와 관련된 많은 문제를 피하고

 

테스트를 쉽게 할 수 있게 된다.

 


2. 데이터 모델에서  UI 도출하기

 

공식 문서에선 데이터 모델은 지속적인 모델로 구성할 것을 권장한다.

 

데이터 모델은 앱의 데이터를 나타내고, 프래그먼트나 액티비티와 같은 UI 요소나 기타 구성요소부터 독립되어 있어야한다.

 

앱의 데이터는 당연하게도 UI와 앱 구성요소, 생명 주기와 관련이 없어야한다.

 

하지만 운영체제가 메모리에서 앱의 프로세스를 삭제하기로 결정하면 데이터 모델도 삭제된다.

 

다음과 같은 이유로 지목적인 데이터 모델이 좋다.

  • 운영체제가 리소스를 확보하기 위해 앱을 제거해도 사용자 데이터가 삭제되지 않는다.
  • 네트워크 연결이 취약하거나 연결되어 있지 않아도 앱은 계속 동작한다.

 

데이터 모델 클래스를 기반으로 아키텍처를 구축하면 앱의 테스트 가능성과 견고성이 더 높아진다.

 

보통 Repository 클래스와 Data Sources 클래스로 구성되어 있으며

 

Repository 클래스는 Data Sources을 관리하여 데이터에 접근할 수 있도록 한다.

 


3. 단일 소스 저장소

 

앱에서 새로운 데이터 유형을 정의할 때는 데이터 유형에 단일 소스 저장소(SSOT)를 할당한다.

 

위에서 설명한 Repository 클래스와 비슷하며 흔히 알려진 것으로는 ViewModel 패턴이다.

 

이 SSOT는 데이터의 소유자이자 관리자로 SSOT를 통해 데이터를 수정하거나 변경이 가능하다.

 

이 패턴의 장점은 다음과 같다.

  • 특정 유형 데이터의 모든 변경사항을 한 곳에서 관리한다.
  • 다른 유형이 조작할 수 없도록 데이터를 보호한다.
  • 데이터 변경사항을 쉽게 추적할 수 있다

 


4. 단방향 데이터 흐름

 

단일 소스 저장소와 같은 맥락으로 상태(State) 는 한 방향으로만 흐르고 데이터 흐름(Data Flow)를 수정하는 이벤트는 반대 방향으로 흐름을 의미한다.

 

즉, 상태(데이터)는 상위 계층에서 하위 계층으로, 그걸 수정하는 이벤트는 하위 계층에서 트리거되어 해당 데이터의 SSOT로 전달되는 패턴을 의미한다.

 

예를 들면, 앱 데이터는 데이터 소스에서 UI로 흐르고, 버튼을 누르는 이벤트는 UI에서 SSOT로 흘러 데이터 소스를 수정하고 변경하게 된다.

 


권장 앱 아키텍처

출처 : 안드로이드 공식 문서

각 구성요소가 한 수준 아래의 구성요소에만 종속됨을 볼 수 있다.

 

액티비티와 프래그먼트는 뷰 모델에만 종속이 되고

 

Repository는 여러 개이 다른 클래스에 종속되는 유일한 클래스이다.

 

위 예시에서 저장소는 지속 데이터 모델과 원격 백엔드 데이터 소스에 종속된다.

 

사용자가 앱을 닫고 많은 시간이 지난 후에 열어도 로컬에 보존했던 사용자의 정보가 바로 표시된다.

 

추가로, 데이터가 오래된 경우 Repository 모듈이 백그라운드에서 데이터 업데이트를 한다.


사용자 인터페이스 제작

UI는 프래그먼트(액티비티)와 관련 레이아웃 파일(xml)로 구성된다.

 

UI를 도출하기 위해선 데이터 모델에 다음 데이터 요소가 존재해야한다.

 

사용자 ID

사용자의 식별자로 프래그먼트(액티비티) 인수를 사용하여 이 정보를 프래그먼트에 전달한다.

 

사용자 객체

사용자에 관한 세부정보를 보유하는 데이터 클래스로 흔히 말하는 ViewModel이다.

 

ViewModel 은 액티비티나 프래그먼트를 구성하는 특정 UI 구성요소에 관한 데이터를 제공하고 모델과 통신하기 위한 데이터 처리 비즈니스 로직을 포함합니다.
ViewModeld 은 UI 구성요소에 관해 알지 못하므로 구성 변경(화면 회전 등 시 재생성)의 영향을 받지 않습니다.

 

ViewModel 예시

class UserProfileViewModel : ViewModel() {
   val userId : String = TODO()
   val user : User = TODO()
}

 

ViewModel을 사용한 Fragment 예시

class UserProfileFragment : Fragment() {
   private val viewModel: UserProfileViewModel by viewModels()

   override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View {
       return inflater.inflate(R.layout.main_fragment, container, false)
   }
}

 

위 경우에서 사용자 객체가 확보되면 프래그먼트에 알려야 하는데

 

여기서 LiveData 아키텍처 구성요소가 사용된다.

 

LiveData는 식별 가능한 데이터 홀더로

 

이 홀더를 사용하여 상호 간에 명시적이고 엄격한 종속성 경로를 만들지 않고도 객체 변경사항을 모니터링할 수 있다.

 

또한 LiveData 구성 요소는 액티비티, 프래그먼트와 같은 앱 구성 요소의 수명 조기 상태를 고려하고

 

개체 유출과 과도한 메모리 소비를 방지하기 위한 정리 로직을 포함한다.

 

ViewModel 예시

class UserProfileViewModel(
   savedStateHandle: SavedStateHandle
) : ViewModel() {
   val userId : String = savedStateHandle["uid"] ?:
          throw IllegalArgumentException("missing user id")
   val user : LiveData<User> = TODO()
}

 

Fragment 예시

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)
   viewModel.user.observe(viewLifecycleOwner) {
       // update UI
   }
}

LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메소드가 실행된다.

 

이로 인해 observe 메서드가 실행된다.