티스토리 뷰

728x90

7. 실제 프로젝트 적용 예제

프로젝트 개요

이번 프로젝트에서는 간단한 네트워킹 요청을 비동기로 처리하고, 이를 코루틴을 사용해 효율적으로 관리하는 방법을 다룹니다. 가정해보는 시나리오는 여러 REST API 호출을 병렬로 처리하고, 결과를 병합하는 상황입니다.

7.1 프로젝트 구성

  • fetchDataFromApi1(): 첫 번째 API에서 데이터를 가져오는 비동기 함수.
  • fetchDataFromApi2(): 두 번째 API에서 데이터를 가져오는 비동기 함수.
  • processData(): 두 API에서 받은 데이터를 처리하는 함수.
  • 구조화된 동시성(Structured Concurrency)를 사용하여 병렬 작업을 관리하고, 예외 발생 시 안전하게 처리하는 방식으로 진행합니다.

7.2 코드 예제: 병렬 네트워킹 요청 처리

import kotlinx.coroutines.*
import kotlin.random.Random

// 첫 번째 API에서 데이터를 비동기적으로 가져오는 함수
suspend fun fetchDataFromApi1(): String {
    delay(1000L)  // 네트워크 지연 시뮬레이션
    return "Data from API 1: ${Random.nextInt(100)}"
}

// 두 번째 API에서 데이터를 비동기적으로 가져오는 함수
suspend fun fetchDataFromApi2(): String {
    delay(1500L)  // 네트워크 지연 시뮬레이션
    return "Data from API 2: ${Random.nextInt(100)}"
}

// 데이터를 병합하고 처리하는 함수
suspend fun processData(data1: String, data2: String): String {
    delay(500L)  // 데이터 처리 지연 시뮬레이션
    return "Processed result: $data1, $data2"
}

// 실제 프로젝트 적용 예제
fun main() = runBlocking {
    try {
        println("Starting parallel API requests...")

        // 두 API에서 데이터를 병렬로 가져옴 (동시에 진행)
        val result = coroutineScope {
            val dataFromApi1 = async { fetchDataFromApi1() }
            val dataFromApi2 = async { fetchDataFromApi2() }

            // 데이터를 병합해서 처리
            processData(dataFromApi1.await(), dataFromApi2.await())
        }

        println("Final result: $result")
    } catch (e: Exception) {
        println("An error occurred: ${e.message}")
    }
}

7.3 설명

주요 개념 및 흐름:

  1. fetchDataFromApi1, fetchDataFromApi2비동기 함수로, 각각의 API에서 데이터를 가져오는 역할을 합니다. 네트워크 요청이 실제로는 지연될 수 있으므로, delay로 네트워크 지연을 시뮬레이션합니다.
  2. coroutineScope {}를 사용하여 두 비동기 작업을 병렬로 처리합니다. 각각의 API 요청은 async를 통해 병렬로 실행되며, 결과를 동시에 받아옵니다.
  3. processData 함수는 두 API에서 가져온 데이터를 병합하고 처리하는 역할을 합니다.
  4. 모든 작업이 구조화된 동시성 내에서 실행되며, 예외가 발생하면 부모 코루틴이 이를 감지하고 처리할 수 있습니다.

7.4 예외 처리와 구조화된 동시성

구조화된 동시성 덕분에 두 개의 비동기 작업이 안전하게 처리되며, 예외가 발생할 경우 부모 스코프에서 이를 감지하고 안전하게 중단시킬 수 있습니다.

예시: API 호출 중 하나에서 예외 발생 시 처리

import kotlinx.coroutines.*
import kotlin.random.Random

// 첫 번째 API에서 데이터를 가져오는 함수 (예외 발생 시뮬레이션)
suspend fun fetchDataFromApi1WithError(): String {
    delay(1000L)  // 네트워크 지연
    throw Exception("API 1 request failed!")  // 예외 발생
}

// 두 번째 API에서 데이터를 비동기적으로 가져오는 함수
suspend fun fetchDataFromApi2(): String {
    delay(1500L)  // 네트워크 지연
    return "Data from API 2: ${Random.nextInt(100)}"
}

// 데이터를 병합하고 처리하는 함수
suspend fun processData(data1: String, data2: String): String {
    delay(500L)
    return "Processed result: $data1, $data2"
}

fun main() = runBlocking {
    try {
        println("Starting parallel API requests with error...")

        // 두 API에서 데이터를 병렬로 가져옴 (동시에 진행)
        val result = coroutineScope {
            val dataFromApi1 = async { fetchDataFromApi1WithError() }
            val dataFromApi2 = async { fetchDataFromApi2() }

            processData(dataFromApi1.await(), dataFromApi2.await())  // 예외 발생 시 이 지점에서 멈춤
        }

        println("Final result: $result")
    } catch (e: Exception) {
        println("An error occurred: ${e.message}")
    }
}

출력:

Starting parallel API requests with error...
An error occurred: API 1 request failed!

7.5 실제 서비스에서의 적용

이 패턴은 실제 서비스에서 비동기 네트워크 요청을 병렬로 처리하고, 결과를 병합하여 동시성 문제 없이 안전하게 처리하는 데 유용하게 적용될 수 있습니다. 특히 다음과 같은 상황에서 유용합니다:

  1. REST API 호출: 여러 API를 병렬로 호출하여 응답을 받아와서 처리하는 대규모 서비스에서 활용될 수 있습니다. 예를 들어, 여행 예약 시스템에서 여러 항공사와 호텔 정보를 동시에 가져오는 시나리오에서 적용 가능합니다.
  2. 파일 입출력: 여러 파일을 동시에 읽거나 쓰는 작업에서, 파일 처리 시간을 줄이기 위해 병렬로 파일 입출력을 처리할 수 있습니다.
  3. 데이터베이스 쿼리: 여러 데이터베이스에서 병렬 쿼리를 실행하고, 그 결과를 병합하여 처리하는 상황에서도 유용합니다.

7.6 테스트 코드

실제 서비스에서는 코루틴을 테스트하기 위해 JUnit이나 kotlinx-coroutines-test와 같은 라이브러리를 사용할 수 있습니다. 예를 들어, 네트워킹 호출을 모의(Mock) 처리하고, 결과를 검증하는 방식으로 테스트할 수 있습니다.

간단한 테스트 코드 예시

import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals

class ApiServiceTest {

    // 모의 데이터로 네트워크 호출을 시뮬레이션
    suspend fun fetchDataMock(): String {
        delay(1000L)  // 지연 시뮬레이션
        return "Mock Data"
    }

    @Test
    fun testFetchData() = runTest {
        val result = fetchDataMock()
        assertEquals("Mock Data", result)  // 결과 검증
    }
}

 

728x90
댓글