똑같은 코드를 두 번 작성하지 마세요: Kotlin Multiplatform으로 비즈니스 로직 공유하기
Sharing business logic between Android and iOS applications using Kotlin Multiplatform (KMP).

똑같은 코드를 두 번 작성하지 마세요: Kotlin Multiplatform으로 비즈니스 로직 공유하기

Advertisement

Here is a blog post written specifically for My Core Pick.


같은 코드를 두 번 작성하는 것을 멈추세요: Kotlin Multiplatform으로 비즈니스 로직 공유하기

Android 버전 앱에서 버그를 수정했는데, 3주 뒤에 iOS 사용자들도 똑같은 버그로 고통받고 있다는 사실을 깨달은 적이 있다면 손을 들어보세요.

우리 모두 겪어본 일입니다.

이것은 모바일 개발의 전형적인 고충입니다. 두 개의 코드베이스가 있습니다. 두 개의 팀(혹은 매우 지친 한 명의 프리랜서)이 있습니다. 두 개의 언어가 있습니다.

하지만 제품은 단 하나뿐입니다.

수년 동안 업계에서는 이에 대한 해결책으로 "하이브리드(Hybrid)" 프레임워크를 제시했습니다. 우리는 PhoneGap을 시도했습니다. React Native와 씨름했습니다. Flutter에 관심을 두기도 했습니다.

이 도구들은 훌륭하지만, 종종 사용자 인터페이스(UI)에서 타협을 요구합니다. 그들의 생태계 안에서 룩앤필(look and feel)을 처음부터 다시 만들어야 하기 때문입니다.

하지만 네이티브 UI를 유지하고 싶다면 어떻게 해야 할까요? SwiftUI와 Jetpack Compose를 사랑한다면요?

앱의 "얼굴"이 아닌 "두뇌"만 공유하고 싶다면 어떻게 해야 할까요?

Kotlin Multiplatform (KMP)이 바로 그 해결책입니다.

여기 My Core Pick에서는 더 열심히 일하는 것보다 더 스마트하게 일하는 것을 지향합니다. 오늘 저는 왜 KMP가 비즈니스 로직 공유의 미래인지, 그리고 이것이 어떻게 반복적인 작업을 막아주는지 보여드리겠습니다.

"이중 플랫폼(Dual-Platform)"의 함정

Image

해결책을 알아보기 전에 문제점부터 이야기해 봅시다.

iOS와 Android용 네이티브 앱을 만들 때, 단순히 UI만 두 번 작성하는 것이 아닙니다.

말 그대로 모든 것을 두 번 작성하고 있습니다.

Swift와 Kotlin으로 각각 네트워킹 계층을 작성합니다.

CoreData와 Room으로 각각 데이터베이스 로직을 작성합니다.

데이터 유효성 검사 로직(이 이메일이 유효한가?)을 서로 다른 두 파일에 작성합니다.

이것이 바로 중복의 정의입니다.

단지 처음 코드를 작성하는 데 걸리는 시간만의 문제가 아닙니다. 진짜 고통은 나중에 찾아옵니다.

바로 유지보수 단계입니다.

Android에서 비즈니스 규칙을 변경했다면, iOS에서도 변경해야 한다는 것을 기억해야 합니다. 만약 잊어버린다면 앱은 일관성 없게 동작할 것입니다.

사용자들은 이를 알아챕니다. 이유는 모를 수 있지만, 앱이 뭔가 "이상하다"고 느낍니다.

이러한 중복은 버그가 발생할 수 있는 범위를 넓힙니다. 테스트 요구 사항을 두 배로 늘립니다. 개발자의 인지적 부하(cognitive load)를 두 배로 늘립니다.

로직 계층을 처리하는 더 나은 방법이 분명히 있어야 합니다.

Kotlin Multiplatform이란 정확히 무엇인가요?

Image

Kotlin Multiplatform은 전통적인 의미의 프레임워크가 아닙니다.

화면에 캔버스를 렌더링하는 "한 번 작성하면 어디서든 실행되는(write once, run anywhere)" 블랙박스가 아닙니다.

KMP는 플랫폼 간에 코드를 공유하기 위해 설계된 SDK입니다.

KMP는 Android를 위해 Kotlin 코드를 JVM 바이트코드로 컴파일합니다. 이건 쉬운 부분이죠.

하지만 진짜 마법은 동일한 Kotlin 코드를 iOS용 LLVM 바이트코드로 컴파일한다는 점입니다. 즉, Apple 하드웨어에서 네이티브로 실행됩니다.

Xcode 입장에서 보면, 여러분의 공유된 Kotlin 코드는 그저 Objective-C 프레임워크처럼 보입니다.

다른 네이티브 라이브러리와 마찬가지로 import 하고, 함수를 호출하고, 데이터를 읽을 수 있습니다.

여기서의 철학은 간단합니다: 공유된 로직(Shared logic), 네이티브 UI(Native UI).

여러분은 아름다운 SwiftUI 뷰를 유지합니다. 성능 좋은 Jetpack Compose 레이아웃을 유지합니다.

