Slide 1

Slide 1 text

Kotlin Multiplatformを軸にした 求⼈アプリ全⾯リニューアルの技術戦略

Slide 2

Slide 2 text

宮川 昌⾼ ディップ株式会社 iOSエンジニアとして 「バイトル」アプリ開発を担当 神奈川県在住 趣味:サーフィン、格闘技観戦 Zenn: zenn.dev/masa38 X: @matthew3880

Slide 3

Slide 3 text

VISION “Labor force solution company” ⼈材サービスとDXサービスの提供を通して、労働市場における諸課題を解決し、 誰もが働く喜びと幸せを感じられる社会の実現を⽬指します。 ⼈材サービス事業 DX事業 Human work force solution Digital labor force solution × ユーザーファーストな独⾃機能を搭載した、 求⼈情報‧⼈材紹介サービスの提供を通じて、 ユーザーの就業課題を解決しています。 バイトコミュニケーションアプリ『バイトルトーク』や、 機能を絞ったシンプルなSaaS型の『コボット』を通じて、 職場環境やコミュニケーション課題を解決しています。

Slide 4

Slide 4 text

dip_tec h | ビジネスに貢献するディップのエンジニアの挑戦 事業戦略とアーキテクチャの「ズレ」を解消すべく 24年のプロダクト『バイトル』のフルリニューアルに挑戦中 As Is (クロスセル構造に対応したシステム) To be (アップセル構造に対応したシステム) 統合基盤 商品A 商品B 商品B ID-A ID-B ID-C 商品 A 商品 B 商品 B 統合ID 各プロダクトが独⽴した「単品」管理 統合基盤による「パッケージ」管理 ✔ 各プロダクトが独⽴した「単品」管理 ✔ 統合基盤による「パッケージ」管理 ✔ 連携は「都度つなぎこみ」でコスト⼤ ✔ 機能は「標準装備」で切り替え⾃在 ✔ 顧客体験:バラバラのID‧管理画⾯ ✔ 顧客体験:単⼀のID‧統合ダッシュボード Copyright © DIP Corporation, All rights reserved.

Slide 5

Slide 5 text

バイトルiOSアプリ でのリアーキテクト の主な取り組み Ø SwiftUIへのリプレイス Ø デザインシステムの導⼊ Ø Liquid Glass対応 Ø Kotlin Multiplatformの採⽤ Ø Swift6対応 Ø SwiftPMを利⽤したモジュール管理

Slide 6

Slide 6 text

Kotlin Multiplatform (KMP) について ● Kotlinで実装したコードを、iOSとAndroidで共有できる仕組み ● ロジックの⼀部からUIまで、共有範囲を選択できる ● iOS向けには、Kotlin/NativeによりKotlinコードをFrameworkと してビルドします ● このときKotlinの型はObjective-C互換のAPIとして公開されます ※Objective-Cを介さずSwift向けに直接公開するSwift exportもAlpha版 として提供されていますが、今回は従来のObjective-C interopを前提 としています。

Slide 7

Slide 7 text

今⽇話すこと ● 現⾏プロダクトで抱えていた課題 ● KMPで共有する範囲の設計 ● KMP採⽤から1年 ⾒えた成果と課題

Slide 8

Slide 8 text

現⾏プロダクトで抱えていた課題

Slide 9

Slide 9 text

バイトルの応募体験 ユーザーにとっては数タップのシンプルな体験 求⼈表⽰ 応募

Slide 10

Slide 10 text

求⼈サービスを⽀える業務ルール 応募できる条件 会員状態 年齢‧職業の制限 WEB∕TEL応募の可否 外部サイトへの応募 応募に必要なプロフィール項⽬ … 会員/ゲストによる応募処理 認証エラー時の振る舞い 退会時のデータ処理 ログイン後のユーザー情報同期 … 求⼈検索 履歴‧お気に⼊り 職種階層の整理 駅‧沿線の選択条件 エリアの選択条件 フリーワード検索条件 検索条件の保存 … 履歴‧お気に⼊りの保存件数‧保持期限 掲載終了求⼈の削除 同じ履歴‧お気に⼊りの重複防⽌ 保存済みの検索条件の求⼈件数の更新 …

