만족은 하되 안주하지는 말자

기록해야 기억한다

프로그래밍/backend&devOps

[AWS] amazon SNS + Spring Boot 이용한 메시지 서비스 만들기

D36choi 2021. 3. 10. 17:19
728x90

 

project link

github.com/d36choi/awssns-springboot

 

d36choi/awssns-springboot

Contribute to d36choi/awssns-springboot development by creating an account on GitHub.

github.com

회사의 신입 개발자 과제 덕분에 알게 된 아마존 클라우드의 서비스인 SNS.

어떤 놈인지는 이전 글을 통해 확인할 수 있다

2021.03.04 - [프로그래밍/backend&devOps] - [AWS] Amazon SNS 란?

 

[AWS] Amazon SNS 란?

Amazon SNS 란? Amazon Simple Notification Service (Amazon SNS) is a managed service that provides message delivery from publishers to subscribers (also known as producers and consumers). Publishe..

choichumji.tistory.com

2021.03.04 - [프로그래밍/backend&devOps] - [AWS] Amazon SNS 로 구독자 메일 전송을 해보자

 

[AWS] Amazon SNS 로 구독자 메일 전송을 해보자

Amazon SNS 서비스를 처음 시작해보는 사람에게 적합한 튜토리얼입니다. aws 서비스 리전은 서울로 설정되어있습니다. Amazon SNS 가 뭐하는 서비스인지는 아래 글에 써놓았습니다. 2021/03/04 - [프로그

choichumji.tistory.com

 

이러이러한 ~ 개념들을 가지고, 콘솔을 활용하지 않고 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 이 어디인지 모르겠다면 ?

amazon console 의 오른쪽 위에서 본인 리전의 영어이름을 입력하면 됨

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

 

Using credentials - AWS SDK for Java

For security, use IAM account credentials instead of the AWS account credentials when accessing AWS. For more information, see AWS Security Credentials in the Amazon Web Services General Reference.

docs.aws.amazon.com

우리가 웹의 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 로 구독자 메일 전송을 해보자

 

[AWS] Amazon SNS 로 구독자 메일 전송을 해보자

Amazon SNS 서비스를 처음 시작해보는 사람에게 적합한 튜토리얼입니다. aws 서비스 리전은 서울로 설정되어있습니다. Amazon SNS 가 뭐하는 서비스인지는 아래 글에 써놓았습니다. 2021/03/04 - [프로그

choichumji.tistory.com

이 곳에서 확인 할 수 있다. 이 방법 그대로 이번엔 스프링부트 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: params 항목에 key : value 를 이 형식으로 입력하자

postman을 통해 이런 식으로 토픽 이름과 구독자 주소를 지정해주고 요청을 보내보자.

 

문제 없이 성공
우리가 작성한 코드대로 로그 또한 콘솔에 잘 출력된다

위 로그대로 구독자는 구독 요청에 대한 "구독확인" 을 해야만 최종적으로 메시지를 수신할 수 있게 된다.

webhook 의 엔드포인트를 통해 구독확인 메시지가 왔는지 보자.

 

SubscriptionConfirmation Type의 메시지가 도착함을 확인 가능하다

구독확인 메시지가 왔으니 "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 를 추가한다.

구독자에게 보내려는 메시지 내용을 http post 명령에서 body 로 전달해준다

Params 에 위에 했던것처럼 topicArn 을 입력하고 전송하면 위처럼 정상적으로 보낸 메시지에 대한 ID값이 응답이 온다.

choiTopic 에 구독한 구독자에게 메시지가 푸쉬된 모습.

내가 발행한 메시지가 바로 구독자에게 푸쉬되어 전송된 것 또한 webhook.site 에 접속해서 확인.

내가 쓴 메시지 내용대로 구독자들에게 메시지가 무사히 도착~

 

 

끝내며

 

이렇게, 단순히 amazon console 을 통해서 GUI 환경에서 메시지를 발행하지 않고도 서버를 따로 둬서 

토픽의 생성부터 구독, 메시지 전송까지 할 수 있음을 알았다.

 

이런 aws sdk 기술을 이용한다면, 문제가 발생할 때 비동기적으로 메시지를 각 위치에 뿌릴 수 있는

서비스를 만들 수 있을 것이라는 확신이 생겼다 ^_^

 

혹시 에러가 나거나 안되는 부분이 있다면 댓글로 물어봐주세요!