Android

Android - UseCase 추상화

팡세영 2024. 3. 15. 10:01

사이드 프로젝트를 진행하면서 Domain Layer의 비지니스 로직을 수행하는 각 UseCase의 리팩토링 필요성을 느끼게 되었다.

사이드 프로젝트 usecase들

위 사진에 보이는 UseCase들은 API 응답을 받아 데이터 스트림을 방출하는 역할들을 하는데

개발을 하다보니 invoke 메서드의 코드들이 전부 중복되는 문제가 있었다. 

아래 두 코드에서 확인할 수 있듯이 단순히 결과를 받아와 데이터 스트림을 방출하는 역할만 수행한다.

문제가 모든 UseCase 코드들이 같은 구조로 되어 있다.

class GetToys @Inject constructor(
    private val toyLibraryRepo: ToyLibraryRepository
) {
    operator fun invoke(): Flow<ApiResult<List<ToyInfo>>> = channelFlow {
        toyLibraryRepo.fetchToyList().collectLatest { apiResult ->
             when (apiResult) {
                is ApiResult.Success -> {
                    send(ApiResult.Success(apiResult.value))
                }

                is ApiResult.Error -> {
                    send(apiResult)
                }

                is ApiResult.Exception -> {
                    send(apiResult)
                }
            }
        
        }
    }
}

 

class GetChats @Inject constructor(
    private val chatRepository: ChatRepository
) {
    operator fun invoke(
        user1: String,
        user2: String
    ): Flow<ApiResult<List<ChatInfo>>> = channelFlow {
        chatRepository.fetchChats(user1, user2).collectLatest { apiResult ->
              when (apiResult) {
                is ApiResult.Success -> {
                    send(ApiResult.Success(apiResult.value))
                }

                is ApiResult.Error -> {
                    send(apiResult)
                }

                is ApiResult.Exception -> {
                    send(apiResult)
                }
            }
        }
    }
}

 

아직 개발 기능의 전부가 완성된게 아니라 전체적인 틀을 잡는 과정이라지만 추상화의 필요성을 느끼게 되었다.

그래서 아래와 같이 추상화 클래스를 구현해 보일러 플레이트 코드를 제거했다. 

abstract class BaseResultUseCase<P, R> {

    abstract suspend fun execute(params: P): Flow<ApiResult<R>>
    
    operator fun invoke(params: P): Flow<ApiResult<R>> = channelFlow {
        execute(params).collectLatest { apiResult ->
            when (apiResult) {
                is ApiResult.Success -> {
                    send(ApiResult.Success(apiResult.value))
                }

                is ApiResult.Error -> {
                    send(apiResult)
                }

                is ApiResult.Exception -> {
                    send(apiResult)
                }
            }
        }
    }
}

 

구현 클래스

class GetChats @Inject constructor(
    private val chatRepository: ChatRepository
): BaseResultUseCase<ChatDto, ChatInfo> {

    override suspend fun execute(chatDto: ChatDto): Flow<ApiResult<ChatInfo>> {
        return chatRepository.fetchChats(chatDto.user1, chatDto.user2)
    }
}