Skip to content

Kotlin Multiplatform로 풀스택 애플리케이션 구축하기

코드 예제: full-stack-task-manager

사용된 플러그인:

Routing
라우팅은 서버 애플리케이션에서 수신 요청을 처리하기 위한 핵심 플러그인입니다.
, kotlinx.serialization,
Content Negotiation
ContentNegotiation 플러그인은 클라이언트와 서버 간 미디어 타입 협상 및 특정 형식으로 콘텐츠를 직렬화/역직렬화하는 두 가지 주요 목적을 수행합니다.
, Compose Multiplatform, Kotlin Multiplatform

이 문서에서는 안드로이드, iOS, 데스크톱 플랫폼에서 실행되는 Kotlin 풀스택 애플리케이션을 개발하는 방법을 Ktor를 활용하여 원활한 데이터 처리를 수행하는 방법을 배우게 됩니다.

이 튜토리얼이 끝나면 다음을 수행하는 방법을 알게 될 것입니다:

  • Kotlin Multiplatform을(를) 사용하여 풀스택 애플리케이션을 생성합니다.
  • IntelliJ IDEA로 생성된 프로젝트를 이해합니다.
  • Ktor 서비스를 호출하는 Compose Multiplatform 클라이언트를 생성합니다.
  • 설계의 다양한 계층에서 공유 타입을 재사용합니다.
  • 멀티플랫폼 라이브러리를 올바르게 포함하고 구성합니다.

이전 튜토리얼에서는 작업 관리자 예제를 사용하여

요청을 처리하고
작업 관리자 애플리케이션을 빌드하여 Ktor와 Kotlin에서 라우팅, 요청 처리 및 매개변수의 기본 사항을 배웁니다.
,
RESTful API를 생성하고
Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하는 방법을 배우세요. JSON 파일을 생성하는 RESTful API 예제가 포함되어 있습니다.
,
Exposed와 데이터베이스를 통합
Exposed SQL 라이브러리를 사용하여 Ktor 서비스를 데이터베이스 리포지토리와 연결하는 프로세스를 배웁니다.
했습니다. 클라이언트 애플리케이션은 Ktor의 기본 사항을 배우는 데 집중할 수 있도록 가능한 한 최소한으로 유지되었습니다.

이 튜토리얼에서는 Ktor 서비스를 사용하여 표시될 데이터를 가져오고 안드로이드, iOS, 데스크톱 플랫폼을 대상으로 하는 클라이언트를 생성합니다. 가능한 모든 곳에서 클라이언트와 서버 간에 데이터 타입을 공유하여 개발 속도를 높이고 오류 발생 가능성을 줄일 것입니다.

사전 요구 사항

이전 문서와 마찬가지로 IntelliJ IDEA를 IDE로 사용할 것입니다. 환경을 설치하고 구성하려면 다음 Kotlin Multiplatform 빠른 시작 가이드를 참조하세요.

Compose Multiplatform를 처음 사용하는 경우, 이 튜토리얼을 시작하기 전에 Compose Multiplatform 시작하기 튜토리얼을 완료하는 것이 좋습니다. 작업의 복잡성을 줄이기 위해 단일 클라이언트 플랫폼에 집중할 수 있습니다. 예를 들어, iOS를 사용해 본 적이 없다면 데스크톱 또는 안드로이드 개발에 집중하는 것이 현명할 수 있습니다.

새 프로젝트 생성

Ktor 프로젝트 제너레이터 대신 IntelliJ IDEA의 Kotlin Multiplatform 프로젝트 마법사를 사용하세요. 이는 클라이언트 및 서비스로 확장할 수 있는 기본 멀티플랫폼 프로젝트를 생성합니다. 클라이언트는 SwiftUI와 같은 네이티브 UI 라이브러리를 사용할 수도 있지만, 이 튜토리얼에서는 Compose Multiplatform을(를) 사용하여 모든 플랫폼에 대한 공유 UI를 생성할 것입니다.

  1. IntelliJ IDEA를 시작합니다.
  2. IntelliJ IDEA에서 File | New | Project를 선택합니다.
  3. 왼쪽 패널에서 Kotlin Multiplatform를 선택합니다.
  4. New Project 창에서 다음 필드를 지정합니다:
    • Name : full-stack-task-manager
    • Group : com.example.ktor
  5. 대상 플랫폼으로 Android , Desktop , Server를 선택합니다.

  6. Mac을 사용 중이라면 iOS도 선택하세요. Share UI 옵션이 선택되어 있는지 확인합니다. Kotlin Multiplatform 마법사 설정

  7. Create 버튼을 클릭하고 IDE가 프로젝트를 생성하고 임포트할 때까지 기다립니다.

