코틀린 멀티플랫폼으로 풀스택 애플리케이션 구축하기
코틀린 멀티플랫폼으로 풀스택 애플리케이션 구축하기
코드 예제: full-stack-task-manager
사용된 플러그인:
이 문서에서는 Ktor를 활용하여 원활한 데이터 처리를 구현하면서 Android, iOS, 웹 및 데스크톱 플랫폼에서 실행되는 Kotlin 기반 풀스택 애플리케이션을 개발하는 방법을 배웁니다.
이 튜토리얼을 마치면 다음 사항을 수행할 수 있게 됩니다:
- Kotlin Multiplatform을 사용하여 풀스택 애플리케이션 생성.
- IntelliJ IDEA에서 생성된 프로젝트 구조 이해.
- Ktor 서비스를 호출하는 Compose Multiplatform 클라이언트 제작.
- 설계의 여러 계층에서 공유 타입(Shared types) 재사용.
- 멀티플랫폼 라이브러리의 올바른 포함 및 구성.
이전 튜토리얼들에서는 할 일 관리자(Task Manager) 예제를 사용하여
이제 표시할 데이터를 가져오기 위해 Ktor 서비스를 사용하는 Android, iOS, 웹 및 데스크톱 플랫폼용 클라이언트를 만들 것입니다. 가능한 한 클라이언트와 서버 간에 데이터 타입을 공유하여 개발 속도를 높이고 오류 발생 가능성을 줄일 것입니다.
사전 요구 사항
이전 문서들과 마찬가지로 IntelliJ IDEA를 IDE로 사용합니다. 환경 설치 및 구성에 대해서는 Kotlin Multiplatform 퀵스타트 를 참조하세요.
Compose Multiplatform을 처음 사용하는 경우, 이 튜토리얼을 시작하기 전에 Compose Multiplatform 시작하기 튜토리얼을 먼저 완료하는 것을 권장합니다. 작업의 복잡도를 줄이기 위해 단일 클라이언트 플랫폼에 집중할 수도 있습니다. 예를 들어 iOS를 사용해 본 적이 없다면 데스크톱이나 Android 개발에 집중하는 것이 현명할 수 있습니다.
새 프로젝트 생성하기
Ktor 프로젝트 생성기 대신 IntelliJ IDEA의 Kotlin Multiplatform 프로젝트 위저드(Wizard)를 사용합니다. 이를 통해 클라이언트와 서비스를 확장해 나갈 수 있는 기본 멀티플랫폼 프로젝트가 생성됩니다. 클라이언트는 SwiftUI와 같은 네이티브 UI 라이브러리를 사용할 수도 있지만, 이 튜토리얼에서는 Compose Multiplatform을 사용하여 모든 플랫폼을 위한 공유 UI를 만들 것입니다.
- IntelliJ IDEA를 실행합니다.
- IntelliJ IDEA에서 File | New | Project를 선택합니다.
- 왼쪽 패널에서 Kotlin Multiplatform을 선택합니다.
- New Project 창에서 다음 필드를 지정합니다:
- Name : full-stack-task-manager
- Project ID : com.example.ktor
대상 플랫폼으로 Android, Desktop, Web, 그리고 Server를 선택합니다.
Mac을 사용 중이라면 iOS도 선택하세요. Share UI 옵션이 선택되어 있는지 확인합니다.

Create 버튼을 클릭하고 IDE가 프로젝트를 생성하고 임포트할 때까지 기다립니다.
서비스 실행하기
- IntelliJ IDEA에서 ApplicationKt 실행 구성을 선택합니다.

- Run 버튼 (
)을 클릭하여 해당 구성을 실행합니다.
Run 도구 창에 새 탭이 열립니다.
브라우저에서 http://0.0.0.0:8080/로 접속하여 애플리케이션을 엽니다. 브라우저에 Ktor가 표시하는 메시지가 나타나야 합니다.

프로젝트 살펴보기
server 폴더는 프로젝트에 있는 세 개의 Kotlin 모듈 중 하나입니다. 나머지 두 개는 core와 app입니다.
server 모듈의 구조는 Ktor 프로젝트 생성기에서 생성된 구조와 매우 유사합니다. 플러그인과 의존성을 선언하기 위한 전용 빌드 파일이 있으며, Ktor 서비스를 빌드하고 실행하기 위한 코드가 포함된 소스 세트가 있습니다:

