728x90
Spring Boot 3 버전과 SNS, SQS 연동하기
지난 포스팅에서는 SNS-SQS를 연동할 때 Amazon console을 이용했다. 이번 글에서는 Spring Boot 3 버전에서 SNS로 이벤트를 발행하고 SQSListener로 메시지를 받아보자!
1. SpringBoot 설정 (build.gradle, application.yml)
1. build.gradle 코드 추가
- 스프링 부트 버전 3과 호환되는 의존성으로 적어준다.
// AWS
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs'
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sns'
3. application.yml 설정 추가
- access-key: IAM 계정의 Access key ID
- secret-key: IAM 계정의 Secret access key
- region.static: SNS, SQS가 생성된 리전
- sns.topics는 커스텀 설정이다.
- 안에는 내가 생성한 주제들을 <주제명>:<주제 ARN>같이 key:value로 작성한다.
- sqs:
- 안에는 내가 구독하는 구독 정보를 <sqs name>:<sqs name>같이 key:value로 작성한다.
spring:
cloud:
aws:
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
region:
static: ${REGION}
sns:
topics:
<topic name1>: <SNS topic ARN>
<topic name2>: <SNS topic ARN>
sqs:
<SQS name>: <SQS name>
※ SNS는 ARN을 작성해주는데 SQS는 name만 작성해도 된다.
@SqsListener 어노테이션에 value 속성을 사용해서 spring.cloud.aws.sqs.name 같은 프로퍼티 키를 넣으면, 스프링 부트의 내부 로직이 해당 프로퍼티 키에 해당하는 값을 찾아서 사용한다. 그래서 프로퍼티 파일에 정의된 큐 이름을 @SqsListener가 참조할 수 있게 되는 거다.
url은 SQS 큐의 전체 URL이다. AWS에서 SQS 서비스를 사용할 때, 각 큐는 고유한 URL을 가지고 있다. 이 URL은 큐에 메시지를 보내거나 받을 때 사용된다. name만으로도 큐를 찾을 수 있지만, 여러 AWS 계정이나 리전에 걸쳐 동일한 이름의 큐가 있을 수 있으므로, url을 명시하는 것이 더 명확하고 안전한 방법이 될 수 있다.
하지만, 대부분의 경우에는 큐 이름만으로 충분하고, 스프링 클라우드 AWS는 내부적으로 큐 이름을 사용하여 알맞은 URL을 찾는 로직을 가지고 있다. 그래서 url을 명시하지 않아도 동작하는 경우가 많다. 하지만 명시적으로 큐 URL을 설정하면, 해당 URL의 큐에 직접 연결되므로, 이름이 같은 다른 큐와 혼동될 여지가 없어져서 더 확실하게 큐를 지정할 수 있다.
- 이 설정을 통해 AWS 자격 증명, 리전, SNS 토픽 ARN, SQS 큐 이름 및 URL을 지정할 수 있다. 이렇게 설정 파일에 정보를 넣으면 코드 내에서 직접 자격 증명을 관리할 필요 없이 Spring Cloud AWS가 자동으로 AWS 서비스와의 연동을 처리해 준다.
- 만약 본인이 Spring Cloud AWS를 사용해서 SNS나 SQS와 통합하려고 한다면, 이 설정을 사용하는 것이 좋다. 이 설정을 사용하면 코드가 더 깔끔해지고, 설정 변경이 필요할 때 코드를 수정하지 않고 설정 파일만 업데이트하면 되니까 유지보수가 편리해진다.
- 하지만 이 설정을 꼭 사용해야 하는 것은 아니다. AWS SDK를 직접 사용해서 서비스를 구성하고 싶다면, SDK의 API를 사용하여 자격 증명과 리전 설정을 코드 내에서 직접 할 수 있다. 그러나 이 방법은 설정을 코드에 직접 하드코딩하는 것이 될 수 있으므로, 일반적으로는 권장되지 않는다.
2. SNS 클라이언트 Bean 등록
1. Config 클래스 작성
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
@Getter
@Configuration
public class AwsSnsConfig {
@Value("${spring.cloud.aws.credentials.access-key}")
private String awsAccessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String awsSecretKey;
@Value("${spring.cloud.aws.region.static}")
private String awsRegion;
@Value("${spring.cloud.aws.sns.topics.nickname-change}")
private String snsTopicARN;
@Bean
public SnsClient getSnsClient() {
return SnsClient.builder()
.region(Region.of(awsRegion)) // 리전 설정 추가
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(awsAccessKey, awsSecretKey)))
.build();
}
}
- 이 코드는 Spring Boot 애플리케이션에서 AWS SNS(Simple Notification Service) 클라이언트를 설정하기 위한 구성 클래스이다. @Configuration 어노테이션은 이 클래스가 스프링의 구성(설정) 클래스임을 나타낸다. @Value 어노테이션은 application.properties 또는 application.yml 파일에서 설정한 값을 필드에 주입한다.
- AwsSnsConfig 클래스는 AWS SNS 클라이언트를 빈으로 등록하기 위한 메서드인 getSnsClient()를 포함하고 있다. 이 메서드는 SnsClient를 생성하고, AWS 자격 증명과 리전 정보를 설정한다.
클래스를 정리한 내용은 다음과 같다.
- AwsSnsConfig
- AWS SNS 클라이언트를 설정하는 스프링 구성 클래스이다.
- awsAccessKey, awsSecretKey, awsRegion, snsTopicARN
- AWS 자격 증명과 리전, SNS 토픽 ARN을 저장하는 필드다. 이 값들은 외부 설정 파일에서 주입된다.
- getSnsClient()
- SnsClient 인스턴스를 생성하고 스프링 빈으로 등록하는 메서드이다.
- 이 메서드는 StaticCredentialsProvider를 사용하여 자격 증명을 설정하고, Region.of() 메서드를 사용하여 AWS 리전을 설정한다.
3. SQS 클라이언트 Bean 등록
1. Config 클래스 작성
import io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
@Getter
@Configuration
public class AwsSqsConfig {
@Value("${spring.cloud.aws.credentials.access-key}")
private String awsAccessKey;
@Value("${spring.cloud.aws.credentials.secret-key}")
private String awsSecretKey;
@Value("${spring.cloud.aws.region.static}")
private String awsRegion;
// SQS Client 세팅
@Bean
public SqsAsyncClient sqsAsyncClient() {
return SqsAsyncClient.builder()
.credentialsProvider(() -> new AwsCredentials() {
@Override
public String accessKeyId() {
return awsAccessKey;
}
@Override
public String secretAccessKey() {
return awsSecretKey;
}
})
.region(Region.of(awsRegion))
.build();
}
// Listener Factory 설정 (Listener쪽에서만 설정하면 됨)
@Bean
public SqsMessageListenerContainerFactory<Object> defaultSqsListenerContainerFactory() {
return SqsMessageListenerContainerFactory
.builder()
.sqsAsyncClient(sqsAsyncClient())
.build();
}
// // 메세지 발송을 위한 SQS 템플릿 설정 (Sender쪽에서만 설정하면 됨)
// @Bean
// public SqsTemplate sqsTemplate() {
// return SqsTemplate.newTemplate(sqsAsyncClient());
// }
}
- 이 코드는 AWS SQS(Simple Queue Service)를 비동기적으로 사용하기 위한 설정을 포함하고 있다.
- SqsAsyncClient는 AWS SQS와 비동기적으로 통신하기 위한 클라이언트를 생성하는 데 사용된다.
- SqsMessageListenerContainerFactory와 SqsTemplate은 메시지를 수신하고 발송하는 데 필요한 추가 설정을 제공한다.
클래스를 정리한 내용은 다음과 같다.
- SqsAsyncClient
- AWS SQS 서비스와 비동기적으로 통신할 수 있는 클라이언트를 생성한다.
- credentialsProvider() 메서드를 통해 AWS 자격 증명을 설정한다. 여기서는 익명 클래스를 사용해서 AwsCredentials 인터페이스를 구현하고 있다.
- region() 메서드를 사용하여 AWS 리전을 설정한다.
- SqsMessageListenerContainerFactory
- SQS 메시지 리스너를 위한 컨테이너 팩토리를 생성한다.
- 이 팩토리는 메시지 리스너가 메시지를 수신할 때 사용되는 컨테이너를 설정하는 데 사용된다.
- sqsAsyncClient() 메서드를 호출하여 생성된 SqsAsyncClient 인스턴스를 사용한다.
- SqsTemplate
- SQS 메시지를 보내기 위한 템플릿을 생성해. 이 템플릿은 메시지를 SQS 큐에 보낼 때 사용된다.
- SqsTemplate.newTemplate() 메서드를 호출하여 SqsAsyncClient 인스턴스를 기반으로 새 템플릿을 생성한다.
- "Listener쪽에서만 설정하면 됨"과 "Sender쪽에서만 설정하라는 것"은 아마도 애플리케이션의 구조에 따라 다른 부분에서 다르게 설정을 해야 한다는 의미다.
- 예를 들어, 메시지를 수신하는 서비스(Listener)는 SqsMessageListenerContainerFactory를 설정해야 하고, 메시지를 보내는 서비스(Sender)는 SqsTemplate를 설정해야 한다.
- 이렇게 분리하는 이유는 각각의 서비스가 필요로 하는 구성 요소만 설정하기 위해서다. 메시지를 수신하는 서비스는 리스너 관련 설정이 필요하고, 메시지를 보내는 서비스는 발송 관련 설정이 필요하기 때문이다.
- 이렇게 설정을 분리함으로써 각 서비스의 책임을 명확히 하고, 필요하지 않은 설정을 로드하지 않아서 애플리케이션의 성능을 최적화할 수 있다.
4. SNS 서비스 클래스 작성
- 이 SnsService 클래스는 AWS Simple Notificaton Service(SNS)에 메시지를 발행하는 기능을 제공한다.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.recipia.member.config.aws.AwsSnsConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.PublishRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Map;
@RequiredArgsConstructor
@Service
public class SnsService {
private final SnsClient snsClient;
private final AwsSnsConfig awsSnsConfig;
private final ObjectMapper objectMapper;
public PublishResponse publishNicknameToTopic(Map<String, Object> messageMap) {
String messageJson = convertMapToJson(messageMap);
PublishRequest publishRequest = PublishRequest.builder()
.message(messageJson)
.topicArn(awsSnsConfig.getSnsTopicNicknameChangeARN())
.build();
return snsClient.publish(publishRequest);
}
private String convertMapToJson(Map<String, Object> messageMap) {
try {
return objectMapper.writeValueAsString(messageMap);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error converting message map to JSON", e);
}
}
}
- publishToTopic 메서드는 외부에서 전달받은 메시지 데이터를 JSON 문자열로 변환한 뒤 이를 AWS SNS 토픽으로 발행한다.
- 해당 메서드는 먼저 convertMapToJson 메서드를 호출하여 맵 형태의 메시지를 JSON 문자열로 변환한다. 이렇게 변환된 문자열은 PublishRequest를 생성하는 데 사용되며, 이는 snsClient의 publish 메서드에 전달되어 실제 메시지 발행을 수행한다.
- awsSnsConfig에서 getSnsTopicARN 메서드를 호출함으로써, 발행될 SNS 토픽의 ARN(Amazon Resource Name)을 가져온다.
- convertMapToJson 메서드는 받은 맵을 JSON으로 변환한다. 이 과정에서 ObjectMapper의 writeValueAsString 메서드를 사용하여, 맵을 문자열 형태의 JSON으로 변환하고 있다.
- 변환 과정 중에 JsonProcessingException 예외가 발생할 수 있는데, 이 경우 메서드는 RuntimeException을 던짐으로써 호출자에게 문제를 알린다.
※ ObjectMapper로 데이터를 JSON으로 변환하는 이유는 SNS가 메시지를 텍스트 형태로 받기 때문이다.
- ObjectMapper.writeValueAsString() 메서드는 JSON 형식의 문자열로 변환을 보장한다. JSON은 키-값 쌍으로 구성된 표준 데이터 포맷이기 때문에, 수신자가 이 데이터를 더 쉽게 파싱하고 사용할 수 있다.
- 타입 안정성: toString() 메서드는 맵의 모든 키와 값을 문자열로 변환하지만, 이 과정에서 데이터 타입에 대한 정보가 손실된다. JSON을 사용하면, 각 값의 타입(예: 숫자, 불리언, 배열 등)이 유지되어 수신자가 타입에 맞게 데이터를 처리할 수 있다.
- 에러 핸들링: ObjectMapper를 사용하면 JSON 변환 과정에서 발생할 수 있는 오류를 적절히 처리할 수 있다. 예외가 발생하면 이를 캐치하고 적절한 예외 처리를 할 수 있어서, 오류가 발생했을 때 더 나은 대응이 가능하다.
- 확장성과 유지보수: JSON 형식은 표준화되어 있고 널리 사용되기 때문에, 시스템이 확장되거나 다른 시스템과 통합될 때 호환성 문제를 최소화할 수 있다. 또한, 코드를 읽고 유지보수하는 개발자에게 JSON 문자열이 무엇을 의미하는지 더 명확하게 전달할 수 있디.
- 표준화된 포맷: JSON은 웹 API와의 통신에서 표준 데이터 포맷으로 자리 잡았다. 대부분의 프로그래밍 언어와 플랫폼에서 JSON을 쉽게 처리할 수 있는 라이브러리를 제공하고 있다.
- toString() 메서드를 사용하는 것은 빠르고 간단하지만, 실제 프로덕션 환경에서는 JSON 형식의 데이터를 사용하는 것이 더 많은 이점을 제공한다.
5. 컨트롤러로 메시지 SNS로 발행하기
- 코드 사용 방법
- 메시지 데이터 준비: 발행하고자 하는 메시지의 내용을 Map<String, Object> 형태로 준비한다. 이 맵에는 메시지의 각 부분을 키-값 쌍으로 넣으면 된다.
- 서비스 호출: SnsService의 publishToSns 메서드를 호출하면서, 준비한 메시지 데이터를 인자로 넘겨준다.
- 결과 처리: publishToSns 메서드는 PublishResponse 객체를 반환해. 이 객체를 사용해서 발행 요청의 결과를 처리할 수 있다.
import aws.sns.SnsService;
import lombok.RequiredArgsConstructor;
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.RestController;
import software.amazon.awssdk.services.sns.model.PublishResponse;
import java.util.Map;
@RequiredArgsConstructor
@RestController
public class NotificationController {
private final SnsService snsService;
@PostMapping("/notify")
public ResponseEntity<?> sendNotification(@RequestBody Map<String, Object> notificationData) {
// 1. SnsService를 사용해서 메시지 발행
PublishResponse response = snsService.publishToSns(notificationData);
// 2. 발행 결과를 HTTP 응답으로 반환
return ResponseEntity.ok().body(response.messageId());
}
}
7. Postman으로 테스트하기
1. Postman을 실행시키고 요청해 보자.
- 맵핑 방법을 Post로 설정해 주고 Headers로 가서 Content-Type을 application/json으로 변경한다.
- Body로 이동해서 아래와 같이 "raw" 선택 후 "JSON" 타입으로 데이터 타입을 설정하고 데이터를 요청한다.
- 성공하면 응답 Body 영역에서 발행에 성공한 Message ID값이 출력된다.
8. SQS 리스너 설정에서 메시지 받아서 메서드 동작시키기
1. SnsListenerService 클래스를 생성하자.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.awspring.cloud.sqs.annotation.SqsListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
@Service
public class SqsListenerService {
private final ObjectMapper objectMapper;
@SqsListener(value = "${spring.cloud.aws.sqs.sqs-name}")
public void receiveMessage(String messageJson) {
try {
JsonNode messageNode = objectMapper.readTree(messageJson);
String topicArn = messageNode.get("TopicArn").asText();
String messageContent = messageNode.get("Message").asText();
// Assuming the "Message" is also a JSON string, we parse it to print as JSON object
JsonNode message = objectMapper.readTree(messageContent);
log.info("Topic ARN: {}", topicArn);
log.info("Message: {}", message.toString());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Error parsing message JSON", e);
}
}
}
- 이 SqsListenerService 클래스는 AWS Simple Queue Service(SQS)에서 메시지를 비동기적으로 수신하기 위한 리스너 서비스이다.
- 이 클래스는 Spring의 @SqsListener 애노테이션을 사용하여 SQS로부터 메시지를 받는 방법을 정의하고 있다.
- Jackson 라이브러리의 ObjectMapper를 사용하여 JSON 형태의 메시지를 처리하고 있다.
- @SqsListener(value = "${spring.cloud.aws.sqs.sqs-name}")
- 이 메서드가 SQS 리스너임을 나타내며, spring.cloud.aws.sqs.sqs-name 프로퍼티에 설정된 SQS 큐에서 메시지를 수신할 것임을 나타낸다.
- receiveMessage 메소드
- SQS로부터 전달받은 메시지를 문자열 형태로 처리하는 메소드
- 이 메서드는 SQS 메시지의 본문을 파싱하여 필요한 작업을 수행한다.
- 메시지 파싱 로직: ObjectMapper를 사용하여 SQS 메시지 본문(JSON 문자열)을 JsonNode 객체로 변환한다.
- 이후 메시지에서 TopicArn과 Message 필드를 추출하여 로깅한다.
2. 로깅 확인
- SqsListenerService가 선언된 서버의 로그를 보면 아래와 같이 메시지를 잘 전달받은 모습을 확인할 수 있다.
9. 마무리
위 과정에서도 AWS Secrets Manager를 활용하여 환경 변수를 안전하게 관리했다. 이 접근 방식은 시스템의 보안을 강화하고 중요한 설정 정보를 숨기는 데 매우 효과적이다.
Spring Boot와 AWS 서비스(SNS와 SQS)를 연동함으로써, 클라우드 네이티브 애플리케이션을 구축하는 과정이 한층 간소화되고 강화되었다. 이로 인해 MSA 아키텍처에서 서로 다른 서비스 간의 통신이 원활해지는 장점도 있다.
그리고 이글이 다른 개발자들이 클라우드 기반 어플리케이션을 구축할 때 참고할 수 있는 좋은 자료가 되면 좋겠다.
[AWS] SNS, SQS 연동하기 (1) - SNS, SQS 생성하기
'AWS' 카테고리의 다른 글
[AWS] AWS ALB 헬스 체크(상태 검사) 간격 및 경로 수정을 통한 서비스 최적화 (0) | 2023.11.22 |
---|---|
[AWS] ECS에서 Zipkin을 통한 스프링 부트 서비스 트레이싱 구축하기 (0) | 2023.11.19 |
[AWS] SNS, SQS 연동하기 (1) - SNS, SQS 생성하기 (0) | 2023.11.06 |
[AWS] IAM MFA 설정하기 (1) | 2023.11.05 |
[AWS] ECS, ALB - 동적 포트 매핑 활성화와 DNS 접속 (1) | 2023.11.05 |