서비스 실행

  1. Project 보기에서 server/src/main/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 Application.kt 파일을 엽니다.
  2. 애플리케이션을 시작하려면 main() 함수 옆에 있는 Run 버튼 (IntelliJ IDEA 실행 아이콘)을 클릭합니다.

    Run 도구 창에 "Responding at http://0.0.0.0:8080" 메시지로 끝나는 새 탭이 열릴 것입니다.

  3. 애플리케이션을 열려면 http://0.0.0.0:8080/로 이동합니다. 브라우저에 Ktor의 메시지가 표시될 것입니다. Ktor 서버 브라우저 응답

프로젝트 검사

server 폴더는 프로젝트 내 세 개의 Kotlin 모듈 중 하나입니다. 나머지 두 개는 sharedcomposeApp 입니다.

server 모듈의 구조는 Ktor Project Generator로 생성된 것과 매우 유사합니다. 플러그인 및 의존성을 선언하는 전용 빌드 파일과 Ktor 서비스를 빌드하고 시작하는 코드를 포함하는 소스 세트가 있습니다:

Kotlin Multiplatform 프로젝트의 서버 폴더 내용

Application.kt 파일의 라우팅 지침을 보면 greet() 함수 호출을 볼 수 있습니다:

kotlin

이는 Greeting 타입의 인스턴스를 생성하고 greet() 메서드를 호출합니다. Greeting 클래스는 shared 모듈에 정의되어 있습니다: IntelliJ IDEA에서 열린 Greeting.kt 및 Platform.kt

shared 모듈은 다양한 대상 플랫폼에서 사용될 코드를 포함합니다.

shared 모듈 세트의 commonMain 소스는 모든 플랫폼에서 사용될 타입을 보유합니다. 보시다시피, Greeting 타입이 정의된 곳입니다. 이것은 또한 서버와 모든 다른 클라이언트 플랫폼 간에 공유될 공통 코드를 넣을 곳이기도 합니다.

shared 모듈은 또한 클라이언트를 제공하고자 하는 각 플랫폼에 대한 소스 세트를 포함합니다. 이는 commonMain 내에 선언된 타입이 대상 플랫폼에 따라 달라지는 기능이 필요할 수 있기 때문입니다. Greeting 타입의 경우, 플랫폼별 API를 사용하여 현재 플랫폼의 이름을 가져오려고 합니다. 이는 expect 및 actual 선언을 통해 달성됩니다.

shared 모듈의 commonMain 소스 세트에서 expect 키워드로 getPlatform() 함수를 선언합니다:

kotlin

그러면 각 대상 플랫폼은 아래와 같이 getPlatform() 함수의 actual 선언을 제공해야 합니다:

kotlin
kotlin
kotlin
kotlin

프로젝트에는 하나의 추가 모듈인 composeApp 모듈이 있습니다. 이는 안드로이드, iOS, 데스크톱 및 웹 클라이언트 앱의 코드를 포함합니다. 이 앱들은 현재 Ktor 서비스에 연결되어 있지 않지만, 공유된 Greeting 클래스를 사용합니다.

클라이언트 애플리케이션 실행

