Generic활용하여 DataSource중복로직 줄이기
- 배경
DataSource를 Flow와 APIHandler클래스를 만들어 구현했는데 이때 emit 하는 과정에서 중복되는 코드가 생겨 이를 Generic을 활용하여 해결하고자 했다. 이를 중복코드를 해결하기 위해Generic, inline, reified, crossinline등 코틀린에서 지원하는 문법들을 활용하여 중복코드를 줄일 수 있는 함수를 제작하기로 하였다.
아래는 함수를 적용하지 않은 DataSource의 코드이다.
class LectureDataSourceImpl @Inject constructor(
private val lectureAPI: LectureAPI,
) : LectureDataSource {
override fun openLecture(body: OpenLectureRequest): Flow<Unit> = flow {
emit(
BitgoeulApiHandler<Unit>()
.httpRequest { lectureAPI.openLecture(body = body) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun getLectureList(page: Int, size: Int, type: String?,): Flow<LectureListResponse> = flow {
emit(
BitgoeulApiHandler<LectureListResponse>()
.httpRequest {
lectureAPI.getLectureList(page = page, size = size, type = type
)
}
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun getDetailLecture(id: UUID): Flow<DetailLectureResponse> = flow {
emit(
BitgoeulApiHandler<DetailLectureResponse>()
.httpRequest { lectureAPI.getDetailLecture(id = id) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun lectureApplication(id: UUID): Flow<Unit> = flow {
emit(
BitgoeulApiHandler<Unit>()
.httpRequest { lectureAPI.lectureApplication(id = id) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun lectureApplicationCancel(id: UUID): Flow<Unit> = flow {
emit(
BitgoeulApiHandler<Unit>()
.httpRequest { lectureAPI.lectureApplicationCancel(id = id) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun searchProfessor(keyword: String): Flow<SearchProfessorResponse> =
flow {
emit(
BitgoeulApiHandler<SearchProfessorResponse>()
.httpRequest { lectureAPI.searchProfessor(keyword = keyword) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun searchLine(keyword: String, division: String): Flow<SearchLineResponse> =
flow {
emit(
BitgoeulApiHandler<SearchLineResponse>()
.httpRequest { lectureAPI.searchLine(keyword = keyword, division = division) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun searchDepartment(keyword: String): Flow<SearchDepartmentResponse> = flow {
emit(
BitgoeulApiHandler<SearchDepartmentResponse>()
.httpRequest { lectureAPI.searchDepartment(keyword = keyword) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun searchDivision(keyword: String): Flow<SearchDivisionResponse> = flow {
emit(
BitgoeulApiHandler<SearchDivisionResponse>()
.httpRequest { lectureAPI.searchDivision(keyword = keyword) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun getLectureSignUpHistory(studentId: UUID): Flow<GetLectureSignUpHistoryResponse> =
flow {
emit(
BitgoeulApiHandler<GetLectureSignUpHistoryResponse>()
.httpRequest { lectureAPI.getLectureSignUpHistory(studentId = studentId) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun getTakingLectureStudentList(id: UUID): Flow<GetTakingLectureStudentListResponse> =
flow {
emit(
BitgoeulApiHandler<GetTakingLectureStudentListResponse>()
.httpRequest { lectureAPI.getTakingLectureStudentList(id = id) }
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun editLectureCourseCompletionStatus(id: UUID, studentId: UUID, isComplete: Boolean): Flow<Unit> =
flow {
emit(
BitgoeulApiHandler<Unit>()
.httpRequest {
lectureAPI.editLectureCourseCompletionStatus(
id = id,
studentId = studentId,
isComplete = isComplete
)
}
.sendRequest()
)
}.flowOn(Dispatchers.IO)
override fun downloadExcelFile(): Flow<DownloadExcelFileResponse> = flow {
emit(
BitgoeulApiHandler<DownloadExcelFileResponse>()
.httpRequest {
lectureAPI.downloadExcelFile()
}
.sendRequest()
)
}.flowOn(Dispatchers.IO)
}
위 코드의 중복코드를 줄이기 위해 아래와 같은 키워드를 이용해 함수를 제작하였다.
- 함수 작성
앞서 설명한 중복 코드를 줄이기 위해 아래와 같은 함수를 작성했다.
inline fun <reified T> makeRequest(crossinline apiCall: suspend () -> T): Flow<T> = flow {
emit(BitgoeulApiHandler<T>()
.httpRequest { apiCall() }
.sendRequest())
}.flowOn(Dispatchers.IO)
함수 구성요소를 하나씩 설명해 보겠다.
Generic
- 클래스나 함수에서 사용할 타입을 인스턴스를 만들 때 지정할 수 있게 하는 기능
inline
- 함수 호출을 inline화 한다. 즉, 함수가 호출될 때 함수 호출 자체를 제거하고, 함수의 바이트 코드를 호출 지점에 직접 삽입한다. 이를 통해 함수 호출의 오버헤드를 줄이고, 특히 람다를 인수로 전달할 때 유용하다.
<reified T>
- inline 함수 내에서 제네릭 타입을 런타임에 사용할 수 있도록 한다. 일반적인 제네릭 타입은 컴파일 시 제거되지만, reified를 사용하면 타입 정보가 런타임까지 유지된다.
crossinline
- inline 함수의 람다 매개변수에서 비지역 반환(non-local return)을 금지한다. 즉, 람다 내에서 return을 사용할 수 없다. 이는 예기치 않은 함수 종료를 방지하고, 코드의 예측 가능성을 높인다.
suspend () -> T
- suspend타입의 람다를 매개변수로 받아 Coroutine 내에서 비동기 작업을 수행할 수 있도록 한다. suspend () -> T 타입은 매개변수와 반환 타입이 없는 일시 중단 가능한 함수 타입을 나타낸다. 이 람다는 네트워크 요청을 수행하는 데 사용된다.
위의 함수를 적용하게 된다면 아래와 같이 코드가 바뀌게 된다.
class LectureDataSourceImpl @Inject constructor(
private val lectureAPI: LectureAPI,
) : LectureDataSource {
override fun openLecture(body: OpenLectureRequest): Flow<Unit> =
makeRequest { lectureAPI.openLecture(body) }
override fun getLectureList(page: Int, size: Int, type: String?): Flow<LectureListResponse> =
makeRequest { lectureAPI.getLectureList(page, size, type) }
override fun getDetailLecture(id: UUID): Flow<DetailLectureResponse> =
makeRequest { lectureAPI.getDetailLecture(id) }
override fun lectureApplication(id: UUID): Flow<Unit> =
makeRequest { lectureAPI.lectureApplication(id) }
override fun lectureApplicationCancel(id: UUID): Flow<Unit> =
makeRequest { lectureAPI.lectureApplicationCancel(id) }
override fun searchProfessor(keyword: String): Flow<SearchProfessorResponse> =
makeRequest { lectureAPI.searchProfessor(keyword) }
override fun searchLine(keyword: String, division: String): Flow<SearchLineResponse> =
makeRequest { lectureAPI.searchLine(keyword, division) }
override fun searchDepartment(keyword: String): Flow<SearchDepartmentResponse> =
makeRequest { lectureAPI.searchDepartment(keyword) }
override fun searchDivision(keyword: String): Flow<SearchDivisionResponse> =
makeRequest { lectureAPI.searchDivision(keyword) }
override fun getLectureSignUpHistory(studentId: UUID): Flow<GetLectureSignUpHistoryResponse> =
makeRequest { lectureAPI.getLectureSignUpHistory(studentId) }
override fun getTakingLectureStudentList(id: UUID): Flow<GetTakingLectureStudentListResponse> =
makeRequest { lectureAPI.getTakingLectureStudentList(id) }
override fun editLectureCourseCompletionStatus(id: UUID, studentId: UUID, isComplete: Boolean): Flow<Unit> =
makeRequest { lectureAPI.editLectureCourseCompletionStatus(id, studentId, isComplete) }
override fun downloadExcelFile(): Flow<DownloadExcelFileResponse> =
makeRequest { lectureAPI.downloadExcelFile() }
}
한눈에 봐도 처음 봤던 DataSource내의 많은 중복코드들이 사라진 걸 볼 수 있다.