Application.kt 파일의 라우팅 지침을 살펴보면 sayHello() 함수를 호출하는 것을 볼 수 있습니다:
sayHello() 함수는 core 모듈에 정의되어 있습니다. 이곳이 서버와 모든 다양한 클라이언트 플랫폼 간에 공유될 공통 코드를 두는 곳입니다.
app/shared/src/commonMain 모듈 내의 Greeting.kt 파일을 열어보면 sayHello() 함수가 그곳에서도 사용되고 있음을 확인할 수 있습니다:
app 모듈에는 다음 서브모듈들이 포함되어 있습니다:
- androidApp, desktopApp, iosApp, webApp 서브모듈은 각각 Android, 데스크톱, iOS, 웹 클라이언트 앱을 위한 플랫폼별 코드를 담고 있습니다. 현재 이 클라이언트 앱들 중 어느 것도 Ktor 서비스와 연결되어 있지 않습니다.
shared 서브모듈은 클라이언트를 제공하려는 각 플랫폼에 대한 소스 세트를 포함합니다. 이는 commonMain 내에 선언된 타입들이 대상 플랫폼마다 다른 기능을 필요로 하기 때문입니다.
예를 들어,
Greeting타입에서 현재 플랫폼의 이름은 기대(expected) 및 실제(actual) 선언을 통해 플랫폼별 API를 사용하여 가져옵니다.shared 서브모듈의 commonMain 소스 세트에서
getPlatform()함수는expect키워드와 함께 선언되어 있습니다:kotlin그런 다음 아래와 같이 각 대상 플랫폼은
getPlatform()함수의actual선언을 제공합니다:kotlinkotlinkotlinkotlin
클라이언트 애플리케이션 실행하기
대상 플랫폼에 대한 실행 구성을 실행하여 클라이언트 애플리케이션을 구동할 수 있습니다. iOS 시뮬레이터에서 애플리케이션을 실행하려면 아래 단계를 따르세요:
- IntelliJ IDEA에서 iosApp 실행 구성과 시뮬레이션 장치를 선택합니다.

- Run 버튼 (
)을 클릭하여 구성을 실행합니다.
iOS 앱을 실행하면 내부적으로 Xcode로 빌드되어 iOS 시뮬레이터에서 실행됩니다. 앱에는 클릭 시 이미지를 토글하는 버튼이 표시됩니다.

