2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
LINE Androidアプリの基盤を支えるOSSライブラリLich (全1記事)
提供:LINE株式会社
リンクをコピー
記事をブックマーク
大石将邦氏(以下、大石):「LINE Androidアプリの基盤を支えるOSSライブラリLich」というタイトルで発表いたします。LINE株式会社の大石と申します。よろしくお願いします。
最初に自己紹介させてください。私はLINEコミュニケーションプラットフォーム開発室に所属しています。最近組織変更があり名前が変わりましたが、基本的にいわゆるみなさんが使われているメッセンジャーアプリのLINEの基盤部分の開発を担当しています。
以前はLINE DEV DAYの2019とか2020にも登壇しました。2019のときは仕様とかおもしろい話をしたと思うので、ぜひ見てもらえればいいと思います。あとは、KotlinもAndroidもぜんぜん関係ない個人的なブログでちょろっと話をしています。
さっそく今回の話ですが、まずそもそもみなさん利用されていると思いますLINEアプリ。これはもう10年近い歴史があり、技術的負債が非常に大きい話になります。さすがに10年も経って、それもAndroidというプラットフォーム自体が大きく変化する上で作られているコードなので、コードの複雑度が非常に高くて、修正や新規機能の追加のコストが高いという問題があります。
単純にわかりやすい支障だと、とにかくビルドに時間がかかる。差分ビルドでだいぶ速くはなってきていますが、単純なフルビルドをやっちゃうと20分ぐらいかかることもあります。
本当に初期の頃はAndroid OSのいろいろな制限を回避するためとかいろいろ問題があって、SPDYプロトコルを採用したりとか独自の作り込みのコードがあったりしました。今となっては、SPDYはHTTP/2に置き換わっていますし、OSそのもののフレームワークも大きく変わっています。特にJavaからKotlinへの変化というのは大きいですね。
そういう変化に対応できていないところがもともとあり、これはいかんということで、とにかく将来に向けていわゆる技術的負債を解消していこうというプロジェクトがチームとして立ち上がりました。それがLINT、LINE Improvement for Next Ten yearsと、将来10年を見据えて基盤を改善していこうと。
それもクライアント、サーバーの両方が協力してやっていくプロジェクトが立ち上がりました。特にクライアントとサーバーが協力しないとなかなかできなかったネットワークプロトコルとか同期まわりの改善を集中してやっています。これは私ではありませんが、別の者が2019年のLINE DEV DAYのセッションで話していますので、興味がある方はぜひ見てもらえればと思います。
このLINTプロジェクトについてはもう2年以上、もうすぐ3年まではいっていないのかな。だけど2年半ぐらいはやっていますね。その間、いろいろな制度ができておりまして、これはLINEアプリにもちろん適用すればいいんですが、LINE株式会社自体としては別に他にもいろいろアプリを作っていますので、LINE以外のアプリにも展開していきたいと。
なので、必要なものであればライブラリモジュールとして他のアプリやSDKからも利用しやすくするということを考えています。さらにもっと汎用的な機能についてはオープンソースライブラリとして社外に公開していくことも考えていますというモチベーションもあります。これは外部からのフィードバックを受けられれば質をより高められるだろうということです。
実際にもうすでに1年以上前にLichというライブラリを公開しています。今でも継続的に開発、メンテナンスがされています。このLichについては、Androidアプリ向けのライブラリコレクションになります。今回のお話はご存知の通りPure Kotlinですね。Kotlinに基本的に書かれているライブラリです。
現在では5つのライブラリがあります。ネットワークまわりのOkHttpとかThriftとかの他にAndroidで一般的に使われる機能をライブラリ化したものがいくつかあります。それぞれのライブラリは独立していますので、必要なものだけ利用可能になっています。今回は特徴的なものとしてLich Componentというライブラリについて話していきたいなと思います。
さてこのLich Componentなのですが、簡単に言うとSingletonオブジェクトの管理をするためのライブラリです。Androidでそういうものと言うとDaggerとかKoinとかそういうDIツールをご存知の人も多いと思いますけれども、それの代わりとなるものとして作りました。もちろん既存のDIを置き換えずに補完的に利用することもできるように考えて作られています。
すでにDaggerとかKoinとかがあるのに、なぜ新たに作るのか。オレオレDIツールなんて実際作ってみたという人は多いかもしれませんが、なぜわざわざ作ってそれを公開しているのかというところなんですが、モチベーションがいろいろあります。とにかく今までのDaggerやKoinとかそういう既存のライブラリには欠点というか気になる点があったので、それに対して改善したいところはいろいろあったわけですね。
最初に、利用するために覚えることをできるだけ少なくしたい。Daggerとか特に覚えることが多かったりしますよね。他にも、グローバルな状態を持ちたくない。アプリケーションの初期化の.onCreate()、アプリケーションの初期化のときに何か初期化の処理を行う必要があってほしくないと。起動を速くしたいということですね。
同じ意味で、lazyな取得というのがKotlinのdelegated propertyと。これはKotlinの機能なのですが、Kotlinのコードとして自然にlazyなオブジェクトの取得も簡単に書けます。これも起動の速度を早くするという点では非常に有効ですね。次にインターフェイスと実装を簡単に分離する。これはDIツールとしては非常に当たり前の機能で、ライブラリモジュールとかDynamic feature moduleです。
Dynamic feature moduleはAndroidの開発をしていないとちょっとわかりづらいかもしれませんけれども、一部の機能を最初のインストール時以外のタイミングでコードをダウンロードするみたいな感じですね。一部のコードをダイナミックに切り替えるみたいな感じのかたちでモジュール化する機能を動かそうと思ったときに、既存のDIツールではこれでなかなかうまく動かないので、どうにかしたかったというところがあります。
あとは細かいところですが、ビルドタイプやフレーバーによる実装の切り替えが簡単にできるとか。もちろん、ユニットテストでモックオブジェクトを差し替えるのが簡単だったとかそういうのもありましたが、できるだけコード生成したりしてビルド時間が延びるのは嫌だと。ここら辺のことをやりたいというのを目的として作りました。
具体的にどうなの? というところで、実際に基本的なコードなんですが、Lich ComponentにおいてコンポーネントのそのSingletonの定義というのはKotlinのcompanionオブジェクトという機能を使ってやります。このcompanionオブジェクトにComponentFactoryという機能を実装してもらって、ここでコンポーネントの初期化のコードを書いてもらう。コンポーネントの定義の方法はこれで終わり。
このcompanionオブジェクトを使うというのはちょっと肝で、実際に取得する方法なんですが、KotlinのExtension functions のcontext.getComponent、この後ろにさっき定義したFooComponentをポンと置くと、Singletonのインスタンスがバッと作られます。
同じようにlazyに書きたい場合は = をbyに変えて.getComponentを単純な.componentに変えれば終わりです。例えばこのFragmentに書きたい場合は、その中にExtension functionsを定義してあるので、FooComponentをこんな感じで簡単に書けます。
基本的な感じはこれで終わりなんですね。ただこれだけだと何がうれしいの? というところがあるんですが、こういうDIツールを使うときに一番うれしいのは、インターフェイスと実装が分離できることです。Lich Componentについてはインターフェイスと実装の分離が非常に簡単にできます。
まずインターフェイスのほうに先ほどのようにcompanionオブジェクトでその実装の定義を書きます。このときこのdelegateToServiceLoaderというファンクションを単純に呼ぶだけにしておきます。一方で実装クラス、ここでいうとFooComponentImplですが、これをに@AutoServiceというアノテーションを付けておきます。これで終わりです。
こうするとこのServiceLoaderという仕組みがこのFooComponentImplのインスタンスを自動的に探し出して、newしてセットしてくれます。ここですごくうれしいのは、FooComponentを使うときにはFooComponentのインターフェイスだけ見えていればいい。このインプリメンテーションのほうは、任意のモジュールに置くことができます。
この仕組みは既存のDIツールで、特にいわゆるDIツールのときの初期化のタイミングで、初期化のクラスが実装クラスが見えていないといけないとか、そういう問題がよくあります。Lich Componentにおいては、特にメインのアプリケーションからすべての実装クラスが見えなきゃいけないとか、そういう制限は一切ありません。最終的なバイナリに含まれていればOKですね。ここが非常にうれしいところです。
さらにもっと便利な機能として、複数の実装クラスを切り替えるということが簡単にできます。先ほどのFooComponentImplですが、プロジェクトのメインはプライオリティが一番低いプライオリティ0というかたちで作っておいて、例えばデバッグビルド用にDebugFooComponentImplというクラスを作って、それはより高いプライオリティでデバッグ用のソースコードをソースツリーの中に入れておく。
そういうことにしておくと、デバッグビルドの場合だけ、このDebugFooComponentImplが使われる。リリースビルドやそれ以外のビルドでは、このプライオリティの低かったFooComponentImplが使われる、ということができます。
これは実際にデバッグビルドでデバッグメニューを出すコードとそうじゃないコードを単純に切り替えることが、特に設定ファイルをいじったりせずに、クラスパスを切り替えるだけでできるという便利な機能として、実際に使われています。
ちょっとここまで話して、少し気になっている人がいると思います。このインターフェイスと実装を分離する機能というのは、もともとはJavaにあるServiceLoaderという機能を使っています。このServiceLoaderは内部でリフレクションを使っています。リフレクションということで非常に、特にAndroidでは遅いんじゃないかと気にする方がいると思います。
しかしこれは、Androidのツールチェーンが非常にうまくできているところです。リリースビルドでは、R8というAndroidのビルドツールの一部なのですが、これが最適化を行ってくれまして、ServiceLoaderの呼び出しをインライン化してくれます。リフレクションのコードを完全にアプリケーションのリンクのタイミングでインライン化してくれるので、リフレクションコードが消えてしまうんですね。
なのでオーバーヘッドがありません。ということで、実際のリリース版のビルドの中ではリフレクションコストなしに、先ほどのような便利な実装とインターフェイスの分離を簡単にできるます。かつしかもこの仕組みは、そのDynamic feature moduleみたいなものでもちゃんと問題なく動作しています。このあたりが特にLich Componentの非常に強い、得意としていることになります。
あとはおまけですが、ユニットテストなんていうのもLich Componentだと簡単に書けます。例えばこのFooRepositoryみたいなクラスを作りました。これを使うFooUseCaseというのがいたとします。FooRepositoryの定義は上のほうにありまして、それを使うFooUseCaseは下のほうですね。
FooUseCaseはそのさっき言ったlazyな取得方法を使って、FooRepositoryのインスタンスを取っています。このFooRepositoryのインスタンスをモックして、FooUseCaseのテストクラスを書きたいと思った場合はどうするかといいますと、画面の中央にあるとおり、mockComponent(FooRepository)と、こう書けば終わりです。
この中にさっき言ったfindFooなどをモックするときは、こう書きます。ここに書いてあるやつは内部的には先ほどちょっと話にありました、mockito-kotlinを使っています。だからmockito-kotlinの文法ですね。MockKに対応したバージョンも実はあります。とにかくこのようにmockComponentをポンと書くだけで、先ほどのFooRepositoryをモックしてテストが簡単に書けます。
このLich Componentを導入した効果なんですが、先ほど言いましたように、lazyが簡単に書けます。これはやっぱり特に、いろいろな初期化を行っているコードがあったときに、それを起動時に初期化のコードをできるだけ減らせるので、だいぶ削減しやすくなった。まだまだリファクタリングの途中なので、LINEアプリそのものは起動時にちょっと時間がかかっていますけれども、それでもだいぶ少しずつ改善はできているという感じですね。
もう1つ、マルチモジュール間の依存関係がとても簡単に切れるようになった。これは先ほど言いましたように、Lich Componentの最大の得意とするところです。もともとその巨大なモノリシックなLINEアプリが機能ごとに分割されて開発効率は良くなってきていますし、分割することによってビルドを変更したときに再コンパイルになるコードが減るので、そのインクリメンタルなビルドが速くなったりとか、いろいろメリットがあります。
さらにまだわずかですが、Dynamic feature moduleによって、アプリの容量を少しずつ削減していく方向ができています。まだまだ基盤であって、まだ道半ばというところなんですが、その足場ができたというのがLich Componentの効果ですね。
これまではLich Componentについて紹介しましたが、基本的にはLich ComponentはSingletonのオブジェクトを扱うものなんですね。当然の話としてSingleton以外のオブジェクトはどうやって扱うんだという話になりますが、ViewModelについては、AndroidのViewModelクラスにでLich ViewModelという、同じくLichの一部のライブラリとして作っています。
これはLich Componentも同じような設計方針で作っていますし、Androidでちょっと扱いづらいとされているSavedStateについても、Kotlinのdelegated propertiesで簡単にアクセスするような機能とかもあります。それ以外のスコープみたいなものが他のライブラリではよくあるんですが、あえてLichでは作っていません。
特にActivity-scopedやFragment-scopedみたいなオブジェクトは、モダンなAndroidアプリでは不要というか、機能を減らして作りたいという、特にメイン開発する私の思いが込められているという感じですね。基本的にはモダンなAndroidアプリでは、そのビジネスロジックはViewModel層以下に作るべきであって、そのActivityやFragmentは基本的にはViewModelとViewのbindingに集中してほしい。
であれば、Activity-scopedとかFragment-scopedみたいなものは、基本的には使う必要がないだろうという設計思想です。実際には他のDIツールに慣れた人というのは、もしくはもうすでに既存のアプリに対しては、DIツールは別にDaggerをすでに使っています、Koinを使っていますみたいな人は当然いると思います。
実際にそういうところでも、Lich Componentのマルチモジュール対応の機能は非常に強力なので、これだけは使いたいという話が時々あります。そのために、Lich Componentというのはできるだけコンパクトに作られているので、他のDIツールと組み合わせて使うのも簡単にできます。
具体的にDaggerと組み合わせた例と言いますと、このようにprovideFooComponentみたいなDaggerのモジュールとして書くところに、Lich Componentの.getComponentという記述を書いてやると。これだけでLich ComponentをDaggerから使うことができることになります。
Lich Componentについて紹介しました。最後にまとめです。LichはLINEアプリの改善プロジェクトの中で生まれたOSSライブラリです。こちらのURLからアクセスできます。その中でもLich Componentはアプリのマルチモジュール化を特に支援することを大きな目的として作られたDIツールです。
軽量かつ高速である点が使いやすい、シンプルであるというところがアピールポイントになります。もうすでにDaggerやKoinといった他のDIツールを使っている方でも、そのマルチモジュールの依存関係を解決するところについて。ピンポイントで使うのもいいのかなと思っています。
それ以外にも、Lichについては、ViewモデルやOkHttpのExtension functionsみたいなものもあります。興味があったらこれを見てもらえればなと思います。以上になります。本日はありがとうございました。
LINE株式会社
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05