Slide 11

Slide 11 text

ユーザー⾏動のトラッキング要件 計測要件 計測⽤パラメータの構築 ‧画⾯‧OS固有の情報 ‧会員プロフィール情報 ‧求⼈‧職種情報 ‧検索条件 … ⽬的 サービス利⽤状況の分析 広告効果の計測 計測条件‧タイミングの制御 ‧画⾯の表⽰ ‧応募等のコンバージョンにつなが るアクション ‧アプリのライフサイクル … 求⼈のレコメンド Push通知の配信

Slide 12

Slide 12 text

業務ルール・トラッキング要件 仕様の理解 仕様の理解 iOS開発チーム 👥👥👥 Android開発チーム 👥👥👥 実装‧テスト 実装‧テスト iOSアプリ Androidアプリ 差異が生じることも

Slide 13

Slide 13 text

プロダクトの成⻑🚀とともに、 同じ仕様を各OSごとに実装‧検証する メンテナンス負担💰が増⼤

Slide 14

Slide 14 text

KMPの選定理由 ● 共有する領域を柔軟に選択できる ● UIは各OSのネイティブ実装を維持できる ● 社内のKotlin経験者の知⾒も活かせる

Slide 15

Slide 15 text

KMPで共有する範囲の設計

Slide 16

Slide 16 text

KMPで共有するレイヤー

Slide 17

Slide 17 text

ロジックの共有例:トラッキング要件

Slide 18

Slide 18 text

KMP採⽤から1年 ⾒えた成果と課題

Slide 19

Slide 19 text

KMPで共有する領域の維持と拡張 ✔ 業務ルールとトラッキング要件を単⼀実装で維持 ✔ 共通の単体テストで、両OSへ提供する振る舞いを検証 開発を通じてKMPで共有する範囲を拡張 例: l 会員認証フロー l ディープリンク(短縮URL)の解析と遷移先判定

Slide 20

Slide 20 text

iOS‧Android開発者の双⽅がKMPの変更に参加 KMP変更Pull Requestの約4割をiOS開発者が起票。 iOS開発者 Android開発者

Slide 21

Slide 21 text

iOSネイティブ実装上の課題と対応 ⼀⽅、iOSへKMPを組み込む中で、いくつかの課題がありました。 ■ Kotlin/Swift間の型制約 ■ KMPオブジェクトとSwift Concurrencyの境界 ■ SwiftUI Previewがタイムアウトしてしまう ■ 複数KMPモジュールを別々のXCFrameworkとして exportしたときに起きる問題

Slide 22

Slide 22 text

KMPオブジェクトとSwift Concurrencyの境界 KMPからexportされた型‧オブジェクトは、 SwiftコンパイラがSendableとして検証できない。 参考: JetBrains YouTrack「KT-74440」

Slide 23

Slide 23 text

Sendable制約への現状の対応 [対応] l KMP型を保持するプロパティに限定してチェックを抑制 l 並⾏呼び出しの安全性はKotlin実装側で担保する l KMP型はそのまま公開せず、SwiftのSendableな型へ変換

Slide 24

Slide 24 text

iOSモジュール構成の⾒直し KMP固有の考慮事項を、KMP接続モジュールに集約

Slide 25

Slide 25 text

本プロダクトにおけるKMP継続判断 判断軸 OS別に実装 KMPで共有 主要な業務ロジックを単⼀実装で維持できる ✕ ◯ 各OSに適したUI/UXを維持できる ◯ ◯ 各OSの開発⾔語‧開発環境で扱いやすい ◯ △ iOS‧Androidの開発者が共同で保守できる △ ◯ ○:満たせている ⻘字:優先する価値 △:⼀部課題あり ×:満たせていない

