http4kをベースにサーバーサイド Kotlinの関連ライブラリをつかってみた


Google I/O 2017でKotlinがAndroidアプリ開発言語に選定された。Androidに限らずサーバーサイドでもメインの言語としてKotlinは選択できて、いくつかのサービス開発の経験を経てきた。これまでSpring BootをメインのFrameworkに置いて開発をしてきたけど、この機会にKotlinで開発されたライブラリをつかってみて実戦投入が検討できそうなライブラリを探っていきたい欲がでてきた。

モチベーション

先述したとおりSpring Bootを中心に置いた開発は快適でKotlinを言語選択しても生産性が落ちることはない。ただKotlin主体で開発されたHTTPサービスを提供するライブラリも多く存在している状況にあり、一度Spring Bootから離れてKotlin主体のFreameworkを試してみたい、というのが今回のエントリのモチベーション。 HTTPサービス周りのライブラリだけを試してみるのではなく、サーバーサイドアプリケーションの関心事にあるORMやDIなどもKotlin主体のライブラリを選択していきたい。

HTTPサービス

まずはHTTPサービス。 これには http4kを選択。

コミット状況も最近の履歴が含まれているのが好印象。
ルーティングを設計してHTTPハンドラを実装していきながら、リクエストインタセプターも実装できるのでHTTPサービスにおいては最低限のものが揃っている。
HTTP ServerはJetty, Netty, Undertowから選択できる。

1
2
3
4
routes(
    GET to "/hello/{name:*}" by { request: Request -> Response(OK).body("Hello, ${request.path("name")}!") },
    POST to "/fail" by { request: Request -> Response(INTERNAL_SERVER_ERROR) }
).asServer(Jetty(8000)).start()

HTTPクライアントも提供されていて簡単に実装できる。

1
2
3
val uri = Uri.of("${getUrlBase()}${getPath()}?Authorization=%s".format(param))
val request = MemoryRequest(Method.GET, uri, uri.queries())
val response = OkHttp()(request)

APIドキュメンテーションにはSwaggerをつかえる。
RouteModuleにSwaggerを有効にすれば /api/api-docのエンドポインにjson形式のApiドキュメンテーションがルーティングされるでSwagger-UIに読み込ませればよい。

1
2
RouteModule(Root / "api", Swagger(ApiInfo("http4k test API", "v1.0"), Jackson))
            .withDescriptionPath { it / "api-docs" }

http4kをつかってtodo-listのバックエンドAPIを作ってみたのでエントリ最後にあるgithubから詳細なプログラムコードが参照できるので機会があれば参考にしてほしい。

データベース

次にデータベース。
これには requeryを選択。

Java/Kotlin/Androidの言語をサポートしたORM。

interfaceまたはdata classでエンティティを作る。
@OneToMany@PostLoadなどのアノテーションが提供されていて、それぞれ1:多の関連づけや更新したときのコールバックを指定できる機能が用意されている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Entity(model = "kt")
@Table(name = "task")
interface Task {

    @get:Key
    @get:Generated
    @get:Column(name = "task_id")
    var id: Long

    @get:Column(name = "title")
    var title: String

    @get:Column(name = "finished_at")
    var finishedAt: LocalDateTime?

    @get:Column(name = "created_at")
    var createdAt: LocalDateTime

    @get:Column(name = "updated_at")
    var updatedAt: LocalDateTime
}

SQLはDSLでタイプセーフに組み立てられる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun findOneById(id: Long): Result<Task, TaskException> {
    return data.invoke {

        // ココ
        val query = select(Task::class) where (Task::id eq id)

        if (query.get().firstOrNull() == null)
            Result.Failure(TaskException.TaskNotFoundException("task not found. taskId:%d".format(id)))
        else
            Result.Success(query.get().first())
    }
}

Data Transfer Object

ユーティリティ的なところであるがResultというモデルをつかってみた。

データベースのサンプルコードのところで出てきたが findOneById(id: Long)のメソッドの返り値にResult<Task, TaskException>をつかっている。
Resultを使えば処理結果にSuccessFailureを含めて返すことができる。

1
2
3
4
if (query.get().firstOrNull() == null)
    Result.Failure(TaskException.TaskNotFoundException("task not found. taskId:%d".format(id)))
else
    Result.Success(query.get().first())

Resultの戻り値を受け取った側は次のように処理できる。(.fold({ task -> TaskModel(task) }, { error -> throw handle(error) })のところ)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class GetTaskService(private val taskRepository: TaskRepository) : ApplicationService&lt;GetTaskCommand, TaskModel> {

    override fun invoke(command: GetTaskCommand): TaskModel {
        return taskRepository.findOneById(command.id).fold({ 
            task -> TaskModel(task)
        }, {
            error -> throw handle(error)
        })
    }
}

データがない場合に nullまたはlistOf()を返すかexceptionをthrowするか迷いがちな印象がある。
今回はレポジトリ層の全ての返り値の型にResultを指定してみたところ開発効率が良かった。

Dependency injection

Spring Bootを使わない縛りを入れたのでDIもkotlin純製のものを選択。
DIにはKodeinをつかった。

1
2
3
4
5
6
7
8
9
val kodein = Kodein {
    // filter
    bind&lt;AuthFilter>("authFilter") with singleton { AuthFilter(instance("authClient")) }
    bind&lt;ExceptionFilter>("exceptionFilter") with singleton { ExceptionFilter() }
・・・
}

val exceptionFilter = kodein.instance&lt;ExceptionFilter>("exceptionFilter")
val authFilter = kodein.instance&lt;AuthFilter>("authFilter")

ドキュメントに好印象。
ドキュメントは熟読できていないが必要最低限のDIはできた。

サンプルアプリケーション

これまで紹介したライブラリを用いてTodoリストのサンプルアプリケーションを開発してみたので合わせて参照していただきたい。

今回はこちらのエントリに触発されてDDDを意識して作ってみたが、まだまだ勉強しなくてはと感じた。

ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか // Speaker Deck

まとめ