대상에 대한 실행 구성을 실행하여 클라이언트 애플리케이션을 실행할 수 있습니다. iOS 시뮬레이터에서 애플리케이션을 실행하려면 아래 단계를 따르세요:

  1. IntelliJ IDEA에서 iosApp 실행 구성과 시뮬레이션된 장치를 선택합니다. 실행 및 디버그 창
  2. Run 버튼 (IntelliJ IDEA 실행 아이콘)을 클릭하여 구성을 실행합니다.
  3. iOS 앱을 실행하면 내부적으로 Xcode로 빌드되고 iOS 시뮬레이터에서 시작됩니다. 이 앱은 클릭 시 이미지를 토글하는 버튼을 표시합니다. iOS 시뮬레이터에서 앱 실행

    버튼을 처음 누르면 현재 플랫폼의 세부 정보가 텍스트에 추가됩니다. 이를 달성하는 코드는 composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager/App.kt에서 찾을 수 있습니다:

    kotlin

    이는 이 문서의 뒷부분에서 수정할 컴포저블 함수입니다. 현재 중요한 것은 UI를 표시하고 공유 Greeting 타입을 사용하며, 이는 다시 공통 Platform 인터페이스를 구현하는 플랫폼별 클래스를 사용한다는 점입니다.

이제 생성된 프로젝트의 구조를 이해했으니 작업 관리자 기능을 점진적으로 추가할 수 있습니다.

모델 타입 추가

먼저 모델 타입을 추가하고 클라이언트와 서버 모두에서 접근 가능한지 확인합니다.

  1. shared/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 model이라는 새 패키지를 생성합니다.
  2. 새 패키지 내에 Task.kt라는 새 파일을 생성합니다.
  3. 우선순위를 나타내는 enum과 작업을 나타내는 class를 추가합니다. Task 클래스는 kotlinx.serialization 라이브러리의 Serializable 타입으로 어노테이션됩니다:

    kotlin

    임포트와 어노테이션 모두 컴파일되지 않는 것을 알 수 있습니다. 이는 프로젝트가 아직 kotlinx.serialization 라이브러리에 대한 의존성을 가지고 있지 않기 때문입니다.

  4. shared/build.gradle.kts 로 이동하여 직렬화 플러그인을 추가합니다:

    kotlin
  5. 동일한 파일에서 commonMain 소스 세트에 새 의존성을 추가합니다:

    kotlin
  6. gradle/libs.versions.toml 로 이동하여 다음을 정의합니다:
    toml
  7. IntelliJ IDEA에서 Build | Sync Project with Gradle Files를 선택하여 업데이트를 적용합니다. Gradle 임포트가 완료되면 Task.kt 파일이 성공적으로 컴파일되는 것을 확인할 수 있습니다.

직렬화 플러그인을 포함하지 않아도 코드는 컴파일되었을 수 있지만, 네트워크를 통해 Task 객체를 직렬화하는 데 필요한 타입은 생성되지 않았을 것입니다. 이는 서비스를 호출하려고 할 때 런타임 오류로 이어질 수 있습니다.

직렬화 플러그인을 다른 모듈(예: server 또는 composeApp )에 배치해도 빌드 시점에는 오류가 발생하지 않았을 것입니다. 하지만 다시 말하지만, 직렬화에 필요한 추가 타입은 생성되지 않아 런타임 오류로 이어질 것입니다.

서버 생성

다음 단계는 작업 관리자를 위한 서버 구현을 생성하는 것입니다.

  1. server/src/main/kotlin/com/example/ktor/full_stack_task_manager 폴더로 이동하여 model이라는 하위 패키지를 생성합니다.
  2. 이 패키지 내에 새 TaskRepository.kt 파일을 생성하고 리포지토리에 대한 다음 인터페이스를 추가합니다:

    kotlin
  3. 동일한 패키지에 다음 클래스를 포함하는 새 파일 InMemoryTaskRepository.kt를 생성합니다:

    kotlin
  4. server/src/main/kotlin/.../Application.kt 로 이동하여 기존 코드를 다음 구현으로 대체합니다:

    kotlin

    이 구현은 이전 튜토리얼과 매우 유사하지만, 단순화를 위해 모든 라우팅 코드를 Application.module() 함수 내에 배치했다는 점이 다릅니다.

    이 코드를 입력하고 임포트를 추가하면, 코드가 웹 클라이언트와 상호 작용하기 위한

    CORS
    필수 의존성: io.ktor:%artifact_name% 코드 예제: full-stack-task-manager 네이티브 서버 지원: ✅
    플러그인을 포함하여 의존성으로 포함되어야 하는 여러 Ktor 플러그인을 사용하므로 여러 컴파일 오류가 발생할 것입니다.

  5. gradle/libs.versions.toml 파일을 열고 다음 라이브러리를 정의합니다:
    toml
  6. 서버 모듈 빌드 파일( server/build.gradle.kts )을 열고 다음 의존성을 추가합니다:

    kotlin
  7. 다시 한번, 메인 메뉴에서 Build | Sync Project with Gradle Files를 선택합니다. 임포트가 완료되면 ContentNegotiation 타입과 json() 함수의 임포트가 제대로 작동하는 것을 확인할 수 있습니다.
  8. 서버를 재실행합니다. 브라우저에서 경로에 도달할 수 있음을 확인할 수 있습니다.
  9. 로 이동하여 JSON 형식의 작업이 포함된 서버 응답을 확인합니다. 브라우저의 서버 응답