단지 그것들을 구동하는 코드를 두 번 작성하는 것을 멈추는 것입니다.

실제로 무엇을 공유해야 할까요?

Image

제가 처음 KMP를 시작했을 때, 저는 모든 것을 공유하려고 했습니다.

그건 실수였습니다.

KMP의 힘은 어디에 선을 그을지 아는 것에서 나옵니다. 버튼, 애니메이션, 내비게이션 전환 등을 공유하려고 해서는 안 됩니다.

비즈니스 로직을 공유해야 합니다.

그렇다면 실제로는 어떤 모습일까요? 우리가 집중하는 세 가지 주요 기둥은 다음과 같습니다.

1. 데이터 계층 (The Data Layer)

이것은 가장 접근하기 쉬운 부분입니다.

모든 앱은 인터넷과 통신해야 합니다. 모든 앱은 JSON을 파싱해야 합니다.

전통적인 설정에서는 Swift의 Codable 구조체와 Kotlin의 Data Class가 있습니다. 백엔드가 변경되면 둘 다 업데이트해야 합니다.

KMP를 사용하면 데이터 모델을 한 번만 작성합니다.

네트워킹에는 Ktor 같은 라이브러리를, 파싱에는 Kotlin Serialization을 사용합니다.

데이터를 가져오고, 파싱하고, 오류를 처리하는 리포지토리(repository) 클래스를 작성합니다.

iOS와 Android 앱 모두 단순히 repository.getData()를 호출하기만 하면 됩니다. 앱은 데이터가 어떻게 그곳에 도달했는지 신경 쓰지 않습니다. 그저 보여줄 뿐입니다.

2. 로컬 저장소 (Local Storage)

데이터베이스 관리는 복잡합니다.

서로 다른 두 플랫폼에서 마이그레이션을 관리하는 것은 악몽과 같습니다.

KMP를 통해 우리는 SQLDelightRoom(이제 KMP를 지원합니다) 같은 라이브러리를 사용합니다.

데이터베이스 스키마를 한 곳에서 정의합니다. SQL 쿼리를 한 곳에서 작성합니다.

생성된 코드가 플랫폼별 드라이버를 처리합니다.

Android 앱은 SQLite 드라이버를 사용합니다. iOS 앱은 Native 드라이버를 사용합니다.

사용자 데이터를 저장, 검색, 삭제하는 방식을 제어하는 로직은 두 플랫폼에서 동일하게 유지됩니다.

3. "두뇌" (ViewModels)

이 부분이 정말 흥미로워지는 지점입니다.

실제로 ViewModel을 공유할 수 있습니다.

ViewModel은 화면의 상태(state)를 보유하는 구성 요소입니다. 사용자가 버튼을 클릭했을 때 어떤 일이 일어날지 결정합니다. 네트워크가 실패했을 때 무엇을 보여줄지 결정합니다.

이것은 순수한 로직입니다. 픽셀과는 아무런 관련이 없습니다.

ViewModel을 공유함으로써 UI는 믿을 수 없을 정도로 "단순해(dumb)"집니다. 그리고 이것은 좋은 일입니다.

SwiftUI 뷰는 공유된 Kotlin 코드에서 오는 상태 객체(state object)를 관찰(observe)하기만 하면 됩니다.

상태가 isLoading = true라고 하면, UI는 로딩 스피너를 보여줍니다.

UI는 로딩 중인지 알 필요가 없습니다. 그저 지시를 따를 뿐입니다.

작동 원리: 기술적인 엿보기

무슨 생각을 하고 계시는지 압니다.

"말은 좋은데, 설정하는 게 악몽 아니야?"

예전에는 그랬습니다. 하지만 지난 1년 동안 생태계가 엄청나게 성숙했습니다.

프로젝트 구조를 살펴봅시다.

소스 셋 (The Source Sets)

KMP 프로젝트는 "소스 셋(Source Sets)"으로 나뉩니다.

먼저 commonMain이 있습니다.

여기가 코드의 90%가 거주하는 곳입니다. 순수 Kotlin입니다. 여기에는 모델, 리포지토리, 로직이 포함됩니다.

그리고 androidMainiosMain이 있습니다.

이 폴더들은 플랫폼별 코드를 위한 곳입니다.

순수한 알고리즘을 작성한다면 commonMain에 들어갑니다.

하지만 때로는 카메라나 블루투스 같은 특정 기기 기능에 접근해야 할 때가 있습니다.

Kotlin은 기본적으로 iOS 카메라에 접근하는 방법을 모릅니다.

Expect/Actual 메커니즘

이것은 공유된 세계와 네이티브 세계 사이의 다리 역할을 합니다.

인터페이스(Interface)처럼 작동하지만, 언어 레벨에서 작동합니다.

commonMain 폴더에 다음과 같이 작성할 수 있습니다:

expect fun getPlatformName(): String

이제 컴파일러가 불평할 것입니다. 함수를 약속해놓고 구현체를 제공하지 않았다고 말이죠.

그래서 androidMain에 다음과 같이 작성합니다:

actual fun getPlatformName(): String = "Android"

그리고 iosMain에는 이렇게 작성합니다:

actual fun getPlatformName(): String = UIDevice.currentDevice.systemName()

iosMain에서 UIDevice에 접근할 수 있다는 점을 주목하세요. 저것은 Apple API입니다.

KMP가 iOS SDK에 접근할 수 있기 때문에 Kotlin 내부에서 직접 접근할 수 있는 것입니다.

이 메커니즘을 통해 필요할 때마다 네이티브 기능을 활용하면서도 높은 수준의 로직을 공유할 수 있습니다.

실제적인 이점

우리는 My Core Pick의 최근 프로젝트에 KMP를 도입했습니다.

그 결과는 제가 모바일 개발을 바라보는 관점을 바꾸어 놓았습니다.

일관성이 핵심이다 (Consistency is King)

가장 큰 이점은 속도가 아니었습니다. 바로 일관성이었습니다.

앱에는 "건강 점수"를 계산하는 복잡한 로직이 있었습니다.

과거에는 iOS 개발자가 요구사항을 한 가지 방식으로 해석하고, Android 개발자는 다른 방식으로 해석하곤 했습니다. 결국 같은 데이터에 대해 서로 조금씩 다른 점수가 나오곤 했습니다.

KMP를 통해 우리는 알고리즘을 딱 한 번 작성했습니다.

Kotlin으로 철저하게 단위 테스트(Unit Test)를 수행했습니다.

그리고 배포했습니다.

우리는 사용자가 기기를 변경하더라도 점수가 정확히 동일할 것이라는 사실을 확신할 수 있었습니다.

더 빠른 기능 반복 (Faster Feature Iteration)

아키텍처가 설정된 후에는 새로운 기능을 추가하는 속도가 훨씬 빨라졌습니다.

새로운 API 엔드포인트를 추가해야 했을 때, 공유 모듈에 추가했습니다.

갑자기 iOS와 Android 개발자 모두 즉시 그 기능에 접근할 수 있게 되었습니다.

iOS 개발자가 네트워킹 계층 작업을 따라잡을 때까지 기다릴 필요가 없었습니다.

이는 "이어달리기"와 같은 역동성을 만들어냅니다. 핵심 로직 개발자가 길을 닦아놓으면, UI 개발자들이 결승선까지 전력 질주합니다.

강력해진 테스트 (Supercharged Testing)

UI 테스트는 어렵습니다. 비즈니스 로직 테스트는 (비교적) 쉽습니다.

로직이 OS와 분리되어 있기 때문에 JVM에서 테스트를 실행할 수 있습니다.

이것은 엄청나게 빠릅니다. ViewModel을 테스트하기 위해 Android 에뮬레이터나 iOS 시뮬레이터를 띄울 필요가 없습니다.

표준 JUnit 테스트를 작성합니다. 테스트는 밀리초(ms) 단위로 실행됩니다.

이는 더 많은 테스트를 작성하도록 장려하며, 결과적으로 더 안정적인 제품으로 이어집니다.

본격적으로 도입해도 될까요?

새로운 기술을 도입하는 것을 망설일 수도 있습니다.

그것은 건전한 회의감입니다.

하지만 KMP는 거대 기업들이 사용하고 있습니다.

Netflix가 사용합니다. VMWare가 사용합니다. McDonald's가 사용합니다.

Google이 공식적으로 지원합니다.

라이브러리 생태계는 번창하고 있습니다.

의존성 주입(Dependency Injection)을 위해 Koin이 있습니다.

네트워킹을 위해 Ktor가 있습니다.

로깅을 위해 Napier가 있습니다.

도구는 준비되었습니다. 지원도 준비되었습니다.

결론

우리는 모바일 개발의 새로운 시대에 와있습니다.

iOS와 Android 로직을 엄격하게 분리하던 시절은 끝나가고 있습니다. 비용과 시간이 너무 많이 들기 때문입니다.

하지만 우리는 네이티브 UI의 품질을 희생하고 싶지도 않습니다.

Kotlin Multiplatform은 그 최적의 지점(sweet spot)을 찾아냈습니다.

중요한 부분(UI)에서는 플랫폼의 차이를 존중하고, 필요한 부분(로직)에서는 플랫폼의 유사성을 통합합니다.

같은 코드를 두 번 작성하는 것을 멈추세요.

같은 버그를 두 번 고치는 것을 멈추세요.

비즈니스 로직 공유를 시작하세요. 여러분의 코드베이스(그리고 여러분의 정신 건강)가 고마워할 것입니다.


이 심층 분석이 마음에 드셨나요? 여기 My Core Pick에서 더 많은 기술적 인사이트를 확인해 보세요.

🔥 Share this Insight

𝕏 Post
Sharing business logic between Android and iOS applications using Kotlin Multiplatform (KMP).

똑같은 코드를 두 번 작성하지 마세요: Kotlin Multiplatform으로 비즈니스 로직 공유하기

Here is a blog post written specifically for **My Core Pick**. *** # 같은 코드를 두 번 작성하는 것을 멈추세요: Kotl...

My Core Pick.
mycorepick.com

Advertisement

Back to Posts