https://a.storyblok.com/f/270183/1368x665/d80f2b5c9f/kotlin_sdk-updates.png

Vonage KotlinサーバーSDKの発表

最終更新日 October 25, 2024

所要時間:1 分

はじめに

Kotlinが2010年代初頭に登場したとき、この言語がトップ20に入るとは誰も想像できなかっただろう。 トップ20のプログラミング言語になるとは誰も想像できなかっただろう。Androidアプリ開発の事実上のデファクト言語となって以来、エコシステムは豊富な機能で拡大する一方だ。 専用の年次カンファレンス.

Kotlinは、関連性を維持するために更新されたリリース・ケイデンスと言語機能の流入にもかかわらず、モバイル・アプリケーション以外でも人気のある言語であることに変わりはない。サーバーサイドの開発では、Kotlinはいくつかの理由で優れた選択肢である。既存のライブラリの豊富なエコシステム(すべてのJVMベースの言語が恩恵を受けている)、最新の2.0リリースでさえ10年前のJava 8バイトコードと互換性がある、レガシーな機能を使わずに一からエレガントに設計されている、最小限の定型文と強力かつ直感的な構文とセマンティック・シュガーでプログラミングが楽しい。

四半期フルタイムで取り組んだ結果、Vonage Kotlin SDKが完成しました。 Vonage Kotlin SDKが正式に利用可能になり v1.0.0!公開先は Mavenセントラルに公開されており、ビルドシステムで使用するための手順も記載されています。この SDK を使用すると、Kotlin ですべての GA Vonage API を使用して、音声通話、SMS の送受信、ビデオアプリケーションを作成できます。このブログ記事では、このSDKがどのようにして生まれたのか、そしてなぜ生まれたのかを説明します。

背景

サーバーサイドでのKotlinの普及が進んでいること、そして多くのKotlin開発者がJavaと一緒に作業しなければならないときに感じている一般的な目つきの悪さを認識し、私たちはKotlinのコード・スニペットを提供することを考えていました。 JavaサーバーSDK.結局のところ、Kotlinの利点の1つは、Javaとのシームレスな互換性です。しかし、私たちはもう一歩踏み込むことにしました。私たちは、Kotlin開発者に真にファーストクラスの体験を提供したかったのですが、SDKを完全にゼロから開発するコストやメンテナンスの負担はありませんでした。私たちは、比較的短期間で概念実証を行い、最終的に本格的なSDKを開発することを目指しました。残りは...そう、コミット履歴の コミット履歴!

モチベーション

当然の疑問が浮かんだ: Java SDKの何が問題なのか?結局のところ、私は2022年第2四半期以来、多かれ少なかれフルタイムでこのSDKを保守してきたし、Kotlin自身も互換性を確保するために多くの重労働をこなしている。答えは、KotlinとJavaの言語機能の差分にある。 Javaは冗長であるという評判があり、Java 8からの移行に消極的な企業(あるいは、少なくとも氷のようなペースで移行している)と組み合わされ、2つの言語間の互換性がバイトコードレベルであるという事実は、Javaでは慣用的であっても、Kotlinでは必ずしもうまく翻訳されないことを意味する。さらに、Kotlinの強力な言語機能の印象的な武器は、Kotlinで書かれたコードによってのみ活用できる。

無効性

Javaと比べたKotlinの特徴の1つは、NULL可能な値とそうでない値の区別である。これは java.util.Optionalのようなモナド構造では実現できない。したがって のような(に相当する オブジェクト) と Any?は明確に異なる2つの型であり、前者は非ヌル型であり、後者はヌル型である。Javaにはこの区別がないため(プリミティブ型を除く)、Kotlinには"プラットフォーム型"という概念があり、これは感嘆符で示されます(たとえば など)!).これは、互換性のためにKotlinのnullability機能をバイパスします。KotlinのSDKを作成することで、関数のパラメータと戻り値の型の両方において、何がヌル可能で何がヌル不可能かを明示する機会が得られます。