클라이언트 생성

클라이언트가 서버에 접근할 수 있도록 Ktor 클라이언트를 포함해야 합니다. 여기에는 세 가지 유형의 의존성이 관련됩니다:

  • Ktor 클라이언트의 핵심 기능.
  • 네트워킹을 처리하는 플랫폼별 엔진.
  • 콘텐츠 협상 및 직렬화 지원.
  1. gradle/libs.versions.toml 파일에 다음 라이브러리를 추가합니다:
    toml
  2. composeApp/build.gradle.kts 로 이동하여 다음 의존성을 추가합니다:
    kotlin

    이 작업이 완료되면 클라이언트가 Ktor 클라이언트를 감싸는 얇은 래퍼 역할을 할 TaskApi 타입을 추가할 수 있습니다.

  3. 빌드 파일의 변경 사항을 임포트하려면 메인 메뉴에서 Build | Sync Project with Gradle Files를 선택합니다.
  4. composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 network라는 새 패키지를 생성합니다.
  5. 새 패키지 내에 클라이언트 구성을 위한 새 HttpClientManager.kt 를 생성합니다:

    kotlin

    1.2.3.4를 현재 머신의 IP 주소로 대체해야 합니다. 안드로이드 가상 장치(Android Virtual Device) 또는 iOS 시뮬레이터에서 실행되는 코드에서는 0.0.0.0 또는 localhost로 호출할 수 없습니다.

  6. 동일한 composeApp/.../full_stack_task_manager/network 패키지에 다음 구현이 포함된 새 TaskApi.kt 파일을 생성합니다:

    kotlin
  7. commonMain/.../App.kt 로 이동하여 App 컴포저블을 아래 구현으로 대체합니다. 이는 TaskApi 타입을 사용하여 서버에서 작업 목록을 검색한 다음 각 작업의 이름을 열에 표시합니다:

    kotlin
  8. 서버가 실행 중인 동안, iosApp 실행 구성을 사용하여 iOS 애플리케이션을 테스트합니다.

  9. Fetch Tasks 버튼을 클릭하여 작업 목록을 표시합니다: iOS에서 실행되는 앱

    NOTE

    이 데모에서는 명확성을 위해 프로세스를 단순화하고 있습니다. 실제 애플리케이션에서는 네트워크를 통해 암호화되지 않은 데이터를 전송하는 것을 피하는 것이 중요합니다.
  10. 안드로이드 플랫폼에서는 애플리케이션에 네트워킹 권한을 명시적으로 부여하고 평문(cleartext)으로 데이터를 송수신할 수 있도록 허용해야 합니다. 이러한 권한을 활성화하려면 composeApp/src/androidMain/AndroidManifest.xml 를 열고 다음 설정을 추가합니다:

    xml
  11. composeApp 실행 구성을 사용하여 안드로이드 애플리케이션을 실행합니다. 이제 안드로이드 클라이언트도 실행되는 것을 확인할 수 있습니다: 안드로이드에서 실행되는 앱

  12. 데스크톱 클라이언트의 경우, 컨테이너 창에 크기와 제목을 할당해야 합니다. composeApp/src/desktopMain/.../main.kt 파일을 열고 title을 변경하고 state 속성을 설정하여 코드를 수정합니다:

    kotlin
  13. composeApp [desktop] 실행 구성을 사용하여 데스크톱 애플리케이션을 실행합니다: 데스크톱에서 실행되는 앱

  14. composeApp [wasmJs] 실행 구성을 사용하여 웹 클라이언트를 실행합니다:

    데스크톱에서 실행되는 앱

