KotlinおよびKovertを使用したREST API

1. 前書き

  • Kovertは、REST APIフレームワークであり、強く評価されているため、使い始めるのは非常に簡単です*。 Vert.xの力を活用しますが、一貫したアプリケーションの開発を非常に簡単にします。

    Kovert APIをゼロから作成することも、既存のVert.xアプリケーションでKovertコントローラーを使用することもできます。 このライブラリは、どのように使用したい場合でも動作するように設計されています。

2. Mavenの依存関係

Kovertは標準のKotlinライブラリであり、https://search.maven.org/search?q = g:uy.kohesive.kovert%20AND%20a:kovert-vertx [Maven Central]で入手できます。
<dependency>
    <groupId>uy.kohesive.kovert</groupId>
    <artifactId>kovert-vertx</artifactId>
    <version>1.5.0</version>
</dependency>

3. Kovertサーバーの起動

Kovertは、アプリケーションの配線にlink:/kotlin-kodein-dependency-injection[Kodein]を多用しています。 これには、KovertおよびVert.xの設定のロード、およびすべてを機能させるために必要なすべてのモジュールが含まれます。
*比較的少量のコードで簡単なKovertサーバーを起動できます*。
Kovertサーバーのサンプル設定ファイルを見てみましょう。
{
   kovert: {
       vertx: {
           clustered: false
       }
       server: {
           listeners: [
               {
                    host: "0.0.0.0"
                    port: "8000"
               }
           ]
       }
   }
}
そして、Kovertサーバーをビルドして実行するKodeinインスタンスを起動できます。
fun main(args: Array<String>) {
    NoopServer.start()
}

class NoopServer {
    companion object {
        private val LOG: Logger = LoggerFactory.getLogger(NoopServer::class.java)
    }

    fun start() {
        Kodein.global.addImport(Kodein.Module {
            val config =ClassResourceConfig("/kovert.conf", NoopServer::class.java)
            importConfig(loadConfig(config, ReferenceConfig())) {
                import("kovert.vertx", KodeinKovertVertx.configModule)
                import("kovert.server", KovertVerticleModule.configModule)
            }

            import(KodeinVertx.moduleWithLoggingToSlf4j)
            import(KodeinKovertVertx.module)
            import(KovertVerticleModule.module)
        })

        val initControllers = fun Router.() { }

        KovertVertx.start() bind { vertx ->
            KovertVerticle.deploy(vertx, routerInit = initControllers)
        } success { deploymentId ->
            LOG.warn("Deployment complete.")
        } fail { error ->
            LOG.error("Deployment failed!", error)
        }
    }
}
これにより、設定が読み込まれ、設定どおりに実行されているWebサーバーが起動します。
この時点で、Webサーバーにはコントローラーがありません。

4. シンプルなコントローラー

  • Kovertの最も強力な側面の1つは、コントローラーを作成する方法です*。 HTTP要求をコードに割り当てる方法をシステムに指示する必要はありません。 代わりに、これはメソッド名に基づいた規則によって駆動されます。

    特定のパターンで名前が付けられたメソッドを持つ単純なクラスとしてコントローラーを記述します。 メソッドは、_RoutingContext_クラスの拡張メソッドとして記述する必要もあります。
class SimpleController {
    fun RoutingContext.getStringById(id: String) = id
}
これは、_GET / string /:id_にバインドされた単一のコントローラーメソッドを定義します。 値はパスパラメーターとして提供され、このコントローラーはそれをそのまま返します。
次に、them_initControllers_クロージャーのKovertルーターにそれらを挿入すると、実行中のサーバーからすべて使用可能になります。
val initControllers = fun Router.() {
    bindController(SimpleController(), "api")
}
これにより、コントローラのメソッドが__ / api_の下にマウントされます。したがって、_getStringById()_メソッドは実際に_ / api / string /:id_で使用できます。 生成されたURLのいずれも衝突しない限り、同じパスにマウントできるコントローラーの数に制限はありません。

* 4.1。 メソッドの命名規則*

メソッド名を使用してURLを生成する方法のルールは、すべてKovertアプリケーションで十分に文書化されています。
簡単に言えば:
  • 最初の単語はHTTPメソッド名として使用されます。get、post、put、
    削除など これらのエイリアスも使用できるため、たとえば「削除」の代わりに「削除」を使用できます。

  • 「By」および「In」という単語は、次の単語が

    パスパラメータ。 たとえば、ById_は /

    id_になります。

  • 「With」という単語は、次の単語が両方ともパスであることを示すために使用されます
    パラメータとパスの一部。 たとえば、WithId_は / id /:id_になります。

    他のすべての単語はパスセグメントとして使用され、それぞれが異なるパスである個々の単語に分けられます。 これを変更する必要がある場合は、Kovertで自動的に解決できるようにする代わりに、アンダースコアを使用して単語を区切ることができます。
    例えば:
