LINEヤフー Advent Calendar 2023の3日目の記事です。
ビジネスプラットフォーム開発本部の松野です。今日はLINEアプリ の Web API の SDK 開発の歴史を振り返るとともに、OpenAPI を基盤とした SDK の自動生成について解説していこうと思います。
はじめに
LINEアプリでは数々のWeb APIを開発し公開してきました。それらのAPIは、世界中の開発者たちに利用され、チャットボットの構築から顧客支援ツールまで、さまざまなサービスを生み出す原動力になりました。私たちの部門の目標は、開発者がそのAPIを最大限に活用できるようにサポートすることです。そして、それを実現す るための重要な手段がSDK(Software Development Kit)です。
私自身、2016年からLINEアプリの外部API向けのSDK開発に関与してきました。
SDKを通じて、開発者はAPIを簡単かつ効果的に利用できます。私たちは、この便利さを通じて皆さんがより創造的なサービスを提供し、充実したライフスタイルを築くお手伝いができることを願っています。
ただし、この取り組みには大きな課題が伴います。それは、APIの進化に伴いSDKが肥大化し、メンテナンスがますます困難になることです。この問題に対処するため、私たちの部門はOpenAPIを活用した自動化に取り組んでいます。
この記事では、LINEアプリのAPIをOpenAPIで定義し、それをもとにSDKを生成するプロジェクトについて詳しく説明します。初期の挑戦から現在の進捗(しんちょく)まで、プロジェクト全体の過程を紹介し、将来に向けた展望についても触れます。SDKの開発者や利用者にとって、参考になる情報や興味深い読み物となることを願っています。それでは、話を始めましょう。
LINEのSDK開発黎明期(れいめいき)
Web APIを提供する際、基本的には仕様を明確にしておけば、開発者はそれを元に実装を進められるはずです。しかし、LINEアプリのMessaging APIの場合、それだけでは不十分でした。
LINEアプリも初めはAPIの仕様だけを公開しましたが、そこでひとつ重要な認識を得ました。それは、LINEアプリのMessaging APIが他のAPIとは大きく異なる点があることです。Messaging APIではWebhookを提供し、このWebhookはユーザーのメッセージを伝達する役割を果たしており、その安全性が改ざんによって脅かされる可能性があります。そのため、LINEはAPIの呼び出しに署名(signature)を付与し、Channel Secretを使用して検証できるように対策を施していました。
しかし、APIを先行公開した結果、外部の開発者が書いてくれたブログの記事のなかで、ほとんど署名の検証が行われていないことに気づきました。これにより、署名の検証が行われていないLINE公式アカウントが増加し、事業リスクが高まる可能性に気付きました。
そのため、私たちの部門のSDK開発の主要な目標は、この問題を防ぐことでした。具体的には、SDKを使用することで、開発者がAPIを簡単に利用し、署名の検証を自動的に行える仕組みを提供しました。これにより、公式アカウントの安全性を確保し、同時に開発者の作業負担を大幅に軽減できました。
また、RubyやPerlなど、HashMapがPrimitiveな言語では、SDKがなくてもAPI呼び出しが簡単です。しかし、JavaのようにリクエストやレスポンスをBeanにマッピングする文化が主流であるプログラミング言語では、SDKの需要が高まる傾向にありました。
そのため、APIの完全公開の前に、SDKの開発対象となるプログラミング言語を選定しました。リクエストとレスポンスの型によるマッピングが便利なGolangとJavaを最初に選択しました。Perlは当時LINEアプリがプロダクションで使用していたため、選ばれました。また、Ruby、Python、PHP、NodeJSはそれらの言語が人気であり、また開発に意欲的なエンジニアがいたために選ばれました。
そして、Messaging APIの完全公開と同時に、私たちのSDKはGitHub上で公開されました。Messaging APIの全公開は、2016年のLINE Developer Dayで発表されました。そして、その基調講演を行ったのが私でした。
開発途中での課題と対策
当初、私たちの部門の目標はMessaging APIのみを対象にしたSDKを提供することでした。しかし、LINEアプリが提供する他のAPIが徐々に増加し、それらも自然とSDKに組み込まれるようになりました。他のAPIをSDKの対象に含める動きは、各SDKの利用者がPull-requestを送信してきたことから始まりました。そして、各SDKのメンテナ(メンテナンス担当者)がこれらを統合し、SDKは機能を拡張していきました。SDKの機能が増加するにつれ、問題や提案の頻度も増加し、メンテナンスの規模は膨大なものに成長しました。
このプロジェクトの開発は基本的にボランティアベースで行われており、新しい機能の追加や既存の問題の解決などの作業は容易ではありませんでした。メンテナは本業の仕事の傍らでSDKのメンテナンスを進めていたため、作業を迅速に進めることが難しかったのです。
また、メンテナが退職などでメンテナンスを続けられない状況がしばしば発生しました。しかし、私たちの部門はSDKのメンテナンスを継続し、開発者がLINEアプリのAPIを最大限に活用できるように努力し続けました。それでも、SDKのメンテナンスは容易ではない大変な作業でした。
OpenAPI 化への挑戦
OpenAPIの導入については、最初のSDKリリース時から年に一度程度話題にされていました。しかし、私たちはOpenAPIの進化を待ち続けていました。とくにMessaging APIにおいては、多くのWebhook用モデルが含まれており、それが未サポートだ とOpenAPI化する利点が限定的だと考えていました。変化が訪れたのは、OpenAPIがWebhookに対応しているという情報を見かけた時でした。これにより、OpenAPI化に本格的に取り組む決断をしました。
しかし、具体的な実装を始めると、新たな問題が発生しました。それは、私たちが使用していたOpenAPIジェネレータが、OpenAPI 3.1およびそのWebhook対応にまだ対応していなかったことでした。しかし、ダミーエンドポイントを設定し、それをクライアントとして生成することにより、目標としていた機能を達成できる可能性が開けたのです。これにより、私たちの部門のプロジェクトは次のステップに進められました。
さらに、OpenAPIのYAML定義を作成する際にはとくに注意が必要でした。API定義には内部情報が散見される可能性があるため、内部情報が混入しないように設計を慎重に行う必要がありました。そのため、LINE Developer Centerで公開されているAPIドキュメントから一行一行丁寧にコピーしてOpenAPIのYAML定義を作成しました。この繰り返し作業は容易ではありませんでしたが、私たちの使命であると認識し、精力的に取り組みました。この手間を惜しまない作業が、OpenAPI定義の信頼性と完成度を高める重要な一歩となりました。また、この過程で公開されているドキュメントを再評価し、ドキュメントの改善にも貢献しました。
一部の API については、Spring Boot を利用していたので、Spring Boot を使ったサーバーの実装からopen api定義を生成できるspringdoc-openapi-ui(https://springdoc.org/)(https://github.com/springdoc/springdoc-openapi)を入れて、実際にAPIを定義しているコードから定義を抽出できました。こういった工夫によって、工数を削減できるので、OpenAPI 定義を書きたい方はこういうアプローチも試すとよいでしょう。
私たちのOpenAPI定義の品質を担保するために、さらなるステップを踏みました。それは、Spectral https://stoplight.io/open-source/spectral を使用してValidationを追加することです。これにより、私たちのOpenAPI定義が特定の基準を満たしていることを確認できました。
また、OpenAPI仕様はある程度の自由度を持っています。そのため、さまざまな定義方法が存在し、一部の方法ではコード生成が難しくなる可能性があります。この問題を解決するために、私たちは一定の制約を定義し、それを実装するために zx https://github.com/google/zx を使用しました。これにより、私たちのOpenAPI定義は一貫性を持ち、コード生成もスムーズに行えるようになりました。
まずは Java SDK を OpenAPI 化してみる
JavaをOpenAPIからのコード生成の最初の対象として選択しましたが、このプロセスは容易ではありませんでした。
Java SDKのOpenAPI化において、インターフェースが変更されることが明らかだったため、メジャーバージョンアップを行うことにしました(SDKはすべてSemantic versioningを採用しています)。
Javaはコンパイル型言語であるため、メジャーバージョンアップ時には旧バージョンとの互換性を思い切って切り捨てることにしました。
openapi-generatorを使用してコードを生成する際、通常はmustacheをテンプレートとして採用しています。ただし、mustacheは表現力に限界があるため、複雑なテンプレートの表現が難しいことがあります。そのような課題に直面した際、Pebble template を知ることになりました。Pebble templateはJinja2やTemplate-Toolkitのような書き方が可能で、非常に柔軟なテンプレートエンジンです。さらに、IntelliJ IDEAのPebbleプラグインは優れており、@pebvariableを使用して変数の型を定義すると、効果的な補完機能が提供され、開発効率が大幅に向上しました。
IntelliJ IDEA のpebbleプラグインは非常に高品質で、@pebvariableを使用して変数の型を定義することで、補完機能が効果的に動作し、開発効率が大幅に向上します。openapi-generatorを使用する開発者には、ぜひPebble templateをオススメします。
{# @pebvariable name="model" type="org.openapitools.codegen.CodegenModel" -#}
{%- if model.isDeprecated -%}
@Deprecated
{% endif %}
Java SDKのOpenAPI化に際して、既存の実装をRetrofit2ベースから自前でRequest/Responseクラスを実装する形に変更しました。これは、将来的にRetrofit2を置き換える可能性があるため、Retrofit2に依存したクラスをインターフェースで使用することを避けるための措置です。将来的にはRetrofit2を外す方向に向かうかもしれません。
また、Java SDKはSpring Boot 3に依存し、Java 17をベースラインとしています。この変更により、Beanの定義にはRecordを使用しました。これにより、toString()などのメソッドが自動的に実装され、開発 効率が向上しました。さらに、lombokに依存する必要がなくなり、SDKを使用する際にlombokに精通している必要がなくなりました。これは大きな改善であり、問題を解決する一助となりました。
また、我々が書いていたOpenAPIのYAML定義自体が、openapi-generatorとの相性が悪いことがわかりました。openapi-generator の仕様に合わせて定義をかなり細かく調整する必要があり、この部分が一番骨の折れる作業でした。これらの一連のチャレンジと改善により、Java SDKのOpenAPI化を達成できました。
その他のSDKへのOpenAPI化
Java SDKのOpenAPI化の成功を受けて、その経験と知見を活かして他のSDKもOpenAPI化する取り組みを進めました。このために、各SDKのコードを詳細に調査し、OpenAPIに対応するための必要な改良や更新を計画しました。
とくに大きな課題の一つは、各SDKの言語特性やライブラリへの依存度の違いでした。各SDKには異なる言語やライブラリが使われており、それに合わせてOpenAPI化を進める方法を選定する必要がありました。各SDKの特性や依存度を理解し、最適な方法を見つけるために、十分な調査と計画が必要でした。
SDKを実質的にフルリライトすることは、検証コストが高くつくため、各プログラミング言語のSDKを一つ一つ実装することは非常に高コストな作業となります。各言語のSDKの開発者に協力してもらうことが望ましいですが、進捗(しんちょく)がなかなか順調に進まないこともありました。しかし、最終的には意気込みと努力によって実装が進展し、成果を上げられました。
OpenAPI化に伴い、SDKのインターフェースが変更されることは避けられませ んでした。とくに、Javaに続いて実装が完了したPHP SDKでは、メジャーバージョンアップが行われました。このメジャーバージョンアップには非互換の変更が含まれており、アップデート時にそれに気付かずに問題が発生した開発者もいたため、今後のSDKでは古いSDKインターフェースをDeprecated な状態で維持しつつ、OpenAPIで生成した新しいインターフェースを追加する方針に切り替えました。これによって、開発者は古い方法を使用し続けることも可能となり、スムーズな移行が可能になります。
各SDK の開発を進めるなかで、OpenAPI 定義が生成されたタイミングで各SDKが更新される仕組みを GitHub actions で実装しました。また、GitHub Relases を発行するだけで npm、 pypi などにリリースされるように GitHub Actions を整備して、各SDKの挙動を整備しました。これにより、各SDKの運用が大変整理され、効率化が大きく進みました。
現在の SDK の実装状況は以下の通りになっています。
- Java: OpenAPI 化済み
- Golang: OpenAPI 化済み
- NodeJS: OpenAPI 化済み
- Python: OpenAPI 化済み
- PHP: OpenAPI 化済み
- Ruby: OpenAPI 化実装中
- Perl: 現状維持
OpenAPIからSDKを生成することにより 、新たなAPIの追加や既存APIの更新をより迅速に、そして確実に反映できました。
まとめ
LINEアプリが提供する多数の複雑なAPIに対応するSDKを開発し、メンテナンスを行うことは決して簡単な作業ではありませんでした。しかし、私たちは開発者がより効率的にAPIを活用できるように、SDKの改善に挑戦し続けました。さまざまな課題に直面しながらも、私たちの部門はSDK生成の新しい方法としてOpenAPIを採用する決断をしました。
OpenAPIの採用により、新しいAPIの追加や既存APIの更新をより迅速かつ確実に反映できました。これにより、社内外の開発者が高品質なSDKを利用し、より良いサービスを提供できるための支援を続けられます。
現在、Java、NodeJS、PHP、Python、GolangのSDKがOpenAPIに対応しており、RubyのSDKのOpenAPI対応も進行中です。OpenAPI 対応により、サードパーティで独自のSDKの開発を行っていただくこともやりやすくなりました。これらの対応が完了すれば、各言語独自のSDKで、常に最新の機能を迅速に取り入れられます。私たちの部門としては、このような取り組みを通じて、皆様がより良いサービスを提供できるよう全力でサポートしていきます。