Kotlin Multiplatform로 풀스택 애플리케이션 구축하기
코드 예제: full-stack-task-manager
사용된 플러그인:
이 문서에서는 안드로이드, iOS, 데스크톱 플랫폼에서 실행되는 Kotlin 풀스택 애플리케이션을 개발하는 방법을 Ktor를 활용하여 원활한 데이터 처리를 수행하는 방법을 배우게 됩니다.
이 튜토리얼이 끝나면 다음을 수행하는 방법을 알게 될 것입니다:
- Kotlin Multiplatform을(를) 사용하여 풀스택 애플리케이션을 생성합니다.
- IntelliJ IDEA로 생성된 프로젝트를 이해합니다.
- Ktor 서비스를 호출하는 Compose Multiplatform 클라이언트를 생성합니다.
- 설계의 다양한 계층에서 공유 타입을 재사용합니다.
- 멀티플랫폼 라이브러리를 올바르게 포함하고 구성합니다.
이전 튜토리얼에서는 작업 관리자 예제를 사용하여
이 튜토리얼에서는 Ktor 서비스를 사용하여 표시될 데이터를 가져오고 안드로이드, iOS, 데스크톱 플랫폼을 대상으로 하는 클라이언트를 생성합니다. 가능한 모든 곳에서 클라이언트와 서버 간에 데이터 타입을 공유하여 개발 속도를 높이고 오류 발생 가능성을 줄일 것입니다.
사전 요구 사항
이전 문서와 마찬가지로 IntelliJ IDEA를 IDE로 사용할 것입니다. 환경을 설치하고 구성하려면 다음 Kotlin Multiplatform 빠른 시작 가이드를 참조하세요.
Compose Multiplatform를 처음 사용하는 경우, 이 튜토리얼을 시작하기 전에 Compose Multiplatform 시작하기 튜토리얼을 완료하는 것이 좋습니다. 작업의 복잡성을 줄이기 위해 단일 클라이언트 플랫폼에 집중할 수 있습니다. 예를 들어, iOS를 사용해 본 적이 없다면 데스크톱 또는 안드로이드 개발에 집중하는 것이 현명할 수 있습니다.
새 프로젝트 생성
Ktor 프로젝트 제너레이터 대신 IntelliJ IDEA의 Kotlin Multiplatform 프로젝트 마법사를 사용하세요. 이는 클라이언트 및 서비스로 확장할 수 있는 기본 멀티플랫폼 프로젝트를 생성합니다. 클라이언트는 SwiftUI와 같은 네이티브 UI 라이브러리를 사용할 수도 있지만, 이 튜토리얼에서는 Compose Multiplatform을(를) 사용하여 모든 플랫폼에 대한 공유 UI를 생성할 것입니다.
- IntelliJ IDEA를 시작합니다.
- IntelliJ IDEA에서 File | New | Project를 선택합니다.
- 왼쪽 패널에서 Kotlin Multiplatform를 선택합니다.
- New Project 창에서 다음 필드를 지정합니다:
- Name : full-stack-task-manager
- Group : com.example.ktor
대상 플랫폼으로 Android , Desktop , Server를 선택합니다.
Mac을 사용 중이라면 iOS도 선택하세요. Share UI 옵션이 선택되어 있는지 확인합니다.
Create 버튼을 클릭하고 IDE가 프로젝트를 생성하고 임포트할 때까지 기다립니다.
서비스 실행
- Project 보기에서 server/src/main/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 Application.kt 파일을 엽니다.
- 애플리케이션을 시작하려면
main()
함수 옆에 있는 Run 버튼 ()을 클릭합니다.
Run 도구 창에 "Responding at http://0.0.0.0:8080" 메시지로 끝나는 새 탭이 열릴 것입니다.
애플리케이션을 열려면 http://0.0.0.0:8080/로 이동합니다. 브라우저에 Ktor의 메시지가 표시될 것입니다.
프로젝트 검사
server 폴더는 프로젝트 내 세 개의 Kotlin 모듈 중 하나입니다. 나머지 두 개는 shared 와 composeApp 입니다.
server 모듈의 구조는 Ktor Project Generator로 생성된 것과 매우 유사합니다. 플러그인 및 의존성을 선언하는 전용 빌드 파일과 Ktor 서비스를 빌드하고 시작하는 코드를 포함하는 소스 세트가 있습니다:

