GitHub 안드로이드 리포지토리를 탐험 하다보면 많은 프로젝트가 Android App Architecture
또는 MVVM + Clean Architecture
로 구성되어 있습니다.
평소 요즘 트렌드인 두 가지의 아키텍처를 동료들과 적용해보고 싶었던 저였지만, 이를 곧 바로 상용 서비스에 도입하기에는 리스크가 크다고 판단되어 엄두를 내지 못하고 있었습니다.
이러한 와중에 서버/ios 팀과 함께 하나의 사내 프로젝트를 만드는 TF팀이 결성되어, Android 앱의 Clean Architecture
앱 구조 설계를 진행하게 되었습니다.
이때 설계를 진행하며 느꼈던 바를 간단히 공유하고자 합니다.✍️
💡 왜
Clean Architecture
를 적용했나요?
안드로이드 앱 아키텍처와 비교 했을때 제일 중요한 차이점은 Domain
레이어의 여부입니다.
클린 아키텍처는 비즈니스 로직을 구현한 Domain
레이어가 필수입니다.
안드로이드 앱 아키텍처는 이 Domain
레이어가 선택 사항이고, 비즈니스 로직이 복잡하지 않아 data
레이어 만으로 충분하다면 구현하지 않아도 됩니다.
이 두 가지의 아키텍처는 각각의 특징이 있습니다.
안드로이드 앱 아키텍처의 경우
Domain
레이어가 선택 사항인 안드로이드 앱 아키텍처는 상대적으로 구현이 간편할 수 있다.클린 아키텍처의 경우
Domain
가 필수이므로 비즈니스 로직의 용이한 재사용Domain
은 의존성이 없는 독립적 레이어이므로 손쉬운 테스트 코드 작성결국 두 아키텍처 모두 관심사 분리를 통한 쉬운 테스트가 그 기본 목적이기 때문에 좀 더 필요에 맞는 아키텍처를 적용하는게 옳아 보입니다.
GitHub 의 샘플 프로젝트들은 아래와 같이 Flow
를 반환하도록 하는 코드가 많습니다.
단순히 One-shot 형태로 데이터를 요청하는데, 비동기적 스트림 형태로 값을 방출하는 Flow
를 사용하는 이유가 있을까요?
이를 위해 많은 샘플 코드를 비교 및 의논하며 직접 구현해본 결과, 아래와 같은 깨달음을 얻었습니다.
🎯 채팅 메시지 또는 지속적 위치 조회와 같이 비동기적 응답이 필요한 경우에만
UseCase
,Repository
등에서 반환 값으로 노출하는게 좋다.
User
단 하나를 반환해야하는 UseCase
는 아래와 같이 Flow
를 반환하지 않고 User
Domain Model 또는 Result 와 같은 랩핑된 타입만 반환시키는게 여러 비동기적 결과들의 값들을 통합하는데 있어 더 유리하다고 생각합니다.
(작성하고 보니 아키텍처랑은 관계가 없어보이네요..)
우리는 어떤 기능이 서버를 통해 이루어진다면, 미리 정의된 에러 코드를 판별하여 예외 상황을 처리해야함을 알고있습니다.
이번 프로젝트의 경우 서버팀의 API 응답 규격에 따라 미리 정의된 에러
가 String
형태로 응답되는 형식이였고, 이때 "로그인 실패" 에러 String
을 응답받는다면 이를 'Presentation
레이어 까지 String
의 형태로 전달해야하는지' 에 대한 고민이 있었습니다.
단순 String
으로 에러를 전달한다면 아래와 같은 이점이 있습니다.
Presentation
, domain
, data
각 레이어에 별도 에러 타입을 구현하지 않아도 되어 구현이 편하다.별도 에러 타입
을 정의하여 전달한다면 아래와 같은 이점이 있습니다.
API 호출
에러 코드 뿐 아니라 DB
등 여러 데이터 처리 중 발생하는 예외 상황을 가르키는 에러 타입을 만들 수 있어 확장성
에 용이하다.우리는 이 중 확장성
에 초점을 맞춰, 별도의 에러 타입을 정의하기로 결정하였습니다.
이 에러 타입은 domain
레이어에 정의되어, data
레이어에서 에러 String 또는 여러 데이터를 판단하여 변환시키고, 이를 presentation
레이어까지 전달할 수 있도록 구현되었습니다.
DomainError
로 정의된 인터페이스를 각 서버 또는 기능별로 사용할 수 있도록 구현합니다. (TokenServerError 는 토큰 발급, 갱신 중 발생하는 에러입니다.)
이를 Domain
레이어 상 공통된 규격으로 사용 할 수 있도록 Repository
가 반환해야하는 타입으로 DomainResult
라는 클래스로 랩핑하였습니다. (성공 결과 처리는 덤입니다)
에러 타입 변환을 쉽게 할 수 있도록 별도의 Mapping 클래스를 만들어 사용합니다.
이를 통해 구현된 Repository
모습입니다.
이렇게 구현해본 감상으로는.. 에러 타입을 잘 작성한다면 좋은 방식이 될 듯 합니다.
다만, Repository 나 기능별로 에러 타입과 Mapping 클래스를 추가해야 할 수 있다는 점에서는 손이 많이 가는 방식이지 않나 싶습니다.
결론적으로, 어떻게 에러 처리를 구현해야할지 좀 더 고민해보는 숙제가 된 듯 합니다.🥲
클린 아키텍처를 적용하며 겪었던 시행착오는 수없이 많았습니다. 대부분이 아키텍처에 대한 이해도 부족으로 인한 것이였지만..
이 외에도 앱 구성요소 (서비스, 브로드캐스트 등) 을 통한 처리는 어떻게 이루어져야 할까요?
고민이 깊어지는 밤입니다..🌠🌠🌠
앞으로도 공유하고 싶은 글이 있으면 언제든지 작성하도록 하겠습니다!
여러분 모두 하시는 일, 원하시는 꿈 모두 잘되길 바랍니다.🔥