名前付きパラメータ、デフォルト・パラメータ、オプション・パラメータ

明示的なnull可能性の主な利点は、おそらく関数の引数で最もよく実感できるだろう。Javaでオプショナルを使うのはエレガントとは言えないが、Kotlinでは、オプショナル・パラメータをnullableと宣言し、デフォルトでnullに設定することで、コード内で明示的にすることができる。これにより、ユーザーは本当に必要なものだけを指定することができる。さらに、Kotlinには名前付き関数パラメータがあり、引数はその位置に基づいて提供される必要はありません。明示的に宣言することができるので、ユーザーは特定のオプションパラメータを省略することができ、IDEからのコードインレイに依存することなく、どのパラメータを提供するかを明示することができます(特に、同じ型のパラメータが複数ある場合に便利です)。さらに、NULLでないパラメータであってもデフォルト値を設定できるということは、デフォルト値を明示的に文書化できるという利点があり、Javaのように組み合わせごとに引数の数を変えてオーバーロードするのではなく、関数ごとに1つのメソッドだけを宣言すればよいということになります。

例えば、ほとんどのAPIで一般的な操作は、リソースのリストです。一般的に、これはAPIの中心的なリソースであり、ユーザー、コール、アプリケーション、ブロードキャストなどです。のメソッドシグネチャは Video API の listRender メソッドのシグネチャをご覧ください。のデフォルト値 カウントオフセットパラメータのデフォルト値が提供されている。これらは両方とも同じ型なので、Javaでこのメソッドの4つの異なるバリエーションを提供することは不可能である。Kotlinでは、1つのメソッドで4つのケースをカバーできる、 オフセットだけである、 カウントのみ、そして両方のパラメーター。これらは以下のように呼び出される:

video.listRenders()            // Default params
video.listRenders(count = 25)  // Count only
video.listRenders(offset = 10) // Offset only
video.listRenders(25, 10)      // Both params

ビルダーのためのラムダ

Java SDKのもう一つの目障りな点は、ビルダーに大きく依存していることだ。私は以前 以前、ビルダー・パターンについて書いたことがあるが、それが存在するのは、言語が名前付きでデフォルトの(オプションの)コンストラクタ・パラメーターを持たないからにほかならない。場合によっては、Kotlinではビルダーを完全に排除し、名前付き/オプションのパラメーターだけに頼ることができる。その良い例が Numbers Insight APIの実装だ。.裏ではJava SDKのビルダーが使われているが、Kotlinの表現力によって、メソッドのシグネチャだけで必須パラメーターとオプションパラメーターを区別できることに注目してほしい。

しかし、このパターンを使ってすべてを再実装することは、このプロジェクトの範囲よりも多くの作業(と重複)を必要とし、メンテナンスの負担を増やすことになる。その代わりに、Java SDKで確立されたビルダー・パターンを、Kotlinでは「末尾のラムダ(trailing lambdas)」を使って非常にエレガントに再利用することができる。説明するために、いくつかの基本的な例から始めよう。例として Messages API実装ではの実装では、有効なメッセージタイプとチャネルの組み合わせごとにユーティリティメソッドが用意されています。各関数は、適切な Builder クラスをレシーバーとするラムダ式を入力として受け取ります。つまり、ユーザーの視点から見ると、SMSを送信するのは次のようになります:

vonage.messages.send(smsText {
  from("Kotlin SDK")
  to(System.getenv("TO_NUMBER"))
  text("Hello, World!")
})

メソッド から, からおよび テキストはすべて JavaSDKのSmsTextRequest.Builderそのスーパークラス MessageRequest.Builderに由来する。しかし、Kotlin開発者の観点からすると、ビルダーを取得して build()メソッドを呼び出すプロセス全体が隠されている。デフォルトのパラメータと組み合わせると、ビルダーのパラメータがすべてオプションの場合、完全に省略できることになる。たとえば createSession関数の実装です:

fun createSession(properties: CreateSessionRequest.Builder.() -> Unit = {}): CreateSessionResponse =
  client.createSession(CreateSessionRequest.builder().apply(properties).build())

デフォルトのパラメーターについては、以下のように呼び出すことができる:

val session = vonage.video.createSession()

オプションのパラメーターを指定するには、次のように末尾にラムダ構文を使う:

val session = vonage.video.createSession {
  mediaMode(MediaMode.RELAYED)
  archiveMode(ArchiveMode.MANUAL)
}

また、オプションの値を提供するために、末尾のラムダを使用することも可能である。おそらく、最も複雑な例は Voice API の実装にある。 NCCOを定義する複雑さが、このアプローチから最も恩恵を受ける。説明のために、2つのアクションを持つ電話番号へのVoiceコールを作成する例を示します。 話す接続- の2つのアクションと、Kotlinで書かれた他のコンフィギュレーションを使用して、電話番号への音声通話を作成する例です:

val callEvent = javaClient.voiceClient.createCall(Call.builder()
  .to(PhoneEndpoint("448001234567", "1p2#5"))
  .fromRandomNumber(true)
  .advancedMachineDetection(AdvancedMachineDetection.builder().build())
  .ncco(
    TalkAction.builder("Hello, this is a text-to-speech call.")
      .language(TextToSpeechLanguage.UNITED_KINGDOM_ENGLISH)
      .premium(true)
      .build(),
    ConnectAction.builder()
      .endpoint(SipEndpoint.builder("sip:me@example.org").build())
      .ringbackTone("http://example.com/ringback.mp3")
      .build()
  )
  .ringingTimer(30)
  .eventUrl("https://example.com/webhooks/events")
  .build()
)

適切なインデントがあればある程度読みやすいが、よりイディオム的なアプローチほどエレガントではない。同じものをKotlin SDKを使って書いてみた:

val callEvent = vonage.voice.createCall {
  toPstn("448001234567", "1p2#5")
  fromRandomNumber(true)
  advancedMachineDetection()
  ncco(
    talkAction("Hello, this is a text-to-speech call.") {
      language(TextToSpeechLanguage.UNITED_KINGDOM_ENGLISH)
      premium(true)
    },
    connectToSip("sip:me@example.org") {
      ringbackTone("http://example.com/ringback.mp3")
    }
  )
  ringingTimer(30)
  eventUrl("https://example.com/webhooks/events")
}

DSLの構文がより自然に流れていることに注目してほしい。特に toPstn関数を呼び出す必要がなくなった。 メソッドを直接呼び出す必要がなくなった。メソッドを直接呼び出す必要がなくなり、高度なマシン検出のコンフィギュレーションがない場合、ビルダーやラムダが不要であることに注意してください。しかし talkActionについては、必須パラメータである話すテキストはコンストラクタの一部であり、オプションパラメータはラムダの中にあります。同様に connectToSipアクションでは、URIは必須で、オプションのラムダは追加設定のためのものです。どちらの場合も、ラムダは省略できます:

val callEvent = vonage.voice.createCall {
  toPstn("448001234567", "1p2#5")
  fromRandomNumber(true)
  advancedMachineDetection()
  ncco(
    talkAction("Hello, this is a text-to-speech call.")
    connectToSip("sip:me@example.org")
  )
  ringingTimer(30)
  eventUrl("https://example.com/webhooks/events")
}

これがどのように実装されているかは 関数に実装されています。の connectTo* 関数で実装されている。関数のパラメータは接続先のエンドポイントであり、末尾のラムダは 接続アクションを設定するためのものです。

リソースベースのエンドポイント

KotlinとJava SDKのもう一つの違いは、Kotlin SDKがAPIコールを行うのにリソースベースのアプローチを持っていることだ。VonageのAPIのほとんどでは、照会、作成、更新、削除できるリソースが少なくとも1つある。これらのリソースのそれぞれについて、そのリソースに対応するクラスが存在し、これらのエンドポイントへのアクセスを提供します。これらのリソースに共通しているのは、一意な識別子です。特定のリソースに対して繰り返しAPIコールを行うために、この識別子を別の場所にキャッシュする必要があるのではなく、SDKがこれを処理するため、毎回IDを提供する必要はありません。これらのリソースは、次のように入れ子にすることもできます。 Video APIの実装に例がある。.以下はその例です:

val existingSession = vonage.video.session(sessionId)
val streams = existingSession.listStreams()
existingSession.connection(connectionId).sendDtmf("1234")

これは、オートコンプリートのあるIDEを使用する場合、より理にかなっている。この点で、APIコールはリソース・タイプごとにグループ化されている。特に、VideoのようにリソースとIDが入れ子になっている複雑なAPIでは、必要なパラメータが構造化によって提供されるため、IDを提供する作業がエラーになりにくくなります。これにより、ネストしたリソースのメソッド・シグネチャを整理することができ、リソースのアドレス指定とは別に、意味のあるパラメータの提供に集中することができます。対照的に、Java SDKは、API仕様のエンドポイントとSDKのエンドポイントとの間に通常1対1のマッピングがあるという意味で、「フラット/コンテキストレス」です。このアプローチは、必須パラメータとオプションパラメータの分離をさらに強化し、リクエストが構造上正しい可能性が高いことを意味する。

ビデオストリームをミュートするための VideoストリームをミュートするAPI仕様を見てみよう。を見てみよう。エンドポイントへの座標がセッションIDとストリームIDを必要とすることは明らかです。また Java SDKの実装ではJava SDK の実装では、セッション ID とストリーム ID の順番を間違えて渡してしまう可能性があります。これに対して Kotlin SDKの実装は、データを送信しないため、パラメータを必要としません。同様に ストリームをアーカイブに追加するにストリームを追加する場合も同様です。 Kotlin SDKの実装には1つのメソッドがあり、コンテキストを考慮すると、ストリームIDが渡されるべきパラメータであることが明らかです。対照的に Java SDKの実装には2つのメソッドがあり(オプションのパラメータがあるため)、どのように動作し、何を行うかを描写するためにメソッドの命名とドキュメントに依存しています。

これはおそらく個人的な好みの問題であり、どちらのアプローチが客観的に優れていると主張することはできない。しかし、Kotlin SDKが可能な限りすべてのAPIでこのアプローチに従っている一貫性は、特にJava SDKが必須パラメーターでさえもビルダーに大きく依存していることを考慮すると、Kotlin SDKを際立たせる重要な点である。

エクステンション

Kotlinのもう一つの美しい機能は 拡張関数既存のクラスを拡張することなくメソッドを定義できる機能だ。これは特に、Java SDKで定義されたビルダーをKotlinでよりイディオムにするために拡張するのに便利だ。例えば アプリケーションAPI.各Vonageアプリケーションは複数のケイパビリティを持つことができます。各Capabilityは1つ以上のWebhookを持ち、各Webhookは以下のようなタイプを持っています。 answer_url, status_url, イベント_urlなどがあります。ただし、ケイパビリティの種類によっては、特定のWebhookのみが適用されます。例えば、Verify ケーパビリティには status_urlだけですが、Voice ケーパビリティには answer_url, fallback_answer_urlおよび イベント.この記事を書いている時点では、Java SDKのこの実装はかなり不便です。説明のために、既存のアプリケーションの名前を更新し、Messages機能を削除し、VerifyとVoiceのWebhookを更新する例を示します。アプリケーションを更新するには、まずそのアプリケーションを取得する必要があることに注意してください。ここでは、Java SDKを使用して記述しています:

