自己紹介

川田裕貴氏:今日は、LINE Messaging APIの中のServer Side Kotlinについてお話をしようと思います。まず、自己紹介をします。私は川田裕貴と言います。LINEに初めて関わったのは2014年の開発インターンシップからなのでけっこう長いのですが、新卒入社で入社しました。

Kotlinに関わり始めたのは2018年くらいからです。「LINE Things」というプロジェクトに参加した時にKotlinを初めて書いて、その年にオランダで行われた「KotlinConf」に参加しました。2019年からは、Messaging APIのサーバーサイド開発をしています。

LINE公式アカウントのBotを作るためのAPI・プラットフォーム「LINE Messaging API」

1日20億以上のメッセージを送信するシステムですが、どんなふうにMessaging APIが動いていて、どんなふうにKotlinが使われているのか、中身の紹介をしたいと思います。

LINE Messaging APIを使ったことがある方も多いと思いますが、今日は知らない人もいると思うので一応紹介すると、LINEのBotを作るAPIやプラットフォームのことです。

LINEアプリは、ユーザー同士でコミュニケーションするという機能もありますが、LINE公式アカウントと言われているアカウントがたくさんあって、LINE NEWS、LINEの通訳Bot、お天気のBotなど、いろいろなものが提供されています。これらのLINE公式アカウントを作るためのAPIが、LINE Messaging APIです。

LINE Messaging APIには、メッセージをブロードキャストしたり、プッシュやリプライと言われている個別にメッセージを配信するAPIだったり、ユーザーからメッセージを受け取るためにBotに対してWebhookを送信していたり、さまざまな機能があります。

また、LINE公式アカウントの画面を開くと出てくる「リッチメニュー」や、どういうユーザーがこのメッセージを受け取ってアクションしたのか判別する機能や、ユーザーのメッセージ送信数を集計する機能など、すべてをまとめてMessaging APIとして提供しています。

Messaging API のバックエンド

どういうことをやっているのかを、簡単な図で説明したいと思います。LINEユーザーは、直接Messaging APIを使っていませんし、Botと会話しているわけでもありません。LINEユーザーは、talk-serverと言われる大きなLINEを司るコンポーネントに対して通信を行なっています。

ただしMessaging APIは、talk-serverのプロトコルとはまったく異なるAPIで通信しています。そこの橋渡し役が私たちのチームで開発しているMessaging APIのバックエンドサーバーです。

Messaging APIはAPIを直接使っているChatbotだけが利用しているわけではありません。LINE公式アカウントのWebから操作できる管理画面や、外部の会社で提供している「LINE WORKS」というアカウントもLINEの中で1つのBotとして動いています。

このような機能も、すべて私たちのMessaging APIのバックエンドを使っていて、実際のLINEユーザーはtalk-serverを経由してコミュニケーションしています。中身はたくさんのキューがあって、たくさんのマイクロサービスコンポーネントが集まっているシステムです。

ユーザーからBot側へのメッセージ送信でKotlinを活用

実際にどういうところでKotlinが使われているのか。LINE Messaging APIには、ユーザーからBot側と、Botからユーザー側と、大きく2つの経路があります。まずはユーザーからBotへメッセージを送信した場合のフローを見ていこうと思います。

Webhook送信側のサーバー構成は、2020年から2021年に大幅に構成を刷新しています。刷新をする際にKotlinを積極的に採用したので、多くのコンポーネントがKotlinで動いています。

昔はtalk-serverの中で、実際にBotを1つのユーザーとして一般のLINEユーザーがAPIを呼んで、メッセージをフェッチしてWebhookを送信していたのですが、今はtalk-serverからLINEが処理するイベントをすべて受け取って、その中からBotに関するイベントを抽出して、そのイベントを処理しながらWebhookとして送信しています。

イベントは本当に多く、秒間で100万以上あります。LINEで取り扱うすべてのイベントを受け取り、大量のイベントの中からBotに関するイベントだけをフィルターするコンポーネントが最初にあります。

(スライドを示して)ここはJavaで、その先はすべてKafkaのキューを挟んでKotlinのシステムになっています。なのでみなさんがMessaging APIのWebhookを受け取る時に送出しているサーバーはすべてKotlinで書かれています。

中身はReactorやCoroutinesを使って非同期化もされていて、REST APIやApache Thriftなど、いろいろなAPIを使ってマイクロサービス化されて動いています。こちら側はかなりKotlinが使われています。

Botからユーザー側へのメッセージ配信はJavaやScalaで書かれている

逆にメッセージ配信側はKotlinが使われているところが少なく、JavaやScalaで書かれています。これには歴史的な事情もあります。LINE公式アカウントは10年近く動いているサービスなので、かなり初期からあるコンポーネントも多く、JavaやScalaで書かれています。

api.line.meというドメインを叩くとつながるAPI Gatewayは、すべてJavaで書かれていますし、その先のメッセージ送信サーバーには、コンポーネントがさまざまあって、ブロードキャスト用のサーバーやナローキャスト用のサーバーなどありますが、JavaやScalaで書かれています。その先にRabbitMQがあって、配信処理サーバーはほぼScalaで書かれていて、talk-serverへ送信しています。

