December 20, 2018

Kotlin Multiplatform構想 ~今やる理由編~

この記事は2018年Kotlin Advent Calendarの11日目の記事です。
(本来は22日目でしたが、今年は残念ながら空きが出来ているので先に乗っ取りました)

Kotlin Advent Calendarは気付いてみれば4年連続4回目の出場になりました。
僕とKotlinの出会いは現在の会社に入社する2015年の4月頃でした。 Android開発ではJava全盛期の時代に、当時まだM13(milestone)だったKotlinのみを使って開発しようというプロジェクトで、 紆余曲折を経て2016年の1月にリリースされたのですが、国内のそれなりに大規模なサービスでフルKotlinでリリースされた Androidアプリでは一番早かったのではないかと思います。ちなみにアプリのリリース時点ではKotlinはまだβバージョンでした。
公式以外の情報もまだまだ少なくチームメンバーと試行錯誤しながら開発を続けていたのですが、 そのような状況でも問題なくリリース出来たのはJetBrainsの手厚いコミュニティサポートのおかげかなと思います。 丁寧な公式ドキュメント、Slackでの質疑応答やYouTrackでのissue管理、Kotlin Blogの存在もとても大きかったです。

2017年5月のAndroid公式サポートで転機を向かえたKotlinですが、2019年のKotlinにとってさらなる進化の火付け役となるであろうプロジェクトがあります。 それがKotlin Multiplatform です。
Kotlin Nativeを始めとするKotlin Multiplatformの構想自体はかなり初期の段階で練られてはいましたが、 正直つい最近までは、まだプロダクト環境で利用出来る状態ではなく、本当に出来るのかな?という状況でした。
私個人の感想としてはJetBrainsはKotlin Conf 2017のKeynoteでも力を入れていくと大々的に宣伝しているので、 「いつかはそれなりに動くのだろう」という程度の淡い期待でした。
しかし、今年のKotlin Conf 2018に参加してみて一気に現実味を増していたことに気が付いたのです。

見たのはこのセッション

運が良い事に、このタイミングで新規サービスの立ち上げに参加させて頂けることになり、 ここ1ヶ月程はKotlin Multiplatformについて調査をしていて、実現可能性が見えてきたので、なぜこのタイミングでKotlin Multiplatformを採用しようと思ったのか、 現時点で考えているKotlin Multiplatformの設計について話したいと思います。(まだプロダクト環境にはリリースされていない点はご注意下さい)

Kotlin Multiplatformとは

Kotlin Multiplatformとは、 Kotlinで書いたコードを、各プラットフォームに適した形でコンパイルしてくれるクロスプラットフォーム開発ライブラリの1つです。
2018年現在では、以下の形式にコンパイルする事が出来ます。

  • iOS (arm32, arm64, emulator x86_64)
  • MacOS (x86_64)
  • Android (arm32, arm64)
  • Windows (mingw x86_64)
  • Linux (x86_64, arm32, MIPS, MIPS little endian)
  • WebAssembly (wasm32)

そのうちKotlin Nativeというワードは、日本でも一部の間ではバズワード的に扱われており、ご存知の方も多いかもしれません。 現段階で、Kotlin Multiplatformで作られたプロジェクトはそこまで多くはありませんが、KotlinConf2018のカンファレンスアプリである Kotlin Conf Appは参考になると思うので一度目を通すことをおすすめします。

Kotlin Nativeのサンプルに関しては以前日本語で記事を書いたので、そちらを見ても良いかと思います。

やりたいこと

現時点で私がKotlin Multiplatformを導入して「解決したい問題」または「要件」は以下の6つです。

  1. BFF的な立ち位置のサーバをKotlinで開発したい
    • マイクロサービス間の値をまとめて、1リクエストでクライアントに返したい
  2. Domain object(≒Model)を共通化したい
    • Android, iOS, Web, Serverも
  3. 認証系を共通化したい
    • Auth tokenの管理等
  4. ログ送信の基盤を共通化したい
    • ログの追加毎に各プラットフォームでディメンション等のすり合わせが面倒
  5. Utility系(広義)を共通化したい
    • 現状考えているのは、サービス依存の文言や時間処理のロジックを共通化したい
  6. View部分は各プラットフォームで作りたい
    • 動画サービスなのでネイティブの部分をゴリゴリ触りたい
    • 人類が本当にやりたいのはロジックの共通化であって、Viewの部分は各プラットフォームに適した形にしたい
    • FlutterやReactNativeとの差別化部分でもある
  7. クライアントとサーバの垣根を無くしたい
    • サーバ側の実装を待たずに、クライント側で必要なAPIを自分で定義出来るようにしたい

当然ですが、基本的には共通化したいという気持ちが大きいです。

1. BFF的なサーバをKotlinで開発したい

