Blocking 요청들
GitHub에 HTTP 요청을 하기 위해 Retrofit 라이브러리를 사용할 것이다. 주어진 조직에 속한 저장소 목록과 각 저장소의 기여자 목록을 요청할 수 있다.
interface GitHubService {
@GET("orgs/{org}/repos?per_page=100")
fun getOrgReposCall(
@Path("org") org: String
): Call<List<Repo>>
@GET("repos/{owner}/{repo}/contributors?per_page=100")
fun getRepoContributorsCall(
@Path("owner") owner: String,
@Path("repo") repo: String
): Call<List<User>>
}
이 API는 주어진 조직의 기여자 목록을 가져오기 위해 loadContributorsBlocking() 함수에서 사용된다.
1. src/tasks/Request1Blocking.kt 를 열어 구현체를 보자 :
fun loadContributorsBlocking(service: GitHubService, req: RequestData): List<User> {
val repos = service
.getOrgReposCall(req.org) // #1
.execute() // #2
.also { logRepos(req, it) } // #3
.body() ?: emptyList() // #4
return repos.flatMap { repo ->
service
.getRepoContributorsCall(req.org, repo.name) // #1
.execute() // #2
.also { logUsers(repo, it) } // #3
.bodyList() // #4
}.aggregate()
}
- 먼저 주어진 조직의 저장소 목록을 가져와 repos 리스트 속에 저장한다. 그리고 각 저장소에 대해 기여자들이 요청되고 모든 리스트들이 하나의 최종 기여자 목록으로 합쳐진다.
- getOrgReposCall() 과 getRepoContributorsCall() 각각은 *Call class의 인스턴스를 반환한다 (#1). 이 시점에는 어떠한 요청도 전송되지 않는다.
- 그 다음 *Call.execute()이 실행되어 요청이 수행된다 (#2). execute() 은 현재 수행중인 Thread를 Block하는 동기 호출이다.
- 응답을 받게되면, 결과값이 logRepos() 와 logUsers() 함수들에 의해 로깅된다 (#3). 만약 HTTP 응답이 애러를 가지고 있으면, 애러가 로깅된다.
- 마지막으로, 필요로 하는 데이터를 가진 응답의 body 부분을 가져온다. 이 튜토리얼에서는 애러가 있는 경우 결과로 빈 리스트를 사용하고 해당 애러를 기록한다 (#4).
2. .body() ?: emptyList()를 반복적으로 사용하는 것을 피하기 위해 확장 함수인 bodyList()가 선언된다.
fun <T> Response<List<T>>.bodyList(): List<T> {
return body() ?: emptyList()
}
3. 프로그램을 다시 실행한 후 IntelliJ IDEA의 시스템 출력을 확인해보자. 다음과 같을 것이다.
1770 [AWT-EventQueue-0] INFO Contributors - kotlin: loaded 40 repos
2025 [AWT-EventQueue-0] INFO Contributors - kotlin-examples: loaded 23 contributors
2229 [AWT-EventQueue-0] INFO Contributors - kotlin-koans: loaded 45 contributors
...
- 각 줄의 첫 항목은 프로그램이 시작된 후 경과된 밀리초를 나타내며, 대괄호 내부에는 Thread의 이름이 있다. 이를 통해 어떤 Thread에서 로딩 요청이 호출되었는지를 확인할 수 있다.
- 각 줄의 마지막 항목은 얼마나 많은 저장소와 기여자들이 로드되었는지에 대한 실제 메세지이다.
이 출력된 로그는 결과값들이 Main Thread에서 로깅 되었다는 것을 나타낸다. 만약 BLOCKING 옵션을 설정한 상태로 코드를 실행하면 윈도우는 멈추며 로딩이 끝날 때까지 입력에 반응하지 않는다. 모든 요청들은 loadContributorsBlocking() 를 호출한 Thread와 동일한 Thread에서 실행되며, 이 Thread는 Main UI Thread(Swing에서는 AWT event dispatching thread이다)이다. 이 Main Thread가 Block되기 때문에 UI가 멈추는 것이다.
기여자들의 리스트가 로드된 후에 결과값은 업데이트 된다.
4. src/contributors/Contributors.kt 내부에서 기여자들을 로드하는 방식을 선택하는 책임을 가진 loadContributors() 함수를 찾고, loadContributorsBlocking()이 어떻게 호출되는지 살펴보자.
when (getSelectedVariant()) {
BLOCKING -> { // Blocking UI thread
val users = loadContributorsBlocking(service, req)
updateResults(users, startTime)
}
}
- loadContributorsBlocking() 바로 다음에 updateResults()가 호출된다.
- updateResults() 는 UI를 업데이트 한다, 따라서 이는 언제나 UI Thread에서 호출되어야 한다.
- loadContributorsBlocking() 또한 UI Thread에서 호출되기 때문에, UI Thread가 Block되며 UI가 멈춘다.
Task 1
첫 작업은 작업 도메인에 익숙해질 수 있도록 하는데 도움을 줍니다. 현재, 각 기여자들의 이름은 참여한 모든 프로젝트에 대해 한 번씩 추가 되고 있어, 여러 번 반복되고 있습니다. 각 기여자가 한 번만 추가될 수 있도록 사용자를 합치는 함수인 aggregate()을 구현합니다. User.contributions 프로퍼티는 주어진 유저가 모든 프로젝트에 기여한 총 횟수를 포함해야 합니다. 결과 목록은 기여 횟수에 따라 내림차순으로 정렬 되어야 합니다.
src/tasks/Aggregation.kt 를 열어 List.aggregate() 함수를 구현하시오. 유저는 그들의 총 기여 횟수에 따라 정렬되어야 합니다.
해당 test/tasks/AggregationKtTest.kt 파일은 기대되는 결과의 예시를 보여줍니다.
📖 IntelliJ IDEA의 단축키 Ctrl+Shift+T/⇧ ⌘ T 를 사용해 소스 코드와 테스트용 클래스 사이를 자동으로 이동할 수 있다.
이 작업을 구현한 이후에, "kotlin" 조직에 대한 결과 목록은 다음과 유사해야 한다.
Task 1 솔루션
1. 로그인 별로 사용자들을 그룹화하기 위해서는 다른 저장소들에서 이 로그인을 사용하는 모든 경우를 Map으로 반환하는 groupBy() 함수를 사용해야 한다.
2. Map의 각 항목에 대해, 총 기여 수를 세고 주어진 이름과 기여 수의 합을 사용해 User Class의 새로운 인스턴스를 생성한다.
3. 결과 리스트를 내림차순으로 정렬한다.
fun List<User>.aggregate(): List<User> =
groupBy { it.login }
.map { (login, group) -> User(login, group.sumOf { it.contributions }) }
.sortedByDescending { it.contributions }
groupBy() 대신에 groupingBy() 함수를 사용할 수 있다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutines and channels − tutorial - Blocking requests
원문 최종 수정 : 2022년 8월 19일
'공식 문서 번역 > Coroutines 공식 문서' 카테고리의 다른 글
Coroutines와 Channels 튜토리얼 - 1. 시작하기 전 준비하기 (0) | 2023.05.19 |
---|---|
IntelliJ IDEA 사용해 Kotlin Flow 디버깅 하기 (0) | 2023.05.18 |
IntelliJ IDEA 사용해서 Coroutine 디버깅 하기 (0) | 2023.05.17 |
Coroutine 공유 상태와 동시성 3편 - Mutex 사용하기, Actors 사용하기 (0) | 2023.05.16 |
Coroutine 공유 상태와 동시성 2편 - Thread-safe한 데이터 구조, 세밀하게 Thread 제한하기, 굵게 Thread 제어하기 (0) | 2023.05.15 |