project link
github.com/d36choi/awssns-springboot
회사의 신입 개발자 과제 덕분에 알게 된 아마존 클라우드의 서비스인 SNS.
어떤 놈인지는 이전 글을 통해 확인할 수 있다
2021.03.04 - [프로그래밍/backend&devOps] - [AWS] Amazon SNS 란?
2021.03.04 - [프로그래밍/backend&devOps] - [AWS] Amazon SNS 로 구독자 메일 전송을 해보자
이러이러한 ~ 개념들을 가지고, 콘솔을 활용하지 않고 HTTP method 들을 통해
메시지를 구독 및 발행할 수 있는 간단한 SpringBoot API 서버를 만들어보자.
아래 내용을 그대로만 따라한다면 에러없이 잘 진행 될 것!
dependencies
java 11
java springboot 2.4.3
maven
lombok
spring-boot-starter-web
awssdk-sns
진행
1. start.spring.io 에서 lombok 과 spring-web 을 포함해 프로젝트를 생성한다.
aws sdk 에 관련된 항목들은 직접 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation platform('software.amazon.awssdk:bom:2.5.29') // BOM for AWS SDK For Java
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'software.amazon.awssdk:sns' // We only need to get SNS SDK in our case
compile group: 'org.springframework.cloud', name: 'spring-cloud-aws-messaging', version: '2.2.1.RELEASE'
compile group: 'org.springframework.cloud', name: 'spring-cloud-aws-autoconfigure', version: '2.2.1.RELEASE'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2. /resources/application.properties 에 필요한 속성을 포함 시킨다.
sns.topic.arn=<TOPICARN>
aws.accessKey=<ACCESSKEY>
aws.secretKey=<SECRETKEY>
aws.region=<REGION>
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false
속성의 값들 4개는 본인이 만든 SNS 서비스에 맞게 채워넣어야 한다.
3. configuration 패키지를 만들고 안에 AWSConfig 를 생성한다.
main/java/com.example.awssns/configuration/AWSConfig
package com.example.awssns.configuration;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Getter
@Configuration
public class AWSConfig {
@Value("${sns.topic.arn}")
private String snsTopicARN;
@Value("${aws.accessKey}")
private String awsAccessKey;
@Value("${aws.secretKey}")
private String awsSecretKey;
@Value("${aws.region}")
private String awsRegion;
}
@Value 어노테이션을 통해 2에서 채워넣은 속성들의 키값과 일치하는 속성의 값을 해당 변수에 주입시켜준다.
@Configuration 어노테이션을 통해 빈이 생성될 때 해당 객체가 생성되도록 한다.
Region 이 어디인지 모르겠다면 ?
4. service 패키지를 만들고 안에 CredentialService 를 생성한다.
main/java/com.example.awssns/service/CredentialService
package com.example.awssns.service;
import com.example.awssns.configuration.AWSConfig;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
@Service
public class CredentialService {
AWSConfig awsConfig;
public CredentialService(AWSConfig awsConfig) {
this.awsConfig = awsConfig;
}
public AwsCredentialsProvider getAwsCredentials(String accessKeyID, String secretAccessKey) {
AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKeyID, secretAccessKey);
return () -> awsBasicCredentials;
}
public SnsClient getSnsClient() {
return SnsClient.builder()
.credentialsProvider(
getAwsCredentials(awsConfig.getAwsAccessKey(), awsConfig.getAwsSecretKey())
).region(Region.of(awsConfig.getAwsRegion()))
.build();
}
}
AWSConfig 는 생성자 주입을 이용한다.
해당 객체에는 개발자가 application.properties에 직접 입력한 필요값들이 들어있을 것이다.
CredentialService 의 역할
이 클래스의 역할은 "아마존 SNS 서비스로의 접근 허용"이다.
docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html
우리가 웹의 AWS console 을 통해 아마존의 여러 클라우드 서비스를 이용할 때 당연히 로그인이 요구 된다.
어플리케이션으로 서비스를 이용하려면 당연히 권한을 얻어야 할 것이다!
Aws Credential 을 통해, AWS 가 발급한 암호화 키를 제출하는 것으로 서비스에 대한 제어 권한이 생긴다.
SnsClient
SnsClient 는 해당 자격을 주입받고 내가 지정한 리전에 명령을 내려주는 배달부라고 생각하면 편할 것 같다.
앞으로 하는 모든 서비스의 명령은 SnsClient 가 전달해준다.
5. Controller 를 생성한다
com/example/awssns/controller/SnsController.java
package com.example.awssns.controller;
import com.example.awssns.configuration.AWSConfig;
import com.example.awssns.service.CredentialService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.*;
import java.util.Map;
@Slf4j
@RestController
public class SnsController {
AWSConfig awsConfig;
CredentialService credentialService;
public SnsController(AWSConfig awsConfig, CredentialService credentialService) {
this.awsConfig = awsConfig;
this.credentialService = credentialService;
}
// 이 사이에 밑의 각 메서드들을 추가한다
private ResponseStatusException getResponseStatusException(SnsResponse response) {
return new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, response.sdkHttpResponse().statusText().get()
);
}
}
컨트롤러에 필요한 서비스와 설정값들을 생성자 주입한다.
맨 밑의 예외는 없어도 테스트하는데엔 문제가 없다. 만약 정상적으로 전송이 되지 않았을때 예외처리를 하기 위한 메서드다.
토픽 생성을 해보자
@PostMapping("/createTopic")
public ResponseEntity<String> createTopic(@RequestParam final String topicName) {
final CreateTopicRequest createTopicRequest = CreateTopicRequest.builder()
.name(topicName)
.build();
SnsClient snsClient = credentialService.getSnsClient();
final CreateTopicResponse createTopicResponse = snsClient.createTopic(createTopicRequest);
if (!createTopicResponse.sdkHttpResponse().isSuccessful()) {
throw getResponseStatusException(createTopicResponse);
}
log.info("topic name = " + createTopicResponse.topicArn());
log.info("topic list = " + snsClient.listTopics());
snsClient.close();
return new ResponseEntity<>("TOPIC CREATING SUCCESS", HttpStatus.OK);
}
HTTP POST 요청을 리스닝할 메서드다.
topicName 을 인자로 받아서 "어떤 이름의 토픽을 생성할지" 를 정한다.
CreateTopicRequest 객체는 SnsClient 의 createTopic() 에 인자로 주입된다.
createTopic 메서드의 리턴값은 createTopicResponse 고 이 객체의 내부에는 요청의 결과 메타데이터가 담겨져있다.
만약 요청이 성공한다면 예외를 던지지 않고 정상적으로 로그가 뜰 것이다.
postman 을 통해 테스트해보자
토픽 생성 테스트
postman 을 활용해 API 서버가 정상적으로 토픽을 생성하는지 점검해보자.
postman 은 매우 유명한 테스트 도구이기때문에 따로 설명하진 않겠다.
스프링부트 프로젝트를 실행한 뒤 포스트맨에 아래 사진처럼 입력해보고 전송해보자.
choiTopic 이란 이름을 가진 토픽이 생성되었는지 AWS SNS console 을 통해 확인해보자
아주 기쁘게도, 토픽이 잘 추가되었다. ^^
스프링 콘솔에도 토픽 이름과 토픽 리스트에 대한 log 가 뜰 것이다.
구독 신청을 해보자
토픽을 만들었으니, 토픽을 구독하는 구독자들이 있어야 할 것이다.
여러가지 엔드포인트 중에, Https endpoint 를 지정해, http api webhook (webhook.site/) 를 통해 테스트를 진행할 것이다.
해당 테스트 방법은
2021.03.04 - [프로그래밍/backend&devOps] - [AWS] Amazon SNS 로 구독자 메일 전송을 해보자
이 곳에서 확인 할 수 있다. 이 방법 그대로 이번엔 스프링부트 api 를 통해 구독을 진행하는 것이다.
@PostMapping("/subscribe")
public ResponseEntity<String> subscribe(@RequestParam final String endpoint, @RequestParam final String topicArn) {
final SubscribeRequest subscribeRequest = SubscribeRequest.builder()
.protocol("https")
.topicArn(topicArn)
.endpoint(endpoint)
.build();
SnsClient snsClient = credentialService.getSnsClient();
final SubscribeResponse subscribeResponse = snsClient.subscribe(subscribeRequest);
if (!subscribeResponse.sdkHttpResponse().isSuccessful()) {
throw getResponseStatusException(subscribeResponse);
}
log.info("topicARN to subscribe = " + subscribeResponse.subscriptionArn());
log.info("subscription list = " + snsClient.listSubscriptions());
snsClient.close();
return new ResponseEntity<>("TOPIC SUBSCRIBE SUCCESS", HttpStatus.OK);
}
인자로, 어떤 토픽을 구독할지 (topicArn) 와 어떤 endpoint 주소가 구독할 것인지 (구독을 할 구독자의 주소) 를 알려줘야 한다.
위에 토픽 구독처럼 이번엔 SubscribeRequest 를 생성한다.
protocol 은 어떤 프로토콜로 구독할지를 지정해준다. email, https 등등이 가능하다. 여긴 https 를 사용한다.
그 뒤 어떤 토픽인지, 구독자 주소는 무엇인지를 넣어준다.
똑같이 SnsClient 를 통해 구독을 amazon sns 서비스에 요청한다.
구독 요청 테스트
postman을 통해 이런 식으로 토픽 이름과 구독자 주소를 지정해주고 요청을 보내보자.
위 로그대로 구독자는 구독 요청에 대한 "구독확인" 을 해야만 최종적으로 메시지를 수신할 수 있게 된다.
webhook 의 엔드포인트를 통해 구독확인 메시지가 왔는지 보자.
구독확인 메시지가 왔으니 "SubscribeURL" 항목의 값을 복붙해 들어가보자.
그러면 최종적으로 구독이 완료 된다.
메시지를 보내보자
자, 이제 토픽도 만들고, 구독자도 생겼으니 메시지를 보내보자.
@PostMapping("/publish")
public String publish(@RequestParam String topicArn, @RequestBody Map<String, Object> message) {
SnsClient snsClient = credentialService.getSnsClient();
final PublishRequest publishRequest = PublishRequest.builder()
.topicArn(topicArn)
.subject("HTTP ENDPOINT TEST MESSAGE")
.message(message.toString())
.build();
PublishResponse publishResponse = snsClient.publish(publishRequest);
log.info("message status:" + publishResponse.sdkHttpResponse().statusCode());
snsClient.close();
return "sent MSG ID = " + publishResponse.messageId();
}
@RequestBody 는 Body 형태의 JSON Message 를 포함해야한다는 걸 뜻한다.
그 외에는 위와 같은 형식으로 snsClient 를 통해 publish 명령을 요청하는 것으로 요약 가능하다.
subject 는 메시지의 제목, message 는 메시지의 내용 이다.
메시지 발행 테스트
포스트맨을 통해 필요한 parameter 와 body 를 추가한다.
Params 에 위에 했던것처럼 topicArn 을 입력하고 전송하면 위처럼 정상적으로 보낸 메시지에 대한 ID값이 응답이 온다.
내가 발행한 메시지가 바로 구독자에게 푸쉬되어 전송된 것 또한 webhook.site 에 접속해서 확인.
내가 쓴 메시지 내용대로 구독자들에게 메시지가 무사히 도착~
끝내며
이렇게, 단순히 amazon console 을 통해서 GUI 환경에서 메시지를 발행하지 않고도 서버를 따로 둬서
토픽의 생성부터 구독, 메시지 전송까지 할 수 있음을 알았다.
이런 aws sdk 기술을 이용한다면, 문제가 발생할 때 비동기적으로 메시지를 각 위치에 뿌릴 수 있는
서비스를 만들 수 있을 것이라는 확신이 생겼다 ^_^
혹시 에러가 나거나 안되는 부분이 있다면 댓글로 물어봐주세요!
'프로그래밍 > backend&devOps' 카테고리의 다른 글
[AWS] amazon lambda + API Gateway 로 SNS 메시지 보내기 - 1 (2) | 2021.04.12 |
---|---|
[AWS] EC2 amazon linux2 에 mongodb (몽고디비) 설치 (0) | 2021.04.02 |
[AWS] Amazon SNS 로 구독자 메일 전송을 해보자 (0) | 2021.03.04 |
[AWS] Amazon SNS 란? (0) | 2021.03.04 |
[spring] dispatcher servlet 이 뭐죠? (spring MVC) (0) | 2021.03.04 |