KotlinとKovertを使用したRESTAPI
1. 序章
KovertはRESTAPIフレームワークであり、強い意見があり、の使用を非常に簡単に開始できます。 Vert.x の機能を活用しますが、アプリケーションの一貫した開発が大幅に容易になります。
Kovert APIを最初から作成することも、既存のVert.xアプリケーションでKovertコントローラーを使用することもできます。 ライブラリは、使用するように設計されています。
2. Mavenの依存関係
Kovertは標準のKotlinライブラリであり、 MavenCentralで利用できます。
<dependency>
<groupId>uy.kohesive.kovert</groupId>
<artifactId>kovert-vertx</artifactId>
<version>1.5.0</version>
</dependency>
3. Kovertサーバーの起動
Kovertは、アプリケーションの配線に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にバインドされる単一のコントローラーメソッドを定義します。 パスパラメーターとして値が提供され、このコントローラーはそれをそのまま返します。
次に、それらを initControllers クロージャーのKovertルーターに挿入すると、実行中のサーバーからすべて利用できるようになります。
val initControllers = fun Router.() {
bindController(SimpleController(), "api")
}
これにより、コントローラーの / api の下にメソッドがマウントされます。したがって、 getStringById()メソッドは、実際には / api / string /:idで使用できます。 生成されたURLが衝突しない限り、同じパスにマウントできるコントローラーの数に制限はありません。
4.1. メソッドの命名規則
メソッド名を使用してURLを生成する方法の規則はすべて、Kovertアプリケーションによって十分に文書化されています。
簡単に言えば:
- 最初の単語はHTTPメソッド名として使用されます– get、post、put、deleteなど。 これらのエイリアスも可能であるため、たとえば「削除」の代わりに「削除」を使用できます。
- 「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は、使用できる2つの追加の例外HttpErrorCodeとHttpErrorCodeWithBodyを定義します。
より一般的なものとは異なり、これらは例外をサーバーログにも出力させ、標準でサポートされていないものを含むステータスコードと応答本文をプログラムで決定できるようにします。
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であり、コントローラーにリッチタイプの適切なパラメーターがある場合、これを自動的に処理します。
たとえば、新しいPersonをサーバーに送信できます。
fun RoutingContext.putPersonById(id: String, person: Person) = person
これにより、PersonBeanに準拠する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は、非同期処理をサポートするためにKovenantライブラリと連携するように設計されています。
私たちがする必要があるのは、 約束
fun RoutingContext.getPersonById(id: String): Promise<Person, Exception> {
task {
return personService.getById(id) ?: throw HttpErrorNotFound()
}
}
これにより、 personService を呼び出して、必要なPersonの詳細をロードできるバックグラウンドスレッドが開始されます。 見つかった場合はそのまま返し、KovertはそれをJSONに変換します。
見つからない場合は、 HttpErrorNotFound をスローします。これにより、代わりにHTTP404が返されます。
7. ルーティングコンテキスト
これまで、デフォルトのRoutingContextをベースとしてすべてのコントローラーを作成してきました。 これは機能しますが、それが唯一のオプションではありません。
独自のカスタムクラスを使用することもできます。ただし、単一パラメーターのコンストラクターが RoutingContext
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で簡単なRESTAPIを作成するためのKovertの概要を説明しました。
ここに示されている以上に、Kovertを使用して達成できることはたくさんあります。 うまくいけば、これで単純なRESTAPIへの旅を始めることができます。
そして、いつものように、GitHubでこのすべての機能の例を確認してください。