サーバ側はマイクロサービスアーキテクチャで開発されています。 前サービスを開発していて感じていたのですが、マイクロサービス毎の役割が別れていると クライアント側は何度もサーバにリクエストをしなければならず、クライアント側でDomain objectの生成をしなければなりません。
さらに、クライアント側のリソースは限られているのでなるべくリクエストを減らしたい点と、 クライアント側で必要なDomain objectを選択するロジックが複雑になりがちな点があるのであまり好ましくはありませんでした。

これを解決する手段としてBFF(Backends for Frontends)を用いて中間サーバをKotlinで作ろうと考えています。 技術的には、フレームワークとしてKtorを用いるつもりです。

2. Domain object(≒Model)を共通化したい

今までもDomain objectの共通化は人類の夢でした。
インタフェースの異なるもの同士が繋がるには共通のプロトコルが必要です。 最も多く使われているのはJSONでしょうか? 最近ではProtocol Buffer(以下protobuf)も広く使われているように感じます。 前のプロジェクトでも両方利用して適材適?で使っていました。 1で述べた通りサーバサイドのBFFはKotlinで開発を行います。 つまりKotlin MultiplatformでAndroid, iOS, Web, Serverのコードを共有することが出来ると、各プラットフォーム間で共通のDomain objectを利用することが出来ます。 今のところ通信部分にはJSONを用いる予定なのですが、protobufで実現したかったプラットフォーム間でのDomain objectの差異を吸収することが可能です。

3. 認証系を共通化したい

Access tokenの管理を各プラットフォームでやるのは不毛ですね。
KotlinNativeではiOS側ではUserDefaults, Android側ではPreferencesに対応しています。 本当はiOS側ではKeyChainを使いたいのですが現状では対応されていません。
もちろんtokenは暗号化してexpiredを付けるつもりです。

4. ログ送信の基盤を共通化したい