fun getSomethingSimple()       // GET /something/simple
fun get_something_elseSimple() // GET /something/elseSimple
アンダースコアを使用してパスセグメントを区切る場合、すべてのセグメントは小文字で始まる必要があることに注意してください。 これには、特別な単語「By」、「In」、および「With」が含まれます。
そうでない場合、Kovertは代わりにパスセグメントとしてそれらを扱います。
例えば:
fun getTruncatedStringById()    // GET /truncated/string/:id
fun get_TruncatedString_By_Id() // GET /TruncatedString/By/Id
fun get_truncatedString_by_id() // GET /truncatedString/:id
fun get_truncatedString_by_Id() // GET /truncatedString/:Id

* 4.2。 JSONレスポンス*

*デフォルトでは、Kovertはコントローラーから返されたBeanに対してJSONレスポンスを返します*:
data class Person(
    val id: String,
    val name: String,
    val job: String
)

class JsonController {
    fun RoutingContext.getPersonById(id: String) = Person(
        id = id,
        name = "Tony Stark",
        job = "Iron Man"
    )
}
これは、_ / person /:id_を処理する単一のコントローラーを定義します。 その後、_ / person / abc、_をリクエストすると、次のJSONレスポンスが返されます:
{
    "id": "abc",
    "name": "Tony Stark",
    "job": "Iron Man"
}
data class Person(
    @JsonProperty("_id")
    val id: String,
    val name: String,
    val job: String
)
これにより、代わりに以下が返されます。
{
    "_id": "abc",
    "name": "Tony Stark",
    "job": "Iron Man"
}

* 4.3。 エラー応答*

*継続できないことを示すエラーをクライアントに返す必要がある場合があります*。 HTTPにはさまざまな理由で返されるさまざまなエラーがあり、* Kovertにはこれらを処理するためのシンプルなメカニズム*があります。
このメカニズムをトリガーするには、コントローラーメソッドから適切な例外をスローするだけです。 Kovertは、サポートされているHTTPステータスコードごとに例外を定義し、これらがスローされた場合に自動的に正しいことを行います。
fun RoutingContext.getForbidden() {
    throw HttpErrorForbidden() // Returns an HTTP 403
}
*場合によっては、発生することをさらに細かく制御する必要がある場合もあります*。したがって、Kovertでは、two_HttpErrorCode_と_HttpErrorCodeWithBody_を使用できる2つの追加の例外を定義しています。
より一般的なものとは異なり、これらは例外をサーバーログにも出力し、標準でサポートされていないものを含むステータスコードと応答本文をプログラムで決定できるようにします。
fun RoutingContext.getError() {
    throw HttpErrorCode("Something went wrong", 590)
}
fun RoutingContext.getErrorbody() {
    throw HttpErrorCodeWithBody("Something went wrong", 591, "Body here")
}
いつものように、本文でリッチオブジェクトを使用でき、これは自動的にJSONに変換されます。

5. 高度なコントローラーバインディング

ほとんどの場合、既に説明したシンプルなコントローラーサポートで対応できますが、アプリケーションに希望どおりの動作をさせるには、もう少しサポートが必要な場合があります。 * Kovertを使用すると、さまざまなことに柔軟に対応できるため、必要なアプリケーションを構築できます*。

* 5.1。 クエリ文字列パラメーター*

*場合によっては、リクエストパスの一部ではない追加のパラメーターをコントローラーに渡す必要もあります*。 メソッドにパラメーターを追加するだけで、クエリ文字列パラメーターとして渡される値を取得できます。
fun RoutingContext.get_truncatedString_by_id(id: String, length: Int = 1) =
    id.subSequence(0, length)
*これらのパラメーターのデフォルト値を指定することもできます*。これにより、オプションでURLで提供できます。
たとえば、上記は次のことを行います。
  • / truncatedString / abc ⇒“ a”

  • _ / truncatedString / abc?length = 2_ ⇒“ ab”

* 5.2。 JSONリクエストボディ*

多くの場合、構造化されたデータもサーバーに送信できるようにしたいと考えています。 * Kovertは、リクエストの本文がJSON *で、コントローラーにリッチタイプの適切なパラメーターがある場合、これを自動的に処理します。
たとえば、サーバーにnew__Person_を送信できます。
fun RoutingContext.putPersonById(id: String, person: Person) = person
これにより、Person Beanに準拠したJSONリクエスト本文を受け入れる_PUT / person /:id_の新しいハンドラーが作成されます。 これは、必要に応じて自動的に使用可能になります。

