JavaプロジェクトからSQSにキューを送信してlambdaをキックする

# 前提
業務でこんな処理を実装する気がするので、簡単に予習してみます。
今回は動くか検証のみなので、ローカルで起動したJavaアプリケーション（Spring Boot）からSQSにメッセージを送信してみます。

本当はlambdaからrdsへ登録・更新する処理を実装したかったのですが、それはまた別の機会に。（なのでSQSやlambdaの名前がmergeXXXXみたいになってます。）

![lambda-sqs.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/3a229f2f-f2b9-d425-1c1e-6ec78dd46b8e.png)

# 環境
- macOS: 12.6
- IntelliJ IDEA: 2022.2.3 (Community Edition)
- Gradle: 7.5
- Java: temurin 17.0.3

# 手順

## プロジェクトの作成

#### spring initializrでプロジェクト作成
[https://start.spring.io/](https://start.spring.io/)でプロジェクトの雛形を作成します。
以下の画像のような構成にしています。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/c2e5604c-71a3-5af3-dea0-c307670cd33e.png">

GENERATEボタンを押してダウンロードしてきます。それをIDEツールで開きます。

#### build.gradleの設定
`com.amazonaws:aws-java-sdk-bom`と`com.amazonaws:aws-java-sdk-sqs`を設定します。
この時点ではどちらも1.12.320が最新でした。

```gradle:build.gradle
plugins {
	id 'org.springframework.boot' version '2.7.5-SNAPSHOT'
	id 'io.spring.dependency-management' version '1.0.14.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
	maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'com.amazonaws:aws-java-sdk-bom:1.12.320'
	implementation 'com.amazonaws:aws-java-sdk-sqs:1.12.320'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
```

## AWS側の設定

#### IAMユーザーの作成
事前にJavaからアクセスするためのIAMユーザーを作成し、アクセスキーとシークレットキーをダウンロードしておきます。
そのIAMユーザーを使用してコンロールでSQS等を作成します。

#### SQSのキューを作成
コンソールでSQSのキューを作成します
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/fe5c311d-d5d2-9077-65ea-c8da1384a74b.png">
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/7663fdfc-7dec-45bc-23f0-77f2b40e4916.png">

#### lambdaを作成
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/30d1c6db-925c-0f01-8c5e-5607cc2638c5.png">
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/f92d2a1e-aabe-7d80-b418-582c7cc4447e.png">

#### IAMロールを作成する
lambdaからSQSを読み取りするのでIAMロールを作成します。
こちらを参考にさせていただきました。
https://www.stsd.co.jp/dev-blog/send_and_receive_amazon_sqs_messages_from_java.html#:~:text=SQS%E3%81%AB%E3%81%AFJava%E3%81%A7,%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%80%8D%E3%82%92%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

- ロール名：for-sqs
- ポリシー名：sqs-policy
- ポリシー

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "sqs:DeleteMessage",
                "sqs:ReceiveMessage",
                "sqs:GetQueueAttributes"
            ],
            "Resource": "*"
        }
    ]
}
```

またロールにAWSLambdaBasicExecutionRoleをアタッチしておくと、CloudWatchログにログを書き込めます。

#### lambdaにIAMロールの設定
コンロールのlambdaのページを開き、設定タブ > アクセス権限 > 実行ロール　> 編集ボタンで設定します。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/9381367d-9a6d-807a-d60e-767a49a9310f.png">

#### SQSでLambdaトリガーを設定
SQSのページで作成したキューを選択し、Lambdaトリガータブからlambdaを設定します。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/223d2b08-00ab-8432-b331-d2fc2f9b065b.png">


これでaws側の設定は完了です。

## 実装

#### SqsClient
こちらを参考にしました。
https://docs.aws.amazon.com/ja_jp/sdk-for-java/v1/developer-guide/examples-sqs-messages.html

```Java:SqsClient.java
package com.example.lambdasqssample.aws;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.AmazonSQSException;
import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import org.springframework.stereotype.Component;

@Component
public class SqsClient {

    private static final String QUEUE_NAME = "merge-data-sqs";

    public String getQueueUrl() {
        var sqs = getSqs();
        var create_request = new CreateQueueRequest(QUEUE_NAME)
                .addAttributesEntry("DelaySeconds", "60")
                .addAttributesEntry("MessageRetentionPeriod", "86400");

        try {
            sqs.createQueue(create_request);
        } catch (AmazonSQSException e) {
            if (!e.getErrorCode().equals("QueueAlreadyExists")) {
                throw e;
            }
        }
        return sqs.getQueueUrl(QUEUE_NAME).getQueueUrl();
    }

    public void sendMessage(String queueUrl) {
        var sqs = getSqs();
        var send_msg_request = new SendMessageRequest()
                .withQueueUrl(queueUrl)
                .withMessageBody("hello world")
                .withDelaySeconds(5);
        sqs.sendMessage(send_msg_request);
    }

    private AmazonSQS getSqs() {
        return AmazonSQSClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(Credentials.get()))
                .withRegion(Regions.AP_NORTHEAST_1.getName())
                .build();
    }
}
```

```Java:Credentials.java
package com.example.lambdasqssample.aws;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Credentials {

    private static String ACCESS_KEY;

    private static String SECRET_KEY;

    @Value("${aws.access-key}")
    public void setAccessKey(String accessKey) {
        this.ACCESS_KEY = accessKey;
    }

    @Value("${aws.secret-key}")
    public void setSecretKey(String secretKey) {
        this.SECRET_KEY = secretKey;
    }

    public static AWSCredentials get() {
        return new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
    }
}
```
ACCESS_KEYとSECRET_KEYはapplication.ymlで設定しておきます。

#### 実行クラス
QueueのUrlを受け取り、メッセージを送信します。
```java:SampleController.java
package com.example.lambdasqssample.Controller;

import com.example.lambdasqssample.aws.SqsClient;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/sample")
@RequiredArgsConstructor
public class SampleController {

    private final SqsClient sqsClient;

    @GetMapping("/result")
    public boolean getResult() {
        var queue = sqsClient.getQueueUrl();
        sqsClient.sendMessage(queue);
        return true;
    }

}
```
これで実装は完了です。

## 実行
アプリケーションをビルド・実行します。
今回はpostmanを使用して`http://localhost:8080/api/v1/sample/result`にGETします。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/55181993-ed75-b898-e461-6f609eeaee42.png">

CloudWatchログを見ると、アクセスするたびに実行されていることがわかります。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/eb9aaece-5e01-4e88-8db3-7ee9986f0afd.png">

# さいごに
まだlambdaで具体的な処理を実行していないですが、とりあえず動くことを確認できた第一歩でした。