val ac = javaClient.applicationClient
val existing = ac.getApplication(appId)
val updated = ac.updateApplication(Application.builder(existing)
  .name("My Updated Application")
  .addCapability(Verify.builder()
    .addWebhook(Webhook.Type.STATUS, Webhook.builder()
      .address("https://example.org/webhooks/verify/status")
      .method(HttpMethod.POST)
      .build()
    )
    .build()
  )
  .addCapability(Voice.builder()
    .addWebhook(Webhook.Type.ANSWER, Webhook.builder()
      .address("https://example.org/webhooks/voice/answer")
      .method(HttpMethod.POST)
      .build()
    )
    .addWebhook(Webhook.Type.EVENT, Webhook.builder()
      .address("https://example.org/webhooks/voice/event")
      .method(HttpMethod.GET)
      .build()
    )
    .build()
  )
  .removeCapability(Capability.Type.MESSAGES)
  .build()
)

たくさんのビルダーがいる!迷子になりやすいし、ケーパビリティに間違ったウェブフック・タイプを指定するのを防ぐデザインは何もない。Kotlin SDKと比較してみよう:

val ac = vonage.application
val application = ac.application(appId)
application.update {
  name("My Updated Application")
  verify {
    status {
      url("https://example.org/webhooks/verify/status")
      method(HttpMethod.POST)
    }
  }
  voice {
    answer {
      url("https://example.org/webhooks/voice/answer")
      method(HttpMethod.GET)
    }
    event {
      url("https://example.org/webhooks/voice/event")
      method(HttpMethod.POST)
    }
  }
  removeCapability(Capability.Type.MESSAGES)
}

エンドポイントを使用してアプリケーションを取得する必要がないことに注目してください。 SDKがこれを処理してくれる。.さらに、各Capabilityに定義されている拡張関数は、定義可能なWebhookを制限しています。これは、自動補完のあるIDEで見るとより理解しやすくなりますが、基本的に、例えば Voiceブロックの中にいるとき、表示されるオプションは アンサー, fallbackAnswerそして イベントこれらは だけだからです。.どこにも "build "の記述はなく、ウェブフックの種類を直接指定する必要すらありません:適切な要素を宣言するだけです。

このようなちょっとしたことが、開発者の経験と生産性を向上させるのです。もちろん、VonageではSDKを自動生成していないので、これはJavaでも適用できる。実際、Kotlin SDKを開発する過程で、私はJava SDKの大小を問わず変更すべき点のリストを作成した。執筆時点で、このリストは55のJIRAチケットに換算できる!しかし、拡張関数で簡単に勝てるのはいいことだ。もちろん、Kotlin SDKの改善点のすべてがJava SDKにバックポートできるわけではないし、バックポートすべきでもない。

ドキュメンテーション

Kotlin SDKはKDocsを使ってドキュメント化されている。すべての関数とクラスには、パラメータ、戻り値の型、例外、その機能の説明に関する手書きのドキュメントがある。さらに、これらは最新のDokka HTMLフォーマットで公開され、比較的古く見えるJavadocsフォーマットとは対照的に、Kotlin APIドキュメントのスタイルを維持している。ドキュメントは、JARファイルからドキュメントをレンダリングできるサービスを使用して閲覧できます、 Javadoc.ioのような.もちろん、ドキュメントはIDEで直接利用することもできます。

コードサンプル

サポートされているすべてのAPIのコード・スニペットは、当社の 開発者ポータル.興味のある製品をブラウズし、'Build Your Solution'の下に、Kotlinが言語の1つであるスニペットを見つけることができます。また、スニペットを直接チェックアウトすることもできます。 GitHub の Vonage Kotlin Code Snippets リポジトリを使用して、それらを直接チェックして実行することもできます。.例えば 以下はKotlin SDKのVoice APIを使用した "Hello World "音声合成スニペットです。. 各コード・スニペットには、ドキュメント・ページでレンダリングされているように、GitHub上のバック・ソース・コードへのリンクもあります。サンプルを実行するには、リポジトリをチェックアウトして環境変数を設定し、GradleまたはIDE経由で実行するだけです。実行方法はリポジトリの README.

テスト