버튼을 처음 누르면 현재 플랫폼의 세부 정보가 텍스트에 추가됩니다. 이를 구현하는 코드는 app/shared/src/commonMain/kotlin/com/example/ktor/App.kt에서 찾을 수 있습니다:
kotlin이것은 Composable 함수이며, 이 문서의 뒷부분에서 수정할 예정입니다. 지금 중요한 것은 이것이 UI를 표시하고 공유
Greeting타입을 활용하며, 이 타입은 다시 공통Platform인터페이스를 구현하는 플랫폼별 클래스를 사용한다는 점입니다.
생성된 프로젝트의 구조를 이해했으므로, 이제 할 일 관리자 기능을 점진적으로 추가할 수 있습니다.
모델 타입 추가하기
먼저 모델 타입을 추가하고 클라이언트와 서버 모두에서 접근 가능한지 확인합니다.
- gradle/libs.versions.toml 파일로 이동하여 다음
kotlinx.serialization의존성을 정의합니다:toml core/build.gradle.kts 파일로 이동하여 직렬화(serialization) 플러그인을 추가합니다:
kotlin같은 파일에서 commonMain 소스 세트에 새 의존성을 추가합니다:
kotlin- IntelliJ IDEA에서 Build | Sync Project with Gradle Files를 선택하여 업데이트를 적용합니다. Gradle 임포트가 완료되면 Task.kt 파일이 성공적으로 컴파일되는 것을 확인할 수 있습니다.
- core/src/commonMain/kotlin/com/example/ktor 폴더로 이동하여 model이라는 새 패키지를 생성합니다.
- 새 패키지 안에 Task.kt라는 새 파일을 생성합니다.
우선순위를 나타내는 enum과 할 일을 나타내는 클래스를 추가합니다.
Task클래스는kotlinx.serialization라이브러리의Serializable어노테이션을 가집니다:kotlin
서버 생성하기
다음 단계는 할 일 관리자를 위한 서버 구현을 만드는 것입니다.
- server/src/main/kotlin/com/example/ktor 폴더로 이동하여 model이라는 서브 패키지를 생성합니다.
이 패키지 안에 TaskRepository.kt 파일을 새로 만들고 리포지토리를 위한 다음 인터페이스를 추가합니다:
kotlin같은 패키지에 InMemoryTaskRepository.kt라는 새 파일을 만들고 다음 클래스를 작성합니다:
kotlinserver/src/main/kotlin/.../Application.kt로 이동하여 기존 코드를 아래 구현으로 교체합니다:
kotlin이 구현은 단순화를 위해 모든 라우팅 코드를
Application.module()함수 안에 배치했다는 점을 제외하면 이전 튜토리얼의 내용과 매우 유사합니다.이 코드를 입력하고 임포트를 추가하면 컴파일 에러가 여러 개 발생할 것입니다. 이는 코드에서 웹 클라이언트와의 상호 작용을 위한
CORS플러그인을 포함하여 의존성으로 추가해야 할 여러 Ktor 플러그인을 사용하고 있기 때문입니다.필요한 의존성: io.ktor:%artifact_name%- gradle/libs.versions.toml 파일을 열고 다음 라이브러리들을 정의합니다: toml
서버 모듈 빌드 파일(server/build.gradle.kts)을 열고 다음 의존성들을 추가합니다:
kotlin- 다시 한번 메인 메뉴에서 Build | Sync Project with Gradle Files를 실행합니다. 임포트가 완료되면
ContentNegotiation타입과json()함수에 대한 임포트가 정상적으로 작동하는 것을 확인할 수 있습니다. - 서버를 다시 실행합니다. 브라우저에서 경로에 접근할 수 있음을 확인할 수 있습니다.
클라이언트 생성하기
클라이언트가 서버에 접근할 수 있도록 하려면 Ktor Client를 포함해야 합니다. 여기에는 세 가지 유형의 의존성이 관련됩니다:
- Ktor Client의 핵심(Core) 기능.
- 네트워킹을 처리하기 위한 플랫폼별 엔진.
- 콘텐츠 협상(Content Negotiation) 및 직렬화 지원.
- gradle/libs.versions.toml 파일에 다음 라이브러리들을 추가합니다: toml
- app/shared/build.gradle.kts로 이동하여 다음 의존성들을 추가합니다: kotlin
이 작업이 완료되면 클라이언트에서 Ktor Client를 감싸는 얇은 래퍼(wrapper) 역할을 할
TaskApi타입을 추가할 수 있습니다. - 메인 메뉴에서 Build | Sync Project with Gradle Files를 선택하여 빌드 파일의 변경 사항을 임포트합니다.
- app/shared/src/commonMain/kotlin/com/example/ktor 폴더로 이동하여 network라는 새 패키지를 생성합니다.
새 패키지 안에 클라이언트 설정을 위한 HttpClientManager.kt 파일을 생성합니다:
kotlin1.2.3.4를 현재 머신의 IP 주소로 바꾸세요. Android 가상 장치나 iOS 시뮬레이터에서 실행되는 코드에서는0.0.0.0또는localhost로 호출할 수 없습니다.TIP
IP 주소 찾기:
모바일 시뮬레이터는
localhost에 도달할 수 없으므로 머신의 실제 IP 주소가 필요합니다. IP 주소를 확인하려면 다음 명령어 중 하나를 실행하세요:- macOS:
ifconfig | grep "inet " | grep -v 127.0.0.1 - Linux:
hostname -I | awk '{print $1}' - Windows:
ipconfig실행 후 "IPv4 Address" 확인
- macOS:
같은 app/shared/.../network 패키지에 다음 구현이 담긴 TaskApi.kt 파일을 생성합니다:
kotlinapp/shared/.../App.kt로 이동하여 코드를 아래 구현으로 교체합니다. 이 코드는
TaskApi타입을 사용하여 서버에서 할 일 목록을 가져온 다음, 각 할 일의 이름을 컬럼(Column)에 표시합니다:kotlin서버가 실행 중인 상태에서 iosApp 실행 구성을 사용하여 iOS 애플리케이션을 테스트합니다.
Fetch Tasks 버튼을 클릭하여 할 일 목록을 표시합니다:

NOTE
이 데모에서는 명확성을 위해 프로세스를 단순화했습니다. 실제 애플리케이션에서는 네트워크를 통해 암호화되지 않은 데이터를 전송하는 것을 피하는 것이 매우 중요합니다.Android 플랫폼에서는 애플리케이션에 네트워킹 권한을 명시적으로 부여하고 일반 텍스트(cleartext) 데이터를 주고받을 수 있도록 허용해야 합니다. 이 권한을 활성화하려면 app/androidApp/src/main/AndroidManifest.xml을 열고 다음 설정을 추가하세요:
xmlapp.androidApp 실행 구성을 사용하여 Android 애플리케이션을 실행합니다. 이제 Android 클라이언트도 정상적으로 실행되는 것을 볼 수 있습니다:

데스크톱 클라이언트의 경우, 창에 크기와 타이틀을 지정할 것입니다. app/desktopApp/src/.../main.kt 파일을 열고
title을 변경하고state속성을 설정하여 코드를 수정합니다:kotlinapp [hot] 🔥 실행 구성을 사용하여 데스크톱 애플리케이션을 실행합니다:

다음 실행 구성 중 하나를 사용하여 웹 클라이언트를 실행합니다:
- app [js]: Kotlin/JS 애플리케이션을 실행합니다.
- app [wasmJs]: Kotlin/Wasm 애플리케이션을 실행합니다.

UI 개선하기
이제 클라이언트가 서버와 통신하고 있지만, 아직 매력적인 UI라고 하기는 어렵습니다.
app/shared/src/commonMain/.../ktor에 위치한 App.kt 파일을 열고 기존
App을 아래의App및TaskCardComposable로 교체합니다:kotlin이 구현을 통해 클라이언트는 기본적인 기능을 갖추게 되었습니다.
LaunchedEffect타입을 사용하여 시작 시 모든 할 일을 로드하고,LazyColumnComposable을 사용하여 사용자가 할 일 목록을 스크롤할 수 있게 했습니다.마지막으로 별도의
TaskCardComposable을 만들어Card를 사용하여 각Task의 세부 정보를 표시했습니다. 할 일을 삭제하거나 업데이트하기 위한 버튼들도 추가되었습니다.클라이언트 애플리케이션(예: Android 앱)을 다시 실행합니다. 이제 할 일 목록을 스크롤하고 세부 정보를 확인하며 삭제할 수 있습니다:

업데이트 기능 추가하기
클라이언트를 완성하기 위해 할 일의 세부 정보를 업데이트할 수 있는 기능을 통합합니다.
- app/shared/src/commonMain/.../ktor에 있는 App.kt 파일로 이동합니다.
아래와 같이
UpdateTaskDialogComposable과 필요한 임포트를 추가합니다:kotlin이 Composable은 다이얼로그 박스로
Task의 세부 정보를 표시합니다.description과priority는TextFieldComposable 안에 배치되어 업데이트가 가능합니다. 사용자가 업데이트 버튼을 누르면onConfirm()콜백이 호출됩니다.같은 파일에서
AppComposable을 업데이트합니다:kotlin선택된 현재 할 일을 저장하기 위해 추가적인 상태(state)를 관리합니다. 이 값이 null이 아니면
UpdateTaskDialogComposable을 호출하고,onConfirm()콜백이TaskApi를 사용하여 서버에 POST 요청을 보내도록 설정합니다.마지막으로
TaskCardComposable을 생성할 때onUpdate()콜백을 사용하여currentTask상태 변수를 설정합니다.- 클라이언트 애플리케이션을 다시 실행합니다. 이제 버튼을 사용하여 각 할 일의 세부 정보를 업데이트할 수 있습니다.

다음 단계
이 문서에서는 Kotlin Multiplatform 애플리케이션의 맥락 내에서 Ktor를 사용해 보았습니다. 이제 다양한 플랫폼을 대상으로 하는 여러 서비스와 클라이언트가 포함된 프로젝트를 만들 수 있습니다.
살펴보았듯이 코드 중복이나 낭비 없이 기능을 구축할 수 있습니다. 프로젝트의 모든 계층에서 필요한 타입은 core 멀티플랫폼 모듈에 배치할 수 있습니다. 서비스에만 필요한 기능은 server 모듈에, 클라이언트에만 필요한 기능은 app 모듈에 배치합니다.
이러한 방식의 개발은 클라이언트와 서버 기술 모두에 대한 지식이 필요합니다. 하지만 Kotlin Multiplatform 라이브러리와 Compose Multiplatform을 사용하면 새로 배워야 할 내용의 양을 최소화할 수 있습니다. 처음에는 단일 플랫폼에만 집중하더라도 애플리케이션에 대한 수요가 늘어남에 따라 다른 플랫폼을 쉽게 추가할 수 있습니다.