ただ、それらがそれぞれ使うマイクロサービスコンポーネントは、一部Kotlinで書かれていたりします。あとで説明しますが、これらのサーバーの中で使っているコモンモジュールみたいなものは、最近Kotlinで書かれるようになってきました。

今まさにこのメッセージ配信側の、Kotlinへの移行を計画中です。最近、LINEのコンポーネントはKafkaへどんどん移行しているので、JavaやScalaで書かれたコンポーネント、RabbitMQを使っている部分はKafkaや、Reactorや、Kotlinを使っていこうと計画をしています。

Kotlinなしでは生きていけないシステムになっている

古いコンポーネントもJavaで書かれていますが、私たちはMonorepoという方式を採用しています。マイクロサービスはいっぱいありますが、実際のリポジトリは1つに統合されています。

サーバー、サービスのコードがたくさんありますが、それぞれの中で共通の部分が出てきます。私たちはGradleでリポジトリを全部管理していて、Gradleの中のモジュールがそれぞれマルチモジュールになっていて、:commonsというモジュールの下にKafka、Redis、Cacheなどのモジュールが合わさっています。

Webhook側の刷新の時に、共通モジュールを書き直したり追加したりしたので、この部分に関してはかなりの割合でKotlinが使われています。

Javaで書かれているコンポーネントからも新しく書いた:commonsモジュールが使いやすく、なにかの理由でちょっと書き換えなきゃいけない時にこちらのモジュールで書き換えているので、表面上はJavaで動いていても中身のモジュールはKotlinで書かれているというものもかなり多いです。なのでもうKotlinなしでは生きていけないシステムになっています(笑)。

Javaとの親和性・言語の機能・学習コストにおいてKotlinはちょうどいい言語

私たちのチームの特徴として、Java、Kotlin、Scala、それぞれ使っているのですが、私はKotlinがちょうどいいと思っています。JavaはJava 9以降進化していていろいろ増えてきてはいますが、まだ言語の機能が追いついていません。

Scalaは、やはり機能が強力でFunctional Programingをやりたい方はとても使い心地がいいと思いますが、若干読みづらく、例えば先ほどのKotlinのコモンモジュールみたいなものをScalaで書いてしまった場合、Javaから呼ぶとどうしてもScala特有の型やオプションが特殊な扱いになってしまいます。

このあたりのやりとりが難しく、その点Kotlinだとほとんど気にならずに使えるので、Javaから置き換えていくにもちょうどいいです。学習コストという面でも、Scalaほどパラダイムが変わらないのかなという点で、ちょうどいい言語だと私たちは思っています。

Coroutinesを使わずにあえて非同期化しているところもある

Kotlinというと、みなさんCoroutinesを使うと思いますが、私たちのチームではあえてCoroutinesを使わずに、非同期化しているところもあります。主にReactorを使ってReactive Streamsで書いています。

Coroutinesはいいのですが、私たちのシステムはキューを使ってたくさんのオペレーションをフィルターしたりflatMapしたり、さらにもっと大きな非同期オペレーションを高速で呼んだりというのを繰り返すので、どうしてもCoroutinesのAsynchronous Flowみたいな機能が必要になってきます。

ReactorもRxJavaも使ったことがありますが、それに比べるとCoroutinesのFlowのOperatorが貧弱かなと思っています。そういう処理をするとどうしても冗長になってしまったり、きれいに書けないことがあったりして、やはりこういう処理にはReactorのほうが向いているのかなと思っています。

また、ReactorはSpringのサポートも手厚いので、Reactive Streamsの学習コストさえ乗り越えられるのであれば、Coroutinesを無理やり使わなくてもいいのかなと思うところがあり、Reactorを採用しています。

Kotlinを使ってみて感じる、いいところと気になるところ

Messaging APIの中でKotlinを使ってみての書き心地ですが、Javaを使っているとほぼ困るところがないのがいいところかなと思っています。Nullableやsmart castなど便利な機能が取り入れられているので、かゆいところに手が届きます。

また、Scalaよりもはるかに良いJavaとの親和性があります。拡張関数など、今まではきれいに書けなかったコードもきれいに書けるようになるのがKotlinのいいところです。

ただ、チームの中にはScala派閥の方もいます(笑)。そういう方からすると、やはりKotlinでも機能が足りず、パターンマッチングが弱いだとか、Scalaのforみたいなフラットマップが言語としてサポートしてほしいだとか、そういう意見がよく出てきます。

また、最近KotlinのExperimentalな機能はけっこう変わっていて、実験的に全部書き直さなければいけません。特にExperimental timeあたりのメソッドがdeprecatedになっていたりしてすごく気になります。

Javaから移行するとどうしてもコンパイルに時間がかかったり、IDEもちょっと重いのが気になる点かなと思いますが、これ以上にKotlinのメリットは多いと今のところ私は思っています。

(次回へつづく)