처음 프로젝트 시작할 때 나는 repository와 DI에 대해 알지 못했다. 그래서 같은 상황에 대해 팀원과 내가 짜는 코드가 달랐다. 가령 나는 data 가공 코드를 다 ViewModel에 적어놓고 retrofit 객체도 직접 생성해서 썼지만 팀원은 repository와 Hilt를 사용해서 data 가공 코드를 ViewModel에서 분리하고 retrofit 객체도 주입 받아서 썼다.
공부해서 구현 방법을 통일하기엔 개발 기간이 2주 남짓되는 해커톤이었기에 현실적으로 어려운 부분이 있었다. 현재는 조금 여유가 생겼고 그때 못했던 공부를 보충하며 리팩토링을 해나가려고 한다.
통신 코드를 짤 때 repository와 hilt를 활용했다.
왜 repository를 사용하는가?
repository를 쓰지 않으면 ViewModel 내 logic들의 관심사 분리가 온전히 이루어지기 어렵다. 가령 통신 코드가 그러하다. 클린 아키텍쳐의 관점에서 ViewModel은 presentation layer에 속하고 UI 관련 data들을 관리하는 역할인데 이것과 관련이 없는 통신 코드가 껴있으면 역할이 흐려지게 된다. 따라서 repository를 활용해 data 관련 logic을 data layer로 분리하고 이것으로 ViewModel에 코드를 늘어놓는 것보다 깔끔한 관리가 가능하게 한다. 이것은 곧 캡슐화여서 해서 객체들 간의 협력에 자율성을 높이는 것이다.
repository를 구현할 때 왜 DI를 사용하는가?
repository를 구현하는 과정에서 source, repositoryImpl, service 등으로 파일을 나눌 수 있는데 이때 서로간의 의존성이 발생한다. 코드를 짤 땐 추후에 수정/업데이트가 일어날 수 있음을 염두에 두어야 한다. 따라서 수월한 관리를 위해 객체를 주입할 땐 수동으로 하지 않고 DI를 활용하기로 했다. test에 용이하다는 내용도 봤지만 아직 테스트 코드를 써본 적이 없어서 이 부분은 당장 체감이 안 된다. 만약 DI를 안 쓰고 직접 객체를 만들어 쓴다고 하면 repository를 호출하는 ViewModel에 직접 객체를 생성하는 코드를 써줘야 하므로 깔끔하지 못하다. 따라서 DI를 사용하여 repository를 구현하기로 했다.
왜 hilt를 사용하는가?
컴파일 시 코드가 생성돼 상대적으로 koin보다 성능이 좋을 뿐만 아니라 기존의 dagger보다 학습이 쉽다고 한다. 핵심적으로 hilt는 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리해주기 때문에 안드로이드 개발을 하는 우리에게 효용이 제일 크다고 판단했다.
리팩토링 전
팀원이 짠 코드이다. Impl 객체를 return할 때 파라미터로 CourseDataSource 객체를 직접 생성하여 넣었고 그 안에 retrofit 객체도 직접 생성하고 있다. 위에서 잠깐 언급했던 것처럼 이는 DI 사용 취지를 퇴색시키는 것이라 판단했고 객체를 직접 생성이 아닌 주입받는 방향으로 리팩토링을 진행하기로 했다.
리팩토링 후
retrofit 객체, DataSource 객체 모두 module을 만들어주었다. 코드가 깔끔해졌다. 뿐만 아니라 기존에 아래처럼 파일을 관리하고 있었는데 retrofit 객체 생성 구조를 정의한 코드를 RetrofitModule로 빼주면서 api라고 두루뭉술하게 네이밍 돼있던 폴더명을 service로 바꾸었다.
AppInterceptor, TokenAuthenticator는 로그인 관련 파일인데 이번 리팩토링 대상은 아니어서 그대로 두었다. 파일 앞에 K-, P- 키워드가 붙어있는 것은 이 프로젝트를 처음 시작할 때 나는 repository와 DI를 알지 못했기 때문에 팀원이 하고자 하는 방법에 맞춰줄 수가 없어서 각자의 방식으로 작업을 일단 시작하고자 한 것이다. 실질적인 개발 기간이 2주밖에 안 됐던 해커톤이었기 때문에 당시에는 공부해서 방법을 맞추기가 어려운 상황이었다.
현재는 나도 repository와 hilt를 공부했기 때문에 더이상 키워드를 붙일 필요가 없어져서 불필요한 파일을 삭제하고 통합시켜주었다.
팀 자체 API와 Tmap Open API를 사용했는데 BaseUrl이 다른 부분은 위처럼 해결했다. 저렇게 구분지어주지 않으면 Hilt가 Retrofit 객체를 주입해줄 때 어떤 것을 주입해줄지 몰라서 에러가 발생한다.
한 번에 너무 많은 것을 커버하려다 놓치는 것이 생길까봐 우선 다루기로 한 문제부터 확실하게 처리하려고 했다. 아직 더 개선해야 할 부분이 남았다. 아래는 이번 refactoring을 진행한 PR이다.
https://github.com/Runnect/Runnect-Android/pull/166