Application.kt 파일의 라우팅 지침을 보면 greet()
함수 호출을 볼 수 있습니다:
이는 Greeting
타입의 인스턴스를 생성하고 greet()
메서드를 호출합니다. Greeting
클래스는 shared 모듈에 정의되어 있습니다:
shared 모듈은 다양한 대상 플랫폼에서 사용될 코드를 포함합니다.
shared 모듈 세트의 commonMain 소스는 모든 플랫폼에서 사용될 타입을 보유합니다. 보시다시피, Greeting
타입이 정의된 곳입니다. 이것은 또한 서버와 모든 다른 클라이언트 플랫폼 간에 공유될 공통 코드를 넣을 곳이기도 합니다.
shared 모듈은 또한 클라이언트를 제공하고자 하는 각 플랫폼에 대한 소스 세트를 포함합니다. 이는 commonMain 내에 선언된 타입이 대상 플랫폼에 따라 달라지는 기능이 필요할 수 있기 때문입니다. Greeting
타입의 경우, 플랫폼별 API를 사용하여 현재 플랫폼의 이름을 가져오려고 합니다. 이는 expect 및 actual 선언을 통해 달성됩니다.
shared 모듈의 commonMain 소스 세트에서 expect
키워드로 getPlatform()
함수를 선언합니다:
그러면 각 대상 플랫폼은 아래와 같이 getPlatform()
함수의 actual
선언을 제공해야 합니다:
프로젝트에는 하나의 추가 모듈인 composeApp 모듈이 있습니다. 이는 안드로이드, iOS, 데스크톱 및 웹 클라이언트 앱의 코드를 포함합니다. 이 앱들은 현재 Ktor 서비스에 연결되어 있지 않지만, 공유된 Greeting
클래스를 사용합니다.
클라이언트 애플리케이션 실행
대상에 대한 실행 구성을 실행하여 클라이언트 애플리케이션을 실행할 수 있습니다. iOS 시뮬레이터에서 애플리케이션을 실행하려면 아래 단계를 따르세요:
- IntelliJ IDEA에서 iosApp 실행 구성과 시뮬레이션된 장치를 선택합니다.
- Run 버튼 (
)을 클릭하여 구성을 실행합니다.
iOS 앱을 실행하면 내부적으로 Xcode로 빌드되고 iOS 시뮬레이터에서 시작됩니다. 이 앱은 클릭 시 이미지를 토글하는 버튼을 표시합니다.
버튼을 처음 누르면 현재 플랫폼의 세부 정보가 텍스트에 추가됩니다. 이를 달성하는 코드는 composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager/App.kt에서 찾을 수 있습니다:
kotlin이는 이 문서의 뒷부분에서 수정할 컴포저블 함수입니다. 현재 중요한 것은 UI를 표시하고 공유
Greeting
타입을 사용하며, 이는 다시 공통Platform
인터페이스를 구현하는 플랫폼별 클래스를 사용한다는 점입니다.
이제 생성된 프로젝트의 구조를 이해했으니 작업 관리자 기능을 점진적으로 추가할 수 있습니다.
모델 타입 추가
먼저 모델 타입을 추가하고 클라이언트와 서버 모두에서 접근 가능한지 확인합니다.
- shared/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 model이라는 새 패키지를 생성합니다.
- 새 패키지 내에 Task.kt라는 새 파일을 생성합니다.
우선순위를 나타내는
enum
과 작업을 나타내는class
를 추가합니다.Task
클래스는kotlinx.serialization
라이브러리의Serializable
타입으로 어노테이션됩니다:kotlin임포트와 어노테이션 모두 컴파일되지 않는 것을 알 수 있습니다. 이는 프로젝트가 아직
kotlinx.serialization
라이브러리에 대한 의존성을 가지고 있지 않기 때문입니다.shared/build.gradle.kts 로 이동하여 직렬화 플러그인을 추가합니다:
kotlin동일한 파일에서 commonMain 소스 세트에 새 의존성을 추가합니다:
kotlin- gradle/libs.versions.toml 로 이동하여 다음을 정의합니다: toml
- IntelliJ IDEA에서 Build | Sync Project with Gradle Files를 선택하여 업데이트를 적용합니다. Gradle 임포트가 완료되면 Task.kt 파일이 성공적으로 컴파일되는 것을 확인할 수 있습니다.
직렬화 플러그인을 포함하지 않아도 코드는 컴파일되었을 수 있지만, 네트워크를 통해 Task
객체를 직렬화하는 데 필요한 타입은 생성되지 않았을 것입니다. 이는 서비스를 호출하려고 할 때 런타임 오류로 이어질 수 있습니다.
직렬화 플러그인을 다른 모듈(예: server 또는 composeApp )에 배치해도 빌드 시점에는 오류가 발생하지 않았을 것입니다. 하지만 다시 말하지만, 직렬화에 필요한 추가 타입은 생성되지 않아 런타임 오류로 이어질 것입니다.
서버 생성
다음 단계는 작업 관리자를 위한 서버 구현을 생성하는 것입니다.
- server/src/main/kotlin/com/example/ktor/full_stack_task_manager 폴더로 이동하여 model이라는 하위 패키지를 생성합니다.
이 패키지 내에 새 TaskRepository.kt 파일을 생성하고 리포지토리에 대한 다음 인터페이스를 추가합니다:
kotlin동일한 패키지에 다음 클래스를 포함하는 새 파일 InMemoryTaskRepository.kt를 생성합니다:
kotlinserver/src/main/kotlin/.../Application.kt 로 이동하여 기존 코드를 다음 구현으로 대체합니다:
kotlin이 구현은 이전 튜토리얼과 매우 유사하지만, 단순화를 위해 모든 라우팅 코드를
Application.module()
함수 내에 배치했다는 점이 다릅니다.이 코드를 입력하고 임포트를 추가하면, 코드가 웹 클라이언트와 상호 작용하기 위한
CORS플러그인을 포함하여 의존성으로 포함되어야 하는 여러 Ktor 플러그인을 사용하므로 여러 컴파일 오류가 발생할 것입니다.필수 의존성: io.ktor:%artifact_name% 코드 예제: full-stack-task-manager 네이티브 서버 지원: ✅- gradle/libs.versions.toml 파일을 열고 다음 라이브러리를 정의합니다: toml
서버 모듈 빌드 파일( server/build.gradle.kts )을 열고 다음 의존성을 추가합니다:
kotlin- 다시 한번, 메인 메뉴에서 Build | Sync Project with Gradle Files를 선택합니다. 임포트가 완료되면
ContentNegotiation
타입과json()
함수의 임포트가 제대로 작동하는 것을 확인할 수 있습니다. - 서버를 재실행합니다. 브라우저에서 경로에 도달할 수 있음을 확인할 수 있습니다.
클라이언트 생성
클라이언트가 서버에 접근할 수 있도록 Ktor 클라이언트를 포함해야 합니다. 여기에는 세 가지 유형의 의존성이 관련됩니다:
- Ktor 클라이언트의 핵심 기능.
- 네트워킹을 처리하는 플랫폼별 엔진.
- 콘텐츠 협상 및 직렬화 지원.
- gradle/libs.versions.toml 파일에 다음 라이브러리를 추가합니다: toml
- composeApp/build.gradle.kts 로 이동하여 다음 의존성을 추가합니다: kotlin
이 작업이 완료되면 클라이언트가 Ktor 클라이언트를 감싸는 얇은 래퍼 역할을 할
TaskApi
타입을 추가할 수 있습니다. - 빌드 파일의 변경 사항을 임포트하려면 메인 메뉴에서 Build | Sync Project with Gradle Files를 선택합니다.
- composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 로 이동하여 network라는 새 패키지를 생성합니다.
새 패키지 내에 클라이언트 구성을 위한 새 HttpClientManager.kt 를 생성합니다:
kotlin1.2.3.4
를 현재 머신의 IP 주소로 대체해야 합니다. 안드로이드 가상 장치(Android Virtual Device) 또는 iOS 시뮬레이터에서 실행되는 코드에서는0.0.0.0
또는localhost
로 호출할 수 없습니다.동일한 composeApp/.../full_stack_task_manager/network 패키지에 다음 구현이 포함된 새 TaskApi.kt 파일을 생성합니다:
kotlincommonMain/.../App.kt 로 이동하여 App 컴포저블을 아래 구현으로 대체합니다. 이는
TaskApi
타입을 사용하여 서버에서 작업 목록을 검색한 다음 각 작업의 이름을 열에 표시합니다:kotlin서버가 실행 중인 동안, iosApp 실행 구성을 사용하여 iOS 애플리케이션을 테스트합니다.
Fetch Tasks 버튼을 클릭하여 작업 목록을 표시합니다:
NOTE
이 데모에서는 명확성을 위해 프로세스를 단순화하고 있습니다. 실제 애플리케이션에서는 네트워크를 통해 암호화되지 않은 데이터를 전송하는 것을 피하는 것이 중요합니다.안드로이드 플랫폼에서는 애플리케이션에 네트워킹 권한을 명시적으로 부여하고 평문(cleartext)으로 데이터를 송수신할 수 있도록 허용해야 합니다. 이러한 권한을 활성화하려면 composeApp/src/androidMain/AndroidManifest.xml 를 열고 다음 설정을 추가합니다:
xmlcomposeApp 실행 구성을 사용하여 안드로이드 애플리케이션을 실행합니다. 이제 안드로이드 클라이언트도 실행되는 것을 확인할 수 있습니다:
데스크톱 클라이언트의 경우, 컨테이너 창에 크기와 제목을 할당해야 합니다. composeApp/src/desktopMain/.../main.kt 파일을 열고
title
을 변경하고state
속성을 설정하여 코드를 수정합니다:kotlincomposeApp [desktop] 실행 구성을 사용하여 데스크톱 애플리케이션을 실행합니다:
composeApp [wasmJs] 실행 구성을 사용하여 웹 클라이언트를 실행합니다:
UI 개선
이제 클라이언트가 서버와 통신하지만, 이는 전혀 매력적인 UI가 아닙니다.
composeApp/src/commonMain/.../full_stack_task_manager 에 있는 App.kt 파일을 열고 기존
App
을 아래의App
및TaskCard
컴포저블로 대체합니다:kotlin이 구현으로 클라이언트는 이제 몇 가지 기본 기능을 갖게 됩니다.
LaunchedEffect
타입을 사용하면 모든 작업이 시작 시 로드되며,LazyColumn
컴포저블을 통해 사용자는 작업을 스크롤할 수 있습니다.마지막으로, 별도의
TaskCard
컴포저블이 생성되며, 이는 다시Card
를 사용하여 각Task
의 세부 정보를 표시합니다. 작업을 삭제하고 업데이트하기 위한 버튼이 추가되었습니다.클라이언트 애플리케이션을 다시 실행합니다. 예를 들어 안드로이드 앱을 실행합니다. 이제 작업을 스크롤하고, 세부 정보를 보고, 삭제할 수 있습니다:
업데이트 기능 추가
클라이언트를 완성하려면 작업 세부 정보를 업데이트할 수 있는 기능을 통합합니다.
- composeApp/src/commonMain/.../full_stack_task_manager 의 App.kt 파일로 이동합니다.
아래와 같이
UpdateTaskDialog
컴포저블과 필요한 임포트를 추가합니다:kotlin이는 대화 상자로
Task
의 세부 정보를 표시하는 컴포저블입니다.description
과priority
는TextField
컴포저블 내에 배치되어 업데이트될 수 있습니다. 사용자가 업데이트 버튼을 누르면onConfirm()
콜백이 트리거됩니다.동일한 파일에서
App
컴포저블을 업데이트합니다:kotlin선택된 현재 작업이라는 추가적인 상태를 저장하고 있습니다. 이 값이 null이 아니면
UpdateTaskDialog
컴포저블을 호출하며,onConfirm()
콜백은TaskApi
를 사용하여 서버에 POST 요청을 보내도록 설정됩니다.마지막으로,
TaskCard
컴포저블을 생성할 때onUpdate()
콜백을 사용하여currentTask
상태 변수를 설정합니다.- 클라이언트 애플리케이션을 다시 실행합니다. 이제 버튼을 사용하여 각 작업의 세부 정보를 업데이트할 수 있어야 합니다.
다음 단계
이 문서에서는 Kotlin Multiplatform 애플리케이션의 맥락에서 Ktor를 사용했습니다. 이제 여러 서비스와 클라이언트를 포함하는 프로젝트를 생성하고 다양한 플랫폼을 대상으로 할 수 있습니다.
보시다시피, 코드 중복이나 불필요한 부분 없이 기능을 구축하는 것이 가능합니다. 프로젝트의 모든 계층에 필요한 타입은 shared 멀티플랫폼 모듈 내에 배치될 수 있습니다. 서비스에만 필요한 기능은 server 모듈에 들어가고, 클라이언트에만 필요한 기능은 composeApp 에 배치됩니다.
이러한 종류의 개발은 필연적으로 클라이언트 및 서버 기술에 대한 지식을 요구합니다. 그러나 Kotlin Multiplatform 라이브러리와 Compose Multiplatform을(를) 사용하여 배워야 할 새로운 자료의 양을 최소화할 수 있습니다. 초기에는 단일 플랫폼에만 집중하더라도, 애플리케이션 수요가 증가함에 따라 다른 플랫폼을 쉽게 추가할 수 있습니다.