SpringBoot + Kotlin で @SqsListener を用いてSQSのメッセージを処理する (v3)

2023/09/19に公開

背景

AWSのSQSにメッセージをキューイングしておき、SpringBootのアプリケーションで処理したいというときに Spring Cloud AWS の @SqsListener というアノテーションが利用できる
これをとりあえず手元で動かすための方法を残す

以前の記事 ではSpringBoot, Spring Cloud AWS 共にversion 2系で試したが、2023年5月にSpring Cloud AWS の v3.0.0 がリリースされており、アップデートが結構あったので現時点での最新バージョンでやり直してみた

概要

サンプルコードはこちら

v2系との差分

  • v3 で Spring Cloud AWS の SQS に関するパッケージが messaging から sqs に変更になっており、 @SqsListener のパッケージも以下のように変更になっている
    io.awspring.cloud.messaging.listener.annotation.SqsListener
    io.awspring.cloud.sqs.annotation.SqsListener
  • パッケージ名の変更に伴い、application.properties で指定する property名も変更に
    cloud.aws.sqs.endpoint -> spring.cloud.aws.sqs.endpoint
  • spring cloud の starter が変更
    • v2系では spring-cloud-starter-aws と spring-cloud-aws-messaging を依存関係に追加していたが、v3系 では spring-cloud-aws-starter-sqs だけ入れればよく、 spring-cloud-aws-dependencies の BOM で version を管理できるようになった
  • @SqsListener アノテーションのプロパティが変更
    • v2系では@SqsListenerのプロパティは deletionPolicy くらいしかなかったが、 factory, id, maxConcurrentMessages, pollTimeoutSeconds, messageVisibilitySeconds などが設定できるようになった(参考)
  • message を簡単に string 以外の型で受け取れるようになった
    • v2系では、自前で MappingJackson2MessageConverter を用意する必要があったが、フレームワーク側でやってくれるようになり、引数に data class を渡すだけで jackson でマッピングしてくれるようになった

準備

localstack の起動

docker compose で起動

docker compose up

適当なコマンドを叩いて、うまく動いていれば OK

例) 接続しているアカウントの確認コマンド

awslocal sts get-caller-identity

↓ のような結果が取得できれば OK

{
    "UserId": "AKIAIOSFODNN7EXAMPLE",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:root"
}

awslocal コマンドを利用した SQS の操作

awslocal sqs コマンドを利用する

  • help の確認
awslocal sqs help
  • キューの作成
awslocal sqs create-queue --queue-name 'sample-queue' --region ap-northeast-1
  • キューの一覧を確認
awslocal sqs list-queues --region ap-northeast-1
  • キューにメッセージを送信
awslocal sqs send-message --queue-url http://localhost:4566/000000000000/sample-queue --message-body '{"message": "hoge"}' --region ap-northeast-1
  • メッセージを確認
awslocal sqs receive-message --queue-url http://localhost:4566/000000000000/sample-queue --region ap-northeast-1

message-apiの実装

spring intializr から、以下の設定で Project を生成

Project: Gradle - Kotlin
Language: Kotlin
Spring Boot: 3.1.3
Project Metadata
  Group: com.example
  Artifact: message-api
  Name: message-api
  Description: Demo project for Spring Boot
  Package name: com.example.message.api
  Packaging: Jar
  Java: 17
Dependencies: No dependency selected

以下の手順で message-api を実装する

1: 必要なライブラリの追加
build.gradle.kts に以下を追加

dependencyManagement {
	imports {
		mavenBom("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2")
	}
}

dependencies {
  ...(略)

	implementation("io.awspring.cloud:spring-cloud-aws-starter-sqs")
}

2: ローカルへの接続設定
application.properties に以下を追加

spring.cloud.aws.sqs.endpoint=http://localhost:4566
spring.cloud.aws.sqs.region=ap-northeast-1

3: handler の実装
com.example.message.api 配下に新規で handler という package を追加し、SampleSQSMeesageHandler.kt を作成

package com.example.message.api.handler

import io.awspring.cloud.sqs.annotation.SqsListener
import org.springframework.stereotype.Component

@Component
class SampleSQSMessageHandler {

    // SqsListener のアノテーションをつけることで、SQSをポーリングしてメッセージを取得するようになる
    // https://spring.pleiades.io/spring-cloud-aws/docs/3.0.2/reference/html/index.html#sqslistener-annotation
    @SqsListener("sample-queue")
    fun handle(message: String) {
        println(message)
    }
}

これだけで OK

message-api の動作確認

前提: message-api の実装 で sample-queue を作成していること

1: application を起動
Intellij から起動
or

cd message-api
./gradlew bootRun

すでに message-api の実装 でメッセージを送信していた場合は、コンソールに以下のように出力されるはず

{"message": "hoge"}

2: SQS にメッセージを送信

awslocal sqs send-message --queue-url http://localhost:4566/000000000000/sample-queue --message-body '{"message": "fuga"}'

3: コンソール出力の確認
message-api で処理されて以下のように出力される

{"message": "fuga"}

message-api の追加実装

先の参考実装では、メッセージを String で受け取っているが、
String では扱いにくいため json メッセージを kotlin の data class で受け取るようにする

以下の data class を追加

data class SampleData(
  @JsonProperty("message")
  val message: String
)

handler の method の引数の型を String -> SampleData に変更

- fun handle(data: String) {
+ fun handle(data: SampleData) {

これで先ほど同様に動作確認をすると、 json を data class のオブジェクトとして扱えることが確認できる

まとめ

しれっと破壊的変更が入っているのは辛いが、v3系で色々パワーアップされており喜ばしい

Discussion