この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Spring WebFluxを使ってKotlinコルーチンでReactiveなREST APIのサンプルアプリを作成し、Amazon CodeGuru Profilerを設定してみました。
結論を言うと、プロダクション環境ではないので、あまり有効なプロファイルは取得できませんでした。
ただ、比較的簡単に設定できることは確認できたので、機会があればプロダクション環境で適用してみたいと思います。
アジェンダ
- Spring WebFlux REST APIサンプルアプリの概要
- Amazon CodeGuru Profilerの設定方法
- 取得したプロファイル結果
Spring WebFlux REST APIサンプルアプリの概要
サンプルといえばTODOアプリ、簡単なCRUD REST APIをDDDっぽく作成してみました。
DBはPostgreSQLで、ユーザー認証は別サービスをHTTPで叩くイメージです。
概要のみ簡単に記載しますが、詳細については、githubを参照してください。
ファイル構成はこんな感じです。
.
├── App.kt
├── Beans.kt
├── api
│ ├── Router.kt
│ ├── exception
│ │ ├── ErrorResponse.kt
│ │ └── ExceptionHandler.kt
│ ├── filters
│ │ └── AuthFilter.kt
│ └── handlers
│ └── TodoHandler.kt
├── application
│ ├── Exceptions.kt
│ ├── auth
│ │ └── AuthService.kt
│ └── todo
│ ├── TodoData.kt
│ └── TodoService.kt
├── domain
│ └── model
│ ├── auth
│ │ ├── AuthToken.kt
│ │ ├── User.kt
│ │ ├── UserId.kt
│ │ └── UserRepository.kt
│ └── todo
│ ├── Todo.kt
│ ├── TodoContent.kt
│ ├── TodoId.kt
│ ├── TodoRepository.kt
│ └── TodoTitle.kt
└── infrastructure
├── db
│ └── todo
│ ├── PostgreSQLClient.kt
│ └── PostgreSQLTodoRepository.kt
└── http
└── auth
└── AuthHttpClient.kt
ルーティングはアノテーション使う方法もありますが、Router Functionを使うと一箇所にまとめられます。コルーチンなので、coRouter使用します。
class Router(private val todoHandler: TodoHandler, private val authFilter: AuthFilter) {
fun router() = coRouter {
GET("/todo", todoHandler::list)
GET("/todo/{id}", todoHandler::find)
POST("/todo", todoHandler::create)
PATCH("/todo/{id}", todoHandler::update)
DELETE("/todo/{id}", todoHandler::delete)
}.filter(authFilter)
}
フィルターでAuthorizationヘッダの認証をしています。全体をフィルタする場合はWebFilterを使うのが一般的だと思いますが、Router Funcitonに設定するタイプのHandlerFilterFunctionを使用してみました。
class AuthFilter(private val service: AuthService) : HandlerFilterFunction<ServerResponse, ServerResponse> {
override fun filter(request: ServerRequest, next: HandlerFunction<ServerResponse>): Mono<ServerResponse> {
val token = tokenOf(request)
return mono(Dispatchers.Unconfined) {
val user = service.authenticate(token)
request.attributes()["AUTHENTICATED_USER"] = user
request
}.flatMap(next::handle)
}
private fun tokenOf(request: ServerRequest): AuthToken =
AuthToken.createOrNull(request.headers().firstHeader("Authorization"))
?: throw TodoUnauthorizedException("Auth header is required.")
}
リポジトリはSQLを直接実行。
class PostgreSQLTodoRepository(private val client: DatabaseClient) : TodoRepository {
override suspend fun fetchAll(userId: UserId): List<Todo> = client
.execute("SELECT * FROM todos WHERE user_id = :userId")
.bind("userId", userId.value)
.map(::readRow)
.all()
.collectList()
.awaitSingle()
override suspend fun find(userId: UserId, todoId: TodoId): Todo? = client
.execute("SELECT * FROM todos WHERE id = :todoId AND user_id = :userId")
.bind("userId", userId.value)
.bind("todoId", todoId.value)
.map(::readRow)
.awaitFirstOrNull()
override suspend fun insert(userId: UserId, title: TodoTitle, content: TodoContent): Todo = client
.execute("INSERT INTO todos (user_id, title, content) VALUES(:userId, :title, :content) RETURNING *")
.bind("userId", userId.value)
.bind("title", title.value)
.bind("content", content.value)
.map(::readRow)
.awaitFirst()
// 以下省略
}
ツッコミどころはあると思うのですが、サンプルなので。
(本当は、Amazon CodeGuru Reviewer にチェックしてもらいたかったのですが、JavaじゃなくてKotlinだったので、一行も認識されませんでした?)
Amazon CodeGuru Profilerの設定方法
1. プロファイリンググループを作成します
コントロールパネル、CodeGuru、プロファイリンググループから、任意の名前のプロファイリンググループを作成します。
2. アプリ実行ロールにプロファイル用のポリシーをアタッチします
ドキュメントを参考に、アプリ実行ロールの以下のポリシーをアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codeguru-profiler:ConfigureAgent",
"codeguru-profiler:PostAgentProfile"
],
"Resource": "arn:aws:codeguru-profiler:<region>:<accountID>:profilingGroup/<profilingGroupName>"
}
]
}
3. アプリにプロファイル起動処理を追加します
build.gradle.tksに以下を追加します。
repositories {
maven {
url = uri("https://d1osg35nybn3tt.cloudfront.net")
}
}
dependencies {
implementation("com.amazonaws:codeguru-profiler-java-agent:1.0.1")
}
アプリのエントリーエントリーポイントに以下を追加します。
fun main(args: Array<String>) {
Profiler.builder()
.profilingGroupName("Test-Reactive-Web")
.build()
.start()
// 以下省略
}
設定は以上になります。
取得したプロファイル結果
アプリを起動し、負荷をかけてプロファイルを取得してみました。 ほとんどWebFluxのチャンネル関係の処理でしたが、フレームを非表示にすることで、自作部分も出力されていることがわかりました。
CPU
レイテンシー
まとめ
Amazon CodeGuru Profilerを設定してみました。
JVMで動いているアプリケーションであれば、設定はとても楽だと思います。別のエージェントサービスを立ち上げる必要もないので、コンテナにもすんなり適用できると思います。
機会があれば、プロダクション環境に適用し、アプリのパフォーマンス改善に利用してみたいと思いました。