일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 서비스 vs 쓰레드
- 안드로이드 라이선스
- 안드로이드 레트로핏 crud
- 멤버변수
- 안드로이드 유닛테스트란
- rxjava cold observable
- 자바 다형성
- android ar 개발
- 클래스
- 2022 플러터 안드로이드 스튜디오
- 안드로이드 라이선스 종류
- rxjava disposable
- jvm이란
- 스택 큐 차이
- 안드로이드 유닛 테스트 예시
- 안드로이드 os 구조
- 서비스 쓰레드 차이
- ANR이란
- rxjava hot observable
- Rxjava Observable
- 2022 플러터 설치
- android retrofit login
- 큐 자바 코드
- 플러터 설치 2022
- 안드로이드 레트로핏 사용법
- 안드로이드 유닛 테스트
- ar vr 차이
- jvm 작동 원리
- 스택 자바 코드
- 객체
- Today
- Total
나만을 위한 블로그
[Android] 앱 아키텍처 - 도메인 레이어란? 본문
https://developer.android.com/topic/architecture/domain-layer?hl=ko
도메인 레이어는 UI 레이어, 데이터 레이어 사이의 선택적 레이어다.
이 레이어는 복잡한 비즈니스 로직, 여러 뷰모델에서 재사용되는 간단한 비즈니스 로직의 캡슐화를 담당한다.
모든 앱에 이런 요구사항이 있는 건 아니라서 이 레이어는 선택사항이다. 따라서 복잡성을 처리하거나 재사용성을 선호하는 등 필요한 경우에만 써야 한다.
도메인 레이어가 제공하는 장점은 아래와 같다.
- 코드 중복 방지
- 도메인 레이어 클래스를 쓰는 클래스의 가독성 개선
- 앱의 테스트 가능성 높임
- 책임을 분할해 대형 클래스를 피할 수 있음
이름 지정 규칙
이 가이드에서 usecase의 이름은 담당하는 단일 작업에 따라 지정된다. 규칙은 아래와 같다.
현재 시제의 동사 + 명사/대상(선택사항) + UseCase
예를 들어 FormatDateUseCase, LogOutUserUseCase, GetLatestNewsWithAuthorsUseCase, MakeLoginRequestUseCase 등이 있다.
종속 항목
일반적인 앱 아키텍처에서 유스케이스 클래스는 UI 레이어의 뷰모델, 데이터 레이어의 레포지토리 사이에 위치한다.
즉 유스케이스 클래스는 일반적으로 레포지토리 클래스에 종속되며 레포지토리와 같은 방법으로 콜백 또는 코루틴을 써서 UI 레이어와 통신한다.
예를 들어 뉴스 레포지토리의 데이터, 작성자 레포지토리의 데이터를 가져와서 결합하는 유스케이스가 있을 수 있다.
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
유스케이스는 재사용 가능한 로직을 포함하기 때문에 다른 유스케이스가 사용할 수도 있다.
도메인 레이어에 여러 수준의 유스케이스가 있는 건 정상이다. 아래의 유스케이스는 UI 레이어의 여러 클래스가 시간대를 사용해서 화면에 메시지를 표시하는 경우 FormatDateUseCase를 쓸 수 있다.
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
코틀린에서 유스케이스 호출
코틀린에서 operator 수정자와 같이 invoke()를 정의해서 유스케이스 클래스 인스턴스를 함수처럼 호출 가능하게 만들 수 있다.
class FormatDateUseCase(userRepository: UserRepository) {
private val formatter = SimpleDateFormat(
userRepository.getPreferredDateFormat(),
userRepository.getPreferredLocale()
)
operator fun invoke(date: Date): String {
return formatter.format(date)
}
}
이 예에서 FormatDateUseCase의 invoke()를 써서 클래스 인스턴스를 함수인 것처럼 호출할 수 있다.
invoke()는 특정 서명으로 제한되지 않는다. 매개변수를 갯수에 상관없이 취하고 모든 타입을 리턴할 수 있다.
클래스의 서로 다른 시그니처로 invoke()를 오버라이드할 수도 있다. 위 유스케이스를 쓰는 법은 아래와 같다.
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
init {
val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)
/* ... */
}
}
생명주기
유스케이스가 고유한 생명주기를 갖지는 않는다. 대신 그 유스케이스를 쓰는 클래스의 범위가 적용된다.
UI 레이어의 클래스, 서비스, 또는 Application 클래스 자체에서 유스케이스를 호출할 수 있다.
유스케이스는 변경 가능한 데이터를 포함해선 안 되므로 유스케이스 클래스의 새 인스턴스를 종속 항목으로 전달할 때마다 그 인스턴스를 만들어야 한다.
쓰레딩
도메인 레이어의 유스케이스는 기본 안전성을 갖춰야 한다. 즉 메인 쓰레드에서 안전하게 호출돼야 한다.
장기 실행 차단 작업을 실행하는 유스케이스는 관련 로직을 적절한 쓰레드로 옮긴다. 그러나 개발자는 이 작업이 이뤄지기 전에 계층 구조의 다른 레이어에 이런 차단 작업이 더 잘 배치될 수 있는지 확인해야 한다.
일반적으로 복잡한 계산은 재사용, 캐싱을 유도하기 위해 데이터 레이어에서 이뤄진다. 결과를 캐시해서 앱의 여러 화면에서 재사용해야 하는 경우 대용량 목록을 대상으로 한 리소스 집약적 작업은 도메인 레이어보다 데이터 레이어에 더 잘 배치된다.
아래 예는 백그라운드 쓰레드에서 작업을 실행하는 유스케이스다.
class MyUseCase(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(...) = withContext(defaultDispatcher) {
// Long-running blocking operations happen on a background thread.
}
}
일반적인 작업
여기선 일반적인 도메인 레이어 작업을 실행하는 방법을 확인한다.
재사용 가능한 간단한 비즈니스 로직
UI 레이어의 반복 가능한 비즈니스 로직은 유스케이스 클래스에 캡슐화해야 한다. 그러면 그 로직이 쓰인 모든 곳에 변경사항을 더 쉽게 적용할 수 있다. 로직을 독립적으로 테스트할 수도 있다.
FormatDateUseCase의 경우 향후 날짜 형식과 관련된 비즈니스 요구사항이 변경되는 경우, 코드를 중앙의 한 위치에서만 바꾸면 된다.
유스케이스에 존재할 수 있는 로직이 대신 Util 클래스의 정적 메서드에 포함된 경우도 있다. 그러나 이 클래스는 찾기 어렵고 기능도 발견하기 어려운 경우가 종종 있어 권장되지 않는다. 그리고 유스케이스에선 기본 클래스의 쓰레딩, 에러 처리 등 공통 기능을 공유할 수 있어서 규모가 큰 팀에 도움이 될 수 있다.
레포지토리 결합
뉴스 앱에는 뉴스, 작성자 데이터 작업을 각각 처리하는 NewsRepository, AuthorsRepository 클래스가 있을 수 있다.
NewsRepository에서 노출되는 Article 클래스에는 작성자 이름만 포함된다. 하지만 개발자가 화면에 자세한 작성자 정보를 표시하고 싶을 수 있다. 작성자 정보는 AuthorsRepository에서 얻을 수 있다.
로직은 여러 저장소와 관련돼 있고 복잡해질 수 있으므로 GetLatestNewsWithAuthorsUseCase 클래스를 만들어 뷰모델에서 로직을 추상화하고 가독성을 높일 수 있다.
/**
* 이 UseCase는 최신 뉴스와 관련 작성자를 가져온다
*/
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(): List<ArticleWithAuthor> =
withContext(defaultDispatcher) {
val news = newsRepository.fetchLatestNews()
val result: MutableList<ArticleWithAuthor> = mutableListOf()
// 이것은 병렬화되지 않고 UseCase가 선형적으로 느리다
for (article in news) {
// Repository는 일시 중단 기능을 노출한다
val author = authorsRepository.getAuthor(article.authorId)
result.add(ArticleWithAuthor(article, author))
}
result
}
}
로직은 news 리스트의 모든 항목을 매핑한다. 따라서 데이터 레이어가 기본 안전성을 갖췄더라도 이 작업은 메인 쓰레드를 차단하지 않는다. 데이터 레이어에서 처리되는 항목 수를 알 수 없기 때문이다.
유스케이스에서 Default 디스패처를 써서 백그라운드 쓰레드로 작업을 옮기는 이유도 여기 있다.
데이터 레이어 액세스 제한
도메인 레이어 구현 시 고려해야 하는 다른 사항은 UI 레이어에서 데이터 레이어에 직접 접근하도록 허용해야 하는지, 도메인 레이어를 통해 모든 걸 강제 적용해야 하는지 여부다.
이렇게 제한할 경우 장단점은 아래와 같다.
- 장점 : 데이터 레이어에 대한 각 접근 요청과 관련해 분석 로깅을 실행하는 경우 같이 UI가 도메인 레이어 로직을 우회하지 않게 됨
- 단점 : 데이터 레이어에 대한 단순 함수 호출이라도 유스케이스를 추가해야 해서 복잡성이 증가
따라서 필요한 경우에만 유스케이스를 추가하는 게 좋다. UI 레이어가 거의 독점적으로 유스케이스를 통해 데이터에 접근하는 게 확인되면 이런 방식으로만 데이터에 접근하는 게 적합할 수 있다.
데이터 레이어 접근을 제한하기로 결정하는 건 개별 코드베이스 기준으로 정하면 되고, 엄격한 규칙 또는 유연한 접근 방식 중 뭘 쓸지에 따라 달라진다.
'Android' 카테고리의 다른 글
[Android] 앱 아키텍처 - UI 이벤트 (0) | 2024.05.14 |
---|---|
[Android] 앱 아키텍처 - 데이터 레이어란? (0) | 2024.05.12 |
[Android] 앱 아키텍처 - UI 레이어란? (0) | 2024.05.06 |
[Android] 안드로이드 앱 아키텍처 가이드 (0) | 2024.05.05 |
[Android] 모바일 브라우저 앱에서 웹뷰 디버깅하는 법 (0) | 2024.04.15 |