SDKは 99%のコード・カバレッジである。すべての関数は、必須およびオプションのパラメーターとともにテストされている。Java SDKに存在する要素でさえ、完全性のためにテストされる。Java SDKがすでにデータ・モデルとパラメータ検証をテストしているため、このアプローチはエンド・ツー・エンド/統合テストです。APIテスト用のフレームワークであるWireMockを使うことにした。

私は 高レベル関数の小さなライブラリをを構築した。その結果、各テストは仕様書のように読めるようになった。テストの解剖学は、予想されるエンドポイントURL、HTTPメソッド、リクエストボディ(JSONまたはクエリパラメータ)、ヘッダ(auth、コンテンツタイプ、ユーザーエージェント)、予想されるレスポンスパラメータ(もしあれば、JSON形式)、HTTPステータスコードをモックしている。これは基本的に、コード内の API 仕様を反映したものです。冗長でエラーの起きやすいテストを少なくするために、リクエストとレスポンスのボディは JSON として直接書かれるのではなく Map<String, Any> として記述します。.これは Jackson を使ってシリアライズできるので、必要なのは正しいプロパティ名と期待される値を提供することだけです。

以下はその例である。で新しいリクエストを送るテストです。 に新しいリクエストを送るテストです。.ここでは、仕様を定義するための宣言的なアプローチと、OpenAPI仕様のすべての要素をどのようにキャプチャしているか、すべての要素を見ることができます。実際には、SDKのほとんどのテストは、重複と複雑さをさらに減らすために、同じファイルで宣言されたさらに高レベルのメソッドを使用しています。さらに、すべてのパラメータがテストされるため、各エンドポイントに2つのテストメソッドが存在することがよくあります。最も複雑なテストは Voice APIのテストである。それでもなお、宣言的なアプローチにより、Java SDK で JSON を直接使用する比較的冗長なテストよりも、はるかに読みやすく保守しやすくなっている。例えば、Video APIのテストを Java SDKKotlin SDKきっと同意していただけると思います!

基礎となる実装とは無関係にグランドアップで書かれたテストを持つことの利点は、将来、誰もがJava SDKを使わずに純粋なKotlinでSDKを書き直すことができることだ。テストは仕様を記述するため、SDKの実際の使用方法だけを変更する必要があり、モックを変更する必要はありません。Kotlin SDKがテストしない唯一のことは、パラメーターの検証です。つまり、パラメーターの特定の組み合わせが有効かどうか、あるいはビルダーで必須のパラメーターかどうかです。データモデルのバリデーションはJava SDKでテストされ、Kotlin SDKでは意図的に複製されていない。SDKではなく、APIバックエンドがバリデーションを行い、エラー応答を返すべきだと主張する人もいるかもしれません。検証ロジックが重複し、テストと開発時間がさらに肥大化するのを避けるため、これは直交する問題であるため、意図的にスコープから除外しました。

次のステップ

SDKは現在、すべてのGeneral AvailabilityステータスのVonage APIの実装で公式にサポートされていますが、これは始まりに過ぎません。Java SDKと連動して新機能やAPIに追いつくだけでなく、チュートリアルを書いたり、統合を構築したり、ユーザーからのフィードバックに基づいて雑多な改善を行う必要があります。そういえば、私たちはコミュニティからの意見や提案を大歓迎しています。SDKをお試しいただき、ご意見をお聞かせください!

サインオフ

今のところは以上です。もし何か問題が見つかったり、機能強化の提案があれば、遠慮なく GitHub で問題を提起するで問題を提起してください。 X (旧 Twitter)または コミュニティ・スラック.Kotlin SDKを快適にお使いいただけることを願っております。

シェア:

https://a.storyblok.com/f/270183/400x400/46a3751f47/sina-madani.png
Sina MadaniVonage 元チームメンバー

シナはVonageのJavaデベロッパー・アドボケイト。アカデミックなバックグラウンドを持ち、自動車、コンピューター、プログラミング、テクノロジー、人間性など、あらゆることに好奇心旺盛。余暇には散歩をしたり、対戦型ビデオゲームをしたりしている。