Android에서 Clean Architecture에 대해 더 알아보기. Cross-cutting features 예시.
Source: Dev.to
Introduction
이제 전체 앱에 걸쳐 있는 기능—예외, 다이얼로그, 전역 이벤트 등—에 대해 논의합니다. 이러한 기능들은 서로 의존합니다.
핵심 아이디어는 전역 이벤트에 대한 단일 진실 원천 (SSOT) 을 만들고 메인 모듈에서 이를 구독하는 것입니다. Kotlin Flows가 이를 가능하게 해 주며, 여기서는 이 시나리오에 맞게 설계된 SharedFlow를 사용할 것입니다.
Requirements
- 플랫폼: Android 전용 (CMP/KMP 없음)
- 프로젝트: 멀티‑모듈
- 각 기능은 자체 화면을 가짐
- 어떤 기능이든 트리거를 보낼 수 있음
- 기능들은 서로 독립적임
Structure
General scheme

Domain module
도메인 모듈은 독립적이며 핵심 계약을 포함합니다:
interface GlobalRepository {
val eventFlow: SharedFlow
fun sendTrigger(trigger: Trigger)
}
enum class Trigger {
SOME_TRIGGER_1,
SOME_TRIGGER_2,
// …
}
enum class GlobalEvent {
SOME_EVENT_1,
SOME_EVENT_2,
// …
}
Domain‑shared module
이 모듈은 :domain:core에 의존하고 트리거 전송을 위한 구현과 DI를 제공합니다:
interface SendTrigger {
operator fun invoke(trigger: Trigger) // or suspend fun if needed
}
class SendGlobalEventImpl @Inject constructor(
private val repository: GlobalRepository
) : SendTrigger {
override fun invoke(trigger: Trigger) = repository.sendTrigger(trigger)
}
Data module
MutableSharedFlow를 사용한 GlobalRepository 구현:
class GlobalRepositoryImpl @Inject constructor(
// injected entities such as dispatcher or scope if needed
) : GlobalRepository {
private val _eventFlow = MutableSharedFlow()
override val eventFlow = _eventFlow.asSharedFlow()
override fun sendTrigger(trigger: Trigger) {
when (trigger) {
// some logic
// example:
// someScope.launch { _eventFlow.emit(event) }
// other logic
}
}
// …
}
Flow 파라미터(replay, buffer 등)는 상황에 맞게 조정할 수 있습니다.
App and feature modules
- App 모듈:
:domain:core에서GlobalRepository를 직접 주입하거나 이벤트 흐름을 얻기 위한 인터페이스를 노출합니다. - Feature 모듈:
:domain:shared에서SendTrigger를 주입합니다.
기능 모듈이 어떻게 동작하는지는 이전 기사에서 설명합니다.
Summary
이 접근 방식은 이전 기사들과 동일한 원칙을 따릅니다:
- 공유 인터페이스를 포함하는 단일 독립적인
:domain:core - 데이터 모듈이 전역 이벤트에 대한 SSOT 역할을 수행
이 패턴은 Sharing data between ViewModels 기사에서 설명된 것과 유사하지만, 더 큰 규모에 적용된 형태입니다.
Warning: 예시는 위에 명시된 특정 요구 사항에 맞춘 것입니다. 다른 프로젝트 유형에는 적합하지 않을 수 있습니다. 생각 없이 그대로 복사하지 마세요.
Thanks for reading!