ログの定義を各プラットフォームでやるのは不(ry
これまでは、仕様決めの後にデザインが決まってから各クライアントの実装者がそれぞれ集まって、ログのディメンション等の仕様を決めていました。 完全に無駄ですね。この処理をKotlin Multiplatformで共通化したいと思っています。

5. Utility系(広義)を共通化したい

以前開発していたサービスでは、各プラットフォーム毎に開発するエンジニアがいたので各々で同様の実装を行っていました。 このような体制で行っていると、稀によく QAの段階でプラットフォーム毎に似たようなissueが切られ、その都度ディレクターに確認してどちらが正しいのか確認するタスクが発生していました。
例えば、日付のカウントダウン表示があったとして
「 Androidは"残り1分です"と表示されているのに、iOSでは"残り60秒"と表示されます。」
のような一見どちらも同じ事を表しているが、仕様によってはバグ扱いになるようなものです。
このように、サービスに依存した実装を各プラットフォーム毎ではなく、1つのソースコードで管理したいと考えています。

6. View部分は各プラットフォームで作りたい

Kotlin Multiplatformを使う理由の第一位がこれです。 人類はこれまでも、さまざまなやり方でソースコードの共通化を行ってきました。Xamarin, Flutter, ReactNative, Titanium Mobile, Adobe AIR...

最近ではFlutter, ReactNativeをよく耳にしますが、もちろん適材適所があるので、これらのフレームワークでも十分に活用出来る場面はたくさんあります。しかしながら、既存のクロスプラットフォーム開発では、iOSは問題ないがAndroidは大変という話や、View部分がAndroid, iOSどちらかに寄ってしまう等の問題がありました。
Kotlin MultiplatformのメリットはView部分は各プラットフォームで実装出来るところにあるのではないかと思います。
例えばカメラアプリの様な、ハードウェアのAPIを用いて何か処理をする場合や動画アプリの様にゴリゴリViewの操作が必要な場合に有効です。また、View部分の実装が別れているので各プラットフォームに適したUIをそれぞれで構築出来る事がメリットです。逆を言えばView部分を2重実装しなければなりませんが。

結局、大規模なクロスプラットフォーム開発で本当に行いたいことは、ロジック部分の共通化なのではないでしょうか? しかし今までのクロスプラットフォーム開発では、本当にやりたかったロジックの共通化によってUI部分まで制約を受けてしまう事が多くありました。Kotlin Multiplatformならその問題を解決することが出来ます。

7. クライアントとサーバの垣根を無くしたい

これは、ポジショントークになってしまうので大きな声では言えないのですが、 既にAndroid開発においてKotlinは切っても切れない関係でしょう。サーバサイドでも既にKotlinは実用されています。前のサービスでも利用していました。 iOSアプリ開発でも既にメジャーなSwiftはKotlinとよく似た言語と言われています。(要出典)

クライアントサイドの人がAPIが必要になったら自分たちで実装して結果を返すことが出来ます。 逆にサーバサイドの人がAPIを作ってクライアント側での取得の部分まで実装してあげれば、クライアントサイドの人はViewの実装に集中することが出来ます。 しかも、チームの誰か一人が実装すれば全クライアントでAPIの接続が完了する。なんて素晴らしい世界なのでしょう。

デメリット

ここまで、良いことばかり言ってきましたがデメリットにも触れたいと思います。 現状(2018年12月現在)で開発をしていて不便だなと思っている事リストです。

  1. Android側のデメリットは基本的に無い
  2. iOS側から呼び出すKotlin Nativeのsuspend function(Coroutine)がSingle threadでしか使えない
  3. Kotlin Nativeで .framework を1つしか読み込めない
  4. jsのライブラリサイズが大きい

1. Android側のデメリットは基本的に無い

Android側からすると基本的にデメリットはありません。 なぜならKotlin Multiplatformはgradleで管理された別モジュールとして存在しているだけだからです。 通常のライブラリと同じく依存するモジュール側に依存関係を記述するだけで問題なく動作します。
強いて言うならば、Kotlin MultiplatformのコードはKotlinのみしか使えません。
Android開発において定番で使われているOkHTTPやRetofit, RxJava等はJavaで書かれていますので使うことは出来ません。 その点には注意が必要です。

2. iOS側から呼び出すKotlin Nativeのsuspend function(Coroutine)がSingle threadでしか使えない

issueにもなっているので、今後対応されると思いますが 現状は使えません。 とはいえ、BFFサーバがいるので基本的にリクエスは1回で済むようにしたいと思っていますので、クリティカルな問題には 発展し辛いかと思うのですが、現状は使えないという事を認識しておく必要はあります。

3. Kotlin Nativeの .framework を1つしか読み込めない

Domain objectを共有すると、サーバサイドとクライアントサイドの差異が生じて開発に支障をきたす懸念があります。 protobufではタグナンバーで無理やり?同様の問題を解決していますが、この構成だとgitのhashで管理することになると思います。 (まだ試行錯誤中ですので変わる可能性あり。いい案があれば教えて下さい!)
このようにする場合、Domain objectとロジック部分のモジュールを分けたくなるのですが、現状だとiOS側でKotlin Nativeの.framework (iOSのライブラリモジュール形式) を1つのみしか読み込めないためモジュールを分ける事が出来ません。 リリースするまでの開発期間は問題ないのですが、リリースまでに解決されることを願います。 なおこちらもissueは切られているので、後に対応されるかと思います。

4. jsのライブラリサイズが大きい

Kotlinをwebで利用するには依存関係にkotlin.js が必要になります。minifyしていない状態ですが、157KBもあります。
さすがにフロント側でこれを読み込むのは現実的ではないので、サーバサイドレンダリングすることになると思うのですが、 Web開発は疎いので私からは詳しくは語れません。

kotlin js

Web側のコードも共通化したかったのですが、まだ直近では共通化するのは難しそうだなーという感想です。 Domain object, util部分だけでも共通化したいのですが...
とはいえ、今後対応も進んでくれば十分可能性はあると思います。

最大の理由

つらつらと書いてきましたが、私個人的にはAndroidエンジニアなら知らない人はいない唯一神Jake Whartonの存在が大きいです。
彼は私がKotlinでAndroid開発を始める2015年の1月頃からAndroidでKotlinを採用する理由(意訳)という記事を書いていて、 かなり早い段階からAndroidでのKotlin利用に目をつけていました。 その彼が作る多くの著名ライブラリにKotlinを取り込んだことで、世間にKotlinの存在が知られ、現在のように広がっていきました。Kotlinブームの火付け役と言っても過言ではありません。
その記事にもあるDocsを読んだときは衝撃を受けました。 このドキュメントはJake神が言うならという、私達がM13の段階でフルKotlinによるAndroid開発を決意させる決定的なドキュメントです。

そして、2018年。JakeはKotlin Multiplatformに対しても とても積極的なように見えます。 自身の作るライブラリも中途半端ではあるものの続々とMultiplatform対応を進めています。 Sdk Search, Timber, SQL Delight, etc...
Jakeは今年のKotlin Conf2018でもSQL DelightのMultiplatform対応について話しています。 また、Kotlin lang slackにもよく出現して会話をしているので見てるだけでもかなり勉強になります。
という最終的にはなんとも他人任せな理由ですが、私のような弱小エンジニアは巨人の肩の上に乗って開発をしていく事が一番効率が良いです。

まとめ

かなり長くなってしまいましたが、Kotlin Multiplatformの現状を理解して頂けたなら幸いです。
銀の弾丸の可能性を感じて頂けたでしょうか?もちろんまだまだ痒いところに手が届かない点もいくつかありますが、 今後時間をかければ十分実運用で使えるポテンシャルを持った素晴らしいプロジェクトだと思います。 以上の観点から、現段階からこのプロジェクトのコミュニティに参加していくのは個人的にとても魅力に感じました。

次回は、実際に考えているKotlin Multiplatformでの設計について解説したいと思います。

次の記事 : Kotlin Multiplatform構想 ~設計編~

© AAkira 2018