UI 개선

이제 클라이언트가 서버와 통신하지만, 이는 전혀 매력적인 UI가 아닙니다.

  1. composeApp/src/commonMain/.../full_stack_task_manager 에 있는 App.kt 파일을 열고 기존 App을 아래의 AppTaskCard 컴포저블로 대체합니다:

    kotlin

    이 구현으로 클라이언트는 이제 몇 가지 기본 기능을 갖게 됩니다.

    LaunchedEffect 타입을 사용하면 모든 작업이 시작 시 로드되며, LazyColumn 컴포저블을 통해 사용자는 작업을 스크롤할 수 있습니다.

    마지막으로, 별도의 TaskCard 컴포저블이 생성되며, 이는 다시 Card를 사용하여 각 Task의 세부 정보를 표시합니다. 작업을 삭제하고 업데이트하기 위한 버튼이 추가되었습니다.

  2. 클라이언트 애플리케이션을 다시 실행합니다. 예를 들어 안드로이드 앱을 실행합니다. 이제 작업을 스크롤하고, 세부 정보를 보고, 삭제할 수 있습니다: 개선된 UI로 안드로이드에서 실행되는 앱

업데이트 기능 추가

클라이언트를 완성하려면 작업 세부 정보를 업데이트할 수 있는 기능을 통합합니다.

  1. composeApp/src/commonMain/.../full_stack_task_managerApp.kt 파일로 이동합니다.
  2. 아래와 같이 UpdateTaskDialog 컴포저블과 필요한 임포트를 추가합니다:

    kotlin

    이는 대화 상자로 Task의 세부 정보를 표시하는 컴포저블입니다. descriptionpriorityTextField 컴포저블 내에 배치되어 업데이트될 수 있습니다. 사용자가 업데이트 버튼을 누르면 onConfirm() 콜백이 트리거됩니다.

  3. 동일한 파일에서 App 컴포저블을 업데이트합니다:

    kotlin

    선택된 현재 작업이라는 추가적인 상태를 저장하고 있습니다. 이 값이 null이 아니면 UpdateTaskDialog 컴포저블을 호출하며, onConfirm() 콜백은 TaskApi를 사용하여 서버에 POST 요청을 보내도록 설정됩니다.

    마지막으로, TaskCard 컴포저블을 생성할 때 onUpdate() 콜백을 사용하여 currentTask 상태 변수를 설정합니다.

  4. 클라이언트 애플리케이션을 다시 실행합니다. 이제 버튼을 사용하여 각 작업의 세부 정보를 업데이트할 수 있어야 합니다. 안드로이드에서 작업 삭제

다음 단계

이 문서에서는 Kotlin Multiplatform 애플리케이션의 맥락에서 Ktor를 사용했습니다. 이제 여러 서비스와 클라이언트를 포함하는 프로젝트를 생성하고 다양한 플랫폼을 대상으로 할 수 있습니다.

보시다시피, 코드 중복이나 불필요한 부분 없이 기능을 구축하는 것이 가능합니다. 프로젝트의 모든 계층에 필요한 타입은 shared 멀티플랫폼 모듈 내에 배치될 수 있습니다. 서비스에만 필요한 기능은 server 모듈에 들어가고, 클라이언트에만 필요한 기능은 composeApp 에 배치됩니다.

이러한 종류의 개발은 필연적으로 클라이언트 및 서버 기술에 대한 지식을 요구합니다. 그러나 Kotlin Multiplatform 라이브러리와 Compose Multiplatform을(를) 사용하여 배워야 할 새로운 자료의 양을 최소화할 수 있습니다. 초기에는 단일 플랫폼에만 집중하더라도, 애플리케이션 수요가 증가함에 따라 다른 플랫폼을 쉽게 추가할 수 있습니다.