안드로이드 앱개발

클린아키텍쳐에서의 usecase 분리

팀(Tim) 2021. 4. 27. 09:22

유스케이스는 비지니스로직을 수행하는 클래스입니다.

이 유스케이스는 하나의 기능을 하는 편이 좋습니다.

유스케이스가 하나의 기능만한다면, 어느 상황에서 어떤 유스케이스가 필요한지 바로바로 알 수 있습니다. 또한 수정에 용이합니다.

 

제 깃허브에 있는 메모앱을 예를들어 봅시다.

 

class memoUsecase @Inject constructor(private val repo : Repository) {

    fun getAll() = repo.getAll()
    fun createMemo(memo : MemoData) = repo.createMemo(memo)
    fun deleteAllMemo() = repo.deleteAllMemo()
    fun updateMemo(memo : MemoData) = repo.updateMemo(memo)
    fun deleteMemo(memo : MemoData) = repo.deleteMemo(memo)

}

 

처음에 저는 유스케이스를 이렇게 구성했습니다.

이 유스케이스를 사용할때는 아래처럼 했습니다.

 

class CreateMemoViewModel @Inject constructor(
    private val useCase: memoUsecase
) : AppViewModel() {

 private fun createMemo(memo : MemoEntity) {
        useCase.createMemo(memo)
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _createMemoEvent.value = memo.toMemo()
            }
    }

    private fun updateMemo(memo : MemoEntity){
        useCase.updateMemo(memo)
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _createMemoEvent.value = memo.toMemo()
            }
    }
    
    //생략
class MemoViewModel @Inject constructor(
    private val useCase: memoUsecase
) : AppViewModel(), MemoAdapterDelegate{

    fun getAll() {
        useCase.getAll()
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _memoList.value = it.map { memo -> memo.toMemo() }
            }
    }


    fun deleteMemo(memo : Memo){
        useCase.deleteMemo(memo.toData())
            .subscribeOnBackground()
            .subscribeWithDisposable(this){

            }
    }


//생략

 

CreateMemoViewModel에서는 memoUsecase의 createMemo, updateMemo 함수 두 가지만 사용하고 있습니다.

나머지 함수는 CreateMemoViewModel에게는 필요없는 것입니다.

MemoViewModel에서는 getAll, deleteMemo 함수 두 가지만 사용합니다.

하나의 유스케이스를 각각  다른 목적으로 사용하고 있습니다.

이 상황에서 만약 memoUsecase를 수정할 일이 생겨서 getAll 함수를 수정했다면 어떨까요?

memoUsecase를 사용하는 모든 클래스가 제대로 돌아가는지 확신이 서지 않을 것입니다.

CreateMemoViewModel은 getAll 함수를 사용하지 않지만, 나중에 가서는 그 사실을 잊어버리기 쉽습니다.

따라서 CreateMemoViewModel도 수정의 여파를 받는 클래스에 염두에 두게됩니다.

수정이 프로그램에 미치는 영향은 최소화 해야합니다.

이 문제를 해결하기 위해서는 유스케이스를 최대한 작게 만드는 편이 좋습니다.

저는 유스케이스의 크기를 스코프라고 정의하고 싶습니다.

유스케이스의 스코프가 작으면 작을수록 이 유스케이스를 사용하는 경우의 수를 특정하기가 쉽고, 유지보수가 쉬워집니다.

 

위의 코드를 바꿔보겠습니다.

 

 

class CreateMemoUsecase @Inject constructor(private val repo : Repository) {
    fun createMemo(memo : MemoData) = repo.createMemo(memo)
}
class DeleteMemoUsecase @Inject constructor(private val repo : Repository) {
    fun deleteMemo(memo : MemoData) = repo.deleteMemo(memo)
}
class GetAllMemoUsecase @Inject constructor(private val repo : Repository) {
    fun getAllMemo() = repo.getAll()
}
class UpdateMemoUsecase  @Inject constructor(private val repo : Repository){
    fun updateMemo(memo : MemoData) = repo.updateMemo(memo)
}

 

이렇게 유스케이스를 분리했습니다.

사용할 때는 아래처럼 하면됩니다.

 

class CreateMemoViewModel @Inject constructor(
    private val createMemoUsecase: CreateMemoUsecase,
    private val updateMemoUsecase: UpdateMemoUsecase
) : AppViewModel() {

    private fun createMemo(memo : MemoData) {
        createMemoUsecase.createMemo(memo)
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _createMemoEvent.value = memo.toMemo()
            }
    }

    private fun updateMemo(memo : MemoData){
        updateMemoUsecase.updateMemo(memo)
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _createMemoEvent.value = memo.toMemo()
            }
    }
class MemoViewModel @Inject constructor(
    private val deleteMemoUsecase: DeleteMemoUsecase,
    private val getAllMemoUsecase: GetAllMemoUsecase
) : AppViewModel(), MemoAdapterDelegate{

    fun getAll() {
        getAllMemoUsecase.getAllMemo()
            .subscribeOnBackground()
            .subscribeWithDisposable(this) {
                _memoList.value = it.map { memo -> memo.toMemo() }
            }
    }


    fun deleteMemo(memo : Memo){
        deleteMemoUsecase.deleteMemo(memo.toData())
            .subscribeOnBackground()
            .subscribeWithDisposable(this){

            }
    }

 

유스케이스의 사용처가 보다 명확해졌음을 알 수 있습니다.

또한 수정도 용이해졌습니다. 만약 MemoViewModel에 더이상 메모를 삭제하는 기능이 필요없다고 하면 그저 deleMemoUsecase 를 사용하지 않으면 됩니다.