서론
제 프로젝트에서는 대부분의 UseCase가 Repository와 ViewModel을 단순히 연결해 주는 역할만 하고 있었습니다. 예를 들면 다음과 같은 코드였죠.
struct FetchBooksUseCase {
func execute() async -> Something {
await repository.fetchBooks()
}
}
이런 구조 때문에 저는 종종 "과연 이 상황에 UseCase가 정말 필요할까?" 하는 의문을 가졌습니다. 하지만 최근 UseCase를 정의한 것이 매우 적절했다고 느낀 경험이 있었습니다.
물론 이것이 엄청난 발견이나 깨달음은 아니었습니다. 다만, 인터넷에서 자주 언급되는 "UseCase를 사용해야 하는 이유"를 직접 체감하고 경험했다는 점에서 작은 감동을 느꼈습니다.
프로필 이미지 표시를 UseCase로 개선하기
UseCase의 유용성을 체감한 것은 프로필 이미지를 표시하는 로직을 개선할 때였습니다.
기존 로직에는 몇 가지 문제점이 있었습니다.
- 코드 중복: 프로필 이미지를 다운로드하고 표시하는 동일한 로직이 최소 3개의 파일에서 불필요하게 반복되고 있었습니다.
- 비효율적인 다운로드: 프로필 이미지의 용량 제한이 없음에도 불구하고, 매번 서버에서 이미지를 새로 다운로드하고 있었습니다. (4k 이미지도 있었습니다 ㄷㄷ;;;)
- 불필요한 이미지 처리: 다운로드한 이미지마다 매번 리사이징 작업을 수행하고 있어 성능 저하의 원인이 되고 있었습니다.
이러한 문제점들은 "동일한 행위가 여러 곳에서 반복되는" 전형적인 사례로, UseCase를 적용하기에 매우 적합한 상황이었습니다.
효율적인 UseCase 구현을 위해 먼저 기존의 프로필 이미지 관련 코드를 분석했습니다. 역할은 동일했지만 구현 방식에 미묘한 차이가 있어서 최적의 접근 방법을 고민해야 했습니다. 다행히 Objective-C 코드는 없었기에 Swift Concurrency를 활용할 수 있었습니다. 일부 파일에서는 Concurrency를 사용하지 않고 있었지만, 프로필 이미지 처리는 독립적으로 수행 가능하기 때문에 적용에 문제가 되지 않았습니다.
이를 통해 UseCase 내에서 이미지를 비동기적으로 다운로드하고 처리하여 UIImage를 반환하는 로직을 구현했습니다. 캐싱과 오프라인 지원을 위해 디스크에 저장할 때 리사이징 처리를 하여 최초 1회만 이미지 처리를 진행하도록 개선했습니다. (이제 4K 이미지를 매번 다운로드하고 리사이징 하지 않아도 됩니다 🥰)
참고로 Repository는 복잡한 네트워크 요청 구조가 아니었기 때문에 별도로 구현하지 않았습니다.
구현한 UseCase를 호출부에 적용하는건 아주 쉬웠습니다. UseCase를 정의할 때 호출부를 고려하였기 때문이죠 😎 동일한 역할을 수행하고 있었기 때문에 코드를 한 번 작성하고 다른 곳에서는 UIImageView 변수명만 변경하는 정도로 수정이 가능했습니다.
더 욕심을 낸다면 프로필 이미지 객체로 정의할 수도 있을 거 같았지만, 그럼 예상했던 것보다 작업 범위가 컸기 때문에 일단은 UseCase 적용만을 진행했습니다. (추가로.. 이미지 라이브러리를 사용하면 아주 편했겠지만 그럴 수 없는 이유가 있었네요... 😇)
동료에게 UseCase 도입 제안
다른 팀 동료의 고민을 듣고 UseCase 적용을 조언한 경험도 있었습니다.
팀 동료가 겪고 있던 문제는 A 화면에 필요한 로직이 B 화면에 있어, A 화면을 표시하려면 B 화면을 먼저 생성해야 하는 상황이었습니다.
즉 A와 B 화면의 의존이 강해 화면 구조가 복잡해지고 사이드 이펙트가 발생하고 있었죠.
동료는 잘 작동하는 로직을 분리하면 버그가 생길까 걱정했고, 효율적으로 코드를 분리하는 방법도 고민하고 있었습니다.
이에 UseCase 패턴을 제안했습니다. UseCase로 로직을 분리하면 A 화면과 B 화면의 의존성을 제거할 수 있고, UseCase만 독립적으로 테스트할 수 있어 버그 발생 우려도 줄일 수 있기 때문입니다.
동료가 ViewModel과의 차이점에 대해 물었을 때, MVVM 구조에서의 ViewModel과 UseCase의 역할 차이를 설명했습니다.
UseCase는 특정 화면에 국한되지 않고 재사용 가능한 비즈니스 로직의 단위로 볼 수 있으며, 이는 마치 '단일 책임을 가진 경량화된 ViewModel'로 이해할 수 있다고 말하니 (개떡같이 말해도) 찰떡처럼 이해해 주더라고요.
구조를 변경해야 하는 문제라 당장의 결과물은 없지만 꼭 해소가 됐으면 좋겠는 바람으로 기대 중입니다 🙏
결론
토이 프로젝트를 할 때는 로직이 재사용되는 일이 많이 없었습니다.
그래서 UseCase의 필요성도 잘 못 느꼈고, 유용하게 쓰지도 못했어요.
하지만 최근들어 UseCase를 활용하기 딱 좋은 케이스를 많이 만나서 배워두길 잘했다고 느껴지더라고요.
"몰라서 못 쓰는 것과 고민해서 안 쓰는건 다르다"는 말이 딱이었고, 시야를 조금 더 넓혀서 아는 것을 잘 활용하는 것도 능력이구나 느꼈습니다.
더 열심히 해야겠네요.
감사합니다.