Slide 26

Slide 26 text

KMP採⽤‧継続の判断軸 1. 両OSで実装を共有することの価値 OS間の差異管理を、実装とテストの共有によって継続的に減らせる領域があるか? 2. iOSにKMPを導⼊するコスト iOSへKMPを⼊れること⽣じる技術的な負担を受け⼊れられるか? 3. KMP共有コードの保守体制 iOS‧Android双⽅が「⾃分たちのコード」として保守できるか?

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

ご清聴ありがとうございました!!

Slide 29

Slide 29 text

Appendix

Slide 30

Slide 30 text

Kotlin APIをSwiftへ公開する際の型制約 Kotlin APIをSwiftで公開する際に、型表現に制約がありました。 対策としては以下の対応を⾏いました。 ■ コーディング規約の整備 ■ Lintでのルール整備 ktlint (KotlinのLintツール)で 違反コードを検出 ※コーディング規約の例

Slide 31

Slide 31 text

補⾜: KMPをフレームワークとしてexportする際の制約 複数KMPモジュールを別々のXCFrameworkとしてexportし た場合、共通する依存が複製されてしまう‧‧ 公式ドキュメント(※)で推奨されているumbrella module // ios-export/build.gradle.kts kotlin { val xcframework = XCFramework("Shared") の⽅式に従い、1つのフレームワークに集約 } listOf(iosArm64(), iosSimulatorArm64()).forEach { it.binaries.framework { baseName = "Shared" isStatic = true xcframework.add(this) // shared + tracking を1フレームワークに集約 export(projects.shared) export(projects.tracking) } } sourceSets { commonMain.dependencies { api(projects.shared) api(projects.tracking) } } ※「Swift package export setup — Exporting multiple modules as an XCFramework」: https://kotlinlang.org/docs/multiplatform/multiplatform-spm-export.html#exporting-multiple-modules-as-an-xcframework 「Choosing a configuration for your Kotlin Multiplatform project」: https://kotlinlang.org/docs/multiplatform/multiplatform-project-configuration.html

Slide 32

Slide 32 text

開発体験の課題 - ビルド時間とSwiftUI Preview KMP(XCFramework)はビルドが重く、開 発イテレーションに影響がでてしまう。 KMPを含む重い依存のリンクで、SwiftUI Preview がタイムアウトして動かない。 公式Issueでも同様の問題があり、回避策「dynamic framework化」は、起動 時間とアプリサイズの肥⼤化につながる懸念があり選択しなかった。 (KT-70158 KMP: SwiftUI Preview not working with Xcode 16 beta)

Slide 33

Slide 33 text

KMPで実装‧テストを共有した結果 求⼈に関わる業務ルールとトラッキング要件の単⼀実装を実現 できました 共通の単体テストで、両OSに提供する業務ロジックの振る舞い を検証できるようになりました iOSアプリの実コード 両OSで共有する Kotlinコード iOS固有実装

Slide 34

Slide 34 text

対策: KMPの疎結合化 Kotlin Multiplatformに依存するモジュールを限定的に し、SwiftUIのViewを持つモジュールからKMPには直接的 に依存させない。 Preview‧テスト時はmockを注⼊し、KMPのビル ド‧リンクが⾛らないように対処。 →Previewが安定して動作するように!

Slide 35

Slide 35 text

補⾜: 依存解決 依存解決にはpointfree製の swift-dependencies を利⽤しています。 TestDependencyKeyでPreview/テスト⽤の軽量な値を、DependencyKeyで本番⽤の実装を定義します。 これにより、Preview/テスト時は重い依存(KMP他)のビルド‧リンクをスキップできます。 インターフェースモジュール 実装モジュール ※ 公式ドキュメントの「Separatinginterface-and-implementation」の章を 参考にしています