* 5.3。 カスタム動詞エイリアス*

場合によっては、Kovertがメソッド名を照合してURLを要求する方法をカスタマイズできるようにしたい場合があります。 特に、使用可能なHTTP動詞エイリアスのデフォルトセットに満足できない場合があります。
Kovertでは、_KovertConfig.addVerbAlias_呼び出しを使用して、これを簡単に管理できます。 これにより、必要に応じて既存のメソッドを置き換えるなど、任意のHTTPメソッドに任意の単語を登録できます。
KovertConfig.addVerbAlias("submit", HttpVerb.POST)
これにより、_submitPerson()_のメソッド名を記述でき、_POST / person_に自動的にマッピングされます。

* 5.4。 メソッドに注釈を付ける*

コントローラーメソッドのカスタマイズをさらに進める必要がある場合があります。 そのような場合、* Kovertは、マッピングを完全に制御するためにメソッドで使用できる注釈を提供します*。
個々のメソッドのレベルで、_ @ Verb_および_ @ Location_アノテーションを使用して、一致する正確なHTTP動詞とURLを指定できます。 たとえば、次は「GET / ping /:id」に応答します。
@Verb(HttpVerb.GET)
@Location("/ping/:id")
fun RoutingContext.ping(id: String) = id
または、_ @ VerbAlias_および_ @ VerbAliases_メソッドを使用して、アプリケーション全体のメソッドではなく、単一クラスのすべてのメソッドの動詞エイリアスをオーバーライドできます。 たとえば、次は「GET / string /:id」に応答します。
@VerbAlias("show", HttpVerb.GET)
class AnnotatedController {
    fun RoutingContext.showStringById(id: String) = id
}

6. 非同期応答

*これまで、すべてのコントローラーメソッドは同期でした*。
これは単純な場合には問題ありませんが、Vert.xは単一のI / Oスレッドを実行するため、コントローラーメソッドのいずれかが何らかのアクションの実行を待機する必要がある場合(たとえば、データベースの場合、残りのアプリケーション全体をブロックする必要はありません。
  • Kovertは、https://www.baeldung.com/kotlin-kovenant [Kovenant]ライブラリと連携して非同期処理をサポートするように設計されています*。

    必要なのは、_Promise <Result、Exception> _ –を返すことです。ここで、_Result_はハンドラーの戻り型です–非同期処理を取得します。
fun RoutingContext.getPersonById(id: String): Promise<Person, Exception> {
    task {
        return personService.getById(id) ?: throw HttpErrorNotFound()
    }
}
これにより、バックグラウンドスレッドが開始され、background_personService_を呼び出して、必要な個人の詳細を読み込むことができます。 見つかった場合はそのまま返され、KovertがJSONに変換します。
見つからない場合は、_HttpErrorNotFound_をスローして、代わりにHTTP 404を返します。

7. ルーティングコンテキスト

これまで、デフォルト_RoutingContext_をベースとして使用してすべてのコントローラーを作成しました。 これは機能しますが、唯一のオプションではありません。
__RoutingContextを取る単一パラメーターコンストラクターがある限り、独自のカスタムクラスを使用することもできます。 __このクラスは、コントローラーメソッドのコンテキストです。つまり、 _this_の値–呼び出しに必要なものはすべて実行できます。
class SecuredContext(private val routingContext: RoutingContext) {
    val authenticated = routingContext.request().getHeader("Authorization") == "Secure"
}

class SecuredController {
    fun SecuredContext.getSecured() = this.authenticated
}
*これにより、「Authorization」ヘッダーの値が「Secure」であるかどうかを確認することで、コールが保護されているかどうかを判断できるコンテキストが提供されます。 これにより、コントローラークラスが単純なメソッド呼び出しのみを処理し、それらの実装が問題にならないように、さらに多くのHTTP詳細を抽象化できます。
この場合、要求が保護されていると判断する方法は、HTTPヘッダーを使用することです。 クエリ文字列パラメーターまたはセッション値を簡単に使用できますが、コントローラーコードは関係ありません。
すべての単一のコントローラーメソッドは、使用するルーティングコンテキストクラスを個別に宣言します。 それらはすべて同じでも、それぞれが異なっていても、Kovertは正しいことを行います。

8. 結論

この記事では、Kotlinで簡単なREST APIを作成するためのKovertの概要を説明しました。
ここに示すよりも、Kovertを使用して達成できる以上のものがあります。 うまくいけば、これで簡単なREST APIへの旅が始まるはずです。
そして、いつものように、https://github.com/eugenp/tutorials/tree/master/kotlin-libraries [GitHubで]のすべての機能の例をチェックしてください。