이론
알림?
- 알림은 Spring Boot 애플리케이션 내에서 이벤트, 경고 또는 에러와 같은 중요한 상황을 감지하고 이를 관리자,개발자, 유저에게 알리는 기능을 가리킵니다.
- 알림은 어플리케이션의 신속한 대응과 문제 해결을 돕는 데 중요한 역할을 합니다.
- 장애 감지와 대응
- 서비스 가용성 유지
- 성능 모니터링
- 비용 절감
- 사용자 경험 향상
- 예방적 조치AWS SNS 연동
- Amazon SImple Notification Service(Amazon SNS)은 AWS의 클라우드 기반 메시징 서비스입니다. Amazon SNS를 사용하면 애플리케이션, 서비스 또는 시스템 간에 다양한 종류의 메시지를 안전하게 전송하고 관리 할 수 있습니다.
- 기능
- 푸시알림
- 다중 프로토콜(HTTP, HTTPS, SMS, email, SQS, Lambda 등 지원)
- 이벤트 기반 아키텍처
- 확장성과 신뢰성
- 미리 알림 및 모니터링
AWS SNS 예제 코드
- aws, accessKeyId, aws.secretKey, asw.sns.topicArn은 각각 AWS 계정의 액세스 키, 비밀 키, 및 SNS Topic의 ARN을 나타냅니다. 이러한 값들은 application.proerties 또는 application.yml에 설정되어야 합니다. 어플리케이션에서 위에서 만든 SnsService를 사용하여 메시지를 발행할 수 있습니다.
aws.accessKeyId=your-access-key-id
aws.secretKey=your=secret-key
aws.sns.topicArn=your-sns-topic-arn
@Service
public class SnsService{
@value("${aws.accessKeyId}")
private String awsAccessKey;
@value("${aws.secretKey}")
private String awsSecretKey;
@value("${aws.sns.topicArn}")
private String snsTopicArn;
public void publishMessage(String message){
// AWS SNS 클라이언트 생성
SnsClient snsClient = SnsClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(awsAccessKey, awsSecretKey)))
.build();
// SNS 메시지 발행 요청 생성
PublishRequest publishRequest = PublishRequest.builder()
.topicArn(snsTopicArn)
.message(message)
.build();
// SNS Message 발행
PublishResponse publishResponse = snsClient
.publish(publishRequest)
}
}
SLACK 알림 연동
메신저 플랫폼인 Slack은 다양한 형태의 알림 및 통지 기능을 제공하며, 이를 통해 사용자들이
팀 간 소통, 협업, 작업 관리를 용이하게 할 수 있습니다. Slack은 주로 다음과 같은 목적으로 사용됩니다.
- 메신저 알림
- 이벤트 알림
- 스케쥴 및 일정 알림
- 앱 및 서비스 통합 알림
- 사용자 정의 알림
SLACK 알림 예제 코드
slack.webhook.url은 Slack Incomming Webhooks URL을 나타냅니다.
이 값은 application.properties 또는 application.yml에 설정 되어야 합니다.
어플리케이션에서 SlackNotificationService를 사용하여 Slack으로 메시지를 전송 할 수 있습니다.
slack.webhook.url=https://hooks.slack.com/services/your/webhook/url
@Service
public class SlackNotificationService{
@Value("${slack.webhook.url}")
private String slackWebhookUrl;
public void sendSlackNotification(String message){
String payload = "{\"text\" : \"" + message + "\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION.JSON);
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(slackWebhookUrl, entity, String.class);
}
}
실습
AWS SNS 연동
- 사전 준비
- AWS SNS 이용을 위한 서비스 구성과 설정이 필요합니다.
- 첫째, ap-northeast-2.console.aws.amazon.com/sns/v3/home?region=ap-northeast-2#/dashboard URL로 접속합니다.
이는 AWS SNS 서비스를 등록하고 이용하기 위함입니다.- 둘째, topic(주제)을 생성합니다.
- 토픽 설정 방법
- FIFO vs 표준 : 표준에 체크
- 이름 지정 : 임의의 이름 지정
- 주제 생성 버튼 클릭
- 토픽 설정 방법
- ARN 값을 복사하여 project의 application.properties or application.yml 파일에 작성합니다.
- IAM을 이용해 만든 사용자의 accessKey, secretKey도 필요합니다.
- 둘째, topic(주제)을 생성합니다.
환경변수 설정
# aws sns
sns.topic.arn=arn:aws:sns:ap-northeast-2:5335938367146:board-server
aws.accessKey=accessKey
aws.secretKey=secretKey
aws.region=ap-northeast-2
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false
- 추천 :
application.yml
파일에서aws.accessKey
와aws.secretKey
는 민감한 정보이므로, 코드 베이스에 직접 작성하지 않고, 환경 변수나 AWS Secrets Manager와 같은 안전한 방법을 사용하여 관리하는 것이 좋습니다. 지금은 편의상 하드코딩으로 하겠습니다.sns.topic.arn=${SNS_TOPIC_ARN} aws.accessKey=${AWS_ACCESS_KEY} aws.secretKey=${AWS_SECRET_KEY} aws.region=${AWS_REGION}
의존성 라이브러리 설치
- build.gradle 파일에 가서 아래 라이브러리와 의존성 라이브러리를 작성해줍니다. 그리고 intellij 에디터에서 우측 상단의 코끼리 버튼을 클릭하여 정상 설치가 되는지 확인합니다.
dependencies {
// 나머지 생략
// aws sns
implementation 'software.amazon.awssdk:sns'
implementation platform('software.amazon.awssdk:bom:2.5.29')
compileOnly group: 'org.springframework.cloud', name:'spring-cloud-aws-messaging', version: '2.2.1.RELEASE' compileOnly group: 'org.springframework.cloud', name:'spring-cloud-aws-autoconfigure', version: '2.2.1.RELEASE'
}
AWS 환경설정 클래스
package com.example.boardserver.config;
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 topicArn;
@Value("${aws.accessKey}")
private String accessKey;
@Value("${aws.secretKey}")
private String awsSecretKey;
@Value("${aws.region}")
private String awsRegion;
}
이 AwsConfig
클래스는 Spring Boot 애플리케이션에서 AWS 관련 설정값들을 관리하기 위한 설정(Configuration) 클래스입니다. 이 클래스는 애플리케이션이 실행될 때 application.properties
또는 application.yml
파일에서 정의된 설정값들을 읽어와서 사용하게 됩니다. 각각의 설정값은 AWS SNS(Simple Notification Service)와 관련이 있으며, 해당 값을 애플리케이션의 다른 클래스에서 쉽게 사용할 수 있도록 제공합니다.
클래스 설명
@Configuration
어노테이션:- 이 어노테이션은 Spring에게 이 클래스가 하나 이상의
@Bean
정의를 포함하고 있으며, Spring IoC 컨테이너에서 빈(Bean)으로 등록해야 한다는 것을 나타냅니다. - 이 클래스 자체가 빈으로 등록되며, 다른 빈들이 이 클래스의 값을 주입받아 사용할 수 있습니다.
- 이 어노테이션은 Spring에게 이 클래스가 하나 이상의
@Getter
어노테이션:- 이 어노테이션은 Lombok 라이브러리에서 제공하는 기능으로, 클래스의 모든 필드에 대한 getter 메서드를 자동으로 생성해줍니다. 이를 통해 코드의 보일러플레이트(중복된 코드)를 줄일 수 있습니다.
@Value
어노테이션:@Value
어노테이션은 Spring에서 외부 설정값을 주입하기 위해 사용됩니다.- 이 클래스에서는
@Value
어노테이션을 사용하여application.properties
또는application.yml
파일에 정의된 값들을 클래스 필드에 주입합니다. - 예를 들어,
@Value("${sns.topic.arn}")
는sns.topic.arn
이라는 키의 값을topicArn
필드에 주입합니다.
클래스 필드 설명
private String topicArn;
:sns.topic.arn
키에 해당하는 값을 저장합니다. 이는 SNS에서 생성한 주제(Topic)의 ARN(Amazon Resource Name)을 의미합니다. 이 ARN은 특정 SNS 주제를 식별하는 고유한 문자열입니다.
private String accessKey;
:sns.accessKey
키에 해당하는 값을 저장합니다. 이는 AWS에서 제공하는 액세스 키로, AWS 서비스에 접근하기 위한 자격 증명 중 하나입니다.
private String awsSecretKey;
:sns.awsSecretKey
키에 해당하는 값을 저장합니다. 이는 AWS 액세스 키와 짝을 이루는 비밀 키로, AWS 서비스에 접근하기 위한 자격 증명입니다. 이 값은 민감한 정보이므로 안전하게 관리되어야 합니다.
private String awsRegion;
:sns.region
키에 해당하는 값을 저장합니다. 이는 AWS 리소스가 위치한 리전을 나타냅니다. 예를 들어,ap-northeast-2
는 서울 리전을 의미합니다.
SnsService & SnsServiceImpl 클래스
package com.example.boardserver.service;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.sns.SnsClient;
public interface SnsService {
AwsCredentialsProvider getAWSCredentials(String accessKeyId, String secretAccessKey);
SnsClient getSnsClient();
}
package com.example.boardserver.service.impl;
import com.example.boardserver.config.AWSConfig;
import com.example.boardserver.service.SnsService;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@Service
public class SnsServiceImpl implements SnsService {
AWSConfig awsConfig;
public SnsServiceImpl(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.getAccessKey(), awsConfig.getAwsSecretKey()))
.region(Region.of(awsConfig.getAwsRegion()))
.build();
}
}
위 클래스는 SnsService
인터페이스의 구현체로, AWS SNS(Simple Notification Service)를 사용하여 메시지를 전송하기 위한 클라이언트를 생성하는 기능을 제공합니다. SnsServiceImpl
클래스는 Spring Boot 애플리케이션에서 SnsService
인터페이스를 구현하며, AWS SDK를 사용해 SNS 클라이언트를 설정하고 구성합니다.
클래스 설명
@Slf4j
어노테이션:- 이 어노테이션은 Lombok 라이브러리에서 제공하는 기능으로, 클래스에 로깅 기능을 추가합니다. 이 어노테이션을 사용하면
log
라는 이름의 로거(Logger) 인스턴스가 자동으로 생성됩니다. 개발자는log.info()
,log.debug()
,log.error()
등의 메서드를 사용해 로그를 기록할 수 있습니다.
- 이 어노테이션은 Lombok 라이브러리에서 제공하는 기능으로, 클래스에 로깅 기능을 추가합니다. 이 어노테이션을 사용하면
@Service
어노테이션:- 이 어노테이션은 Spring에게 이 클래스가 서비스 클래스임을 알리며, Spring IoC 컨테이너에 빈(Bean)으로 등록됩니다. 다른 빈에서 의존성 주입을 통해 이 서비스를 사용할 수 있습니다.
- 의존성 주입 (Constructor Injection):
AWSConfig
객체는 이 클래스의 생성자를 통해 주입됩니다. 이 방식은 의존성 주입 중 생성자 주입(Constructor Injection)을 사용하는 것으로, Spring에서 권장하는 방법입니다. 생성자 주입을 사용하면 불변성(immutability) 보장과 테스트 용이성을 높일 수 있습니다.
getAWSCredentials
메서드:- 이 메서드는 AWS 서비스에 접근하기 위한 자격 증명(
AwsCredentialsProvider
)을 생성합니다. AwsBasicCredentials.create(accessKeyId, secretAccessKey)
를 사용하여AwsBasicCredentials
객체를 생성하고, 이를 반환하는AwsCredentialsProvider
를 제공합니다.- 문제점:
getAWSCredentials
메서드에서 자격 증명을 제공하는 방식이 비효율적입니다. 현재 방식은 자격 증명을 람다 표현식으로 반환하는데, 이를 직접 반환하는 것이 더 간단하고 명확합니다.
public AwsCredentialsProvider getAWSCredentials(String accessKeyId, String secretAccessKey) { return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKeyId, secretAccessKey)); }
- 이 메서드는 AWS 서비스에 접근하기 위한 자격 증명(
getSnsClient
메서드:- 이 메서드는 AWS SNS 클라이언트를 생성합니다.
SnsClient.builder()
를 사용해 클라이언트를 구성하고, 자격 증명 공급자와 AWS 리전을 설정한 뒤 빌드하여 클라이언트를 반환합니다.awsConfig.getAccessKey()
,awsConfig.getAwsSecretKey()
,awsConfig.getAwsRegion()
값을 사용하여 클라이언트의 자격 증명과 리전을 설정합니다.
수정해야 할 부분
getAWSCredentials
메서드 개선:- 위에서 설명한 것처럼, 자격 증명 공급자를 반환하는 람다 대신
StaticCredentialsProvider.create()
를 사용하여 더 간단하고 명확하게 수정할 수 있습니다.
public AwsCredentialsProvider getAWSCredentials(String accessKeyId, String secretAccessKey) { return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKeyId, secretAccessKey)); }
- 위에서 설명한 것처럼, 자격 증명 공급자를 반환하는 람다 대신
- 자격 증명 관리:
- 자격 증명(액세스 키 및 시크릿 키)을 코드에서 직접 관리하는 것은 보안 측면에서 취약할 수 있습니다. 따라서 환경 변수, AWS Secrets Manager, 또는 AWS IAM 역할을 사용하는 것이 좋습니다. 이를 통해 자격 증명을 외부에서 관리하고, 코드 내에서 민감한 정보를 다루지 않도록 개선할 수 있습니다.
최종 클래스
package com.example.boardserver.service.impl;
import com.example.boardserver.config.AWSConfig;
import com.example.boardserver.service.SnsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
@Slf4j
@Service
public class SnsServiceImpl implements SnsService {
private final AWSConfig awsConfig;
public SnsServiceImpl(AWSConfig awsConfig){
this.awsConfig = awsConfig;
}
public AwsCredentialsProvider getAWSCredentials(String accessKeyId, String secretAccessKey) {
return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKeyId, secretAccessKey));
}
public SnsClient getSnsClient(){
return SnsClient.builder()
.credentialsProvider(
getAWSCredentials(awsConfig.getAccessKey(), awsConfig.getAwsSecretKey()))
.region(Region.of(awsConfig.getAwsRegion()))
.build();
}
}
이와 같이 수정된 코드가 더 간결하고, 효율적으로 동작할 것입니다. 자격 증명 관리에 대한 보안 문제도 별도의 관리 방법을 도입하여 해결하는 것이 좋습니다.
SnsController
package com.example.boardserver.controller;
import com.example.boardserver.config.AWSConfig;
import com.example.boardserver.service.SnsService;
import lombok.extern.log4j.Log4j2;
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;
@Log4j2
@RestController
public class SnsController {
private final AWSConfig awsConfig;
private final SnsService snsService;
public SnsController(AWSConfig awsConfig, SnsService snsService) {
this.awsConfig = awsConfig;
this.snsService = snsService;
}
@PostMapping("/create-topic")
public ResponseEntity<String> createTopic(@RequestParam(value="topic") String topic) {
final CreateTopicRequest createTopicRequest = CreateTopicRequest.builder()
.name(topic)
.build();
SnsClient snsClient = snsService.getSnsClient();
final CreateTopicResponse createTopicResponse = snsClient.createTopic(createTopicRequest);
if(!createTopicResponse.sdkHttpResponse().isSuccessful()) throw getResponseStatusException(createTopicResponse);
log.info("topic name = {}", createTopicResponse.topicArn());
snsClient.close();
return new ResponseEntity<>("TOPIC CREATED", HttpStatus.OK);
}
@PostMapping("/subscribe")
public ResponseEntity<String> subscribe(
@RequestParam(value="endpoint") String endpoint,
@RequestParam(value="topicArn") String topicArn
){
final SubscribeRequest subscribeRequest = SubscribeRequest.builder()
.protocol("https")
.topicArn(topicArn)
.endpoint(endpoint)
.build();
SnsClient snsClient = snsService.getSnsClient();
final SubscribeResponse subscribeResponse = snsClient.subscribe(subscribeRequest);
if(!subscribeResponse.sdkHttpResponse().isSuccessful()) throw getResponseStatusException(subscribeResponse);
log.info("topicARN to subscribe = {}", subscribeResponse.subscriptionArn());
return new ResponseEntity<>("TOPIC SUBSCRIBED", HttpStatus.OK);
}
@PostMapping("/publish")
public ResponseEntity<String> publish(
@RequestBody Map<String, Object> message,
@RequestParam(value="topicArn") String topicArn
){
final PublishRequest publishRequest = PublishRequest.builder()
.topicArn(topicArn)
.subject("HTTP ENDPOINT TEST MESSAGE")
.message(message.toString())
.build();
SnsClient snsClient = snsService.getSnsClient();
final PublishResponse publishResponse = snsClient.publish(publishRequest);
log.info("message : {}", publishResponse.sdkHttpResponse().statusCode());
snsClient.close();
return new ResponseEntity<>(publishResponse.messageId(), HttpStatus.OK);
}
private ResponseStatusException getResponseStatusException(SnsResponse snsResponse) {
return new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, snsResponse.sdkHttpResponse().statusText().get()
);
}
}
클래스 설명
SnsController
클래스는 AWS SNS(Simple Notification Service)를 사용하여 주제를 생성하고, 주제에 구독하고, 메시지를 게시할 수 있는 RESTful API를 제공하는 Spring Boot 컨트롤러입니다. 이 클래스는 세 가지 주요 기능을 제공합니다: 주제 생성, 주제에 대한 구독, 메시지 게시. 각 기능은 HTTP POST 요청을 통해 호출할 수 있습니다.
주요 구성 요소
- 의존성 주입 (Dependency Injection):
AWSConfig
와SnsService
는 생성자 주입(Constructor Injection) 방식으로 주입됩니다. 이 클래스는 이들 의존성을 사용하여 AWS SNS와 관련된 작업을 수행합니다.
@RestController
어노테이션:- 이 어노테이션은 이 클래스가 Spring의 RESTful 웹 서비스 컨트롤러임을 나타냅니다. 모든 메서드는 JSON 형식의 응답을 반환합니다.
@Log4j2
어노테이션:- 이 어노테이션은 Log4j2 로깅 프레임워크를 사용하여 로그를 기록하는 데 사용됩니다.
log.info()
,log.error()
등의 메서드를 통해 로그 메시지를 기록할 수 있습니다.
- 이 어노테이션은 Log4j2 로깅 프레임워크를 사용하여 로그를 기록하는 데 사용됩니다.
주요 메서드
createTopic
메서드:- 역할: 주어진 이름으로 SNS 주제를 생성합니다.
- 세부사항:
CreateTopicRequest
를 빌드하여 SNS 클라이언트를 통해 주제를 생성합니다.- 주제 생성이 성공하면 HTTP 상태 코드 200과 함께 "TOPIC CREATED"라는 메시지를 반환합니다.
- 생성된 주제의 ARN을 로그로 기록합니다.
- 예외 처리: 주제 생성이 실패할 경우,
ResponseStatusException
을 통해 500 내부 서버 오류를 반환합니다.
subscribe
메서드:- 역할: 주어진 엔드포인트를 SNS 주제에 HTTPS 프로토콜을 사용하여 구독합니다.
- 세부사항:
SubscribeRequest
를 빌드하여 SNS 클라이언트를 통해 주제에 대한 구독을 수행합니다.- 구독이 성공하면 구독 완료 메시지를 반환합니다.
- 구독된 주제의 ARN을 로그로 기록합니다.
- 예외 처리: 구독이 실패할 경우,
ResponseStatusException
을 통해 500 내부 서버 오류를 반환합니다.
publish
메서드:- 역할: 주어진 SNS 주제에 메시지를 게시합니다.
- 세부사항:
PublishRequest
를 빌드하여 SNS 클라이언트를 통해 메시지를 주제에 게시합니다.- 메시지 게시가 성공하면 메시지 ID를 반환합니다.
- 게시된 메시지의 상태 코드를 로그로 기록합니다.
- 예외 처리: 메시지 게시가 실패할 경우,
ResponseStatusException
을 통해 500 내부 서버 오류를 반환합니다.
getResponseStatusException
메서드:- 역할: SNS 응답에서 HTTP 상태가 실패했을 때 예외를 발생시키는 유틸리티 메서드입니다.
- 세부사항:
- SNS 응답의 상태 메시지를 사용하여
ResponseStatusException
을 생성합니다. - 이 메서드는 각 SNS 요청의 성공 여부를 검사하는 데 사용됩니다.
- SNS 응답의 상태 메시지를 사용하여
보완 및 개선 사항
- SNS 클라이언트 관리:
- 각 메서드에서 SNS 클라이언트를 생성하고
snsClient.close()
를 호출하고 있습니다. SNS 클라이언트는 스레드 안전하며 재사용 가능하므로, 클라이언트를 매번 생성하고 닫는 대신 빈으로 관리하여 애플리케이션 전체에서 재사용하는 것이 더 효율적입니다. - 이를 위해 Spring 빈으로 SNS 클라이언트를 관리하고, 컨트롤러에서는 주입받아 사용하는 방식으로 개선할 수 있습니다.
- 각 메서드에서 SNS 클라이언트를 생성하고
- 메서드 반환값의 일관성:
- 메서드에서 반환하는 값이 일관성을 유지해야 합니다. 현재
publish
메서드는ResponseEntity<String>
을 반환하고 있어 다른 메서드와 일관성을 유지하고 있습니다.
- 메서드에서 반환하는 값이 일관성을 유지해야 합니다. 현재
- 예외 처리:
- 예외 처리 로직이 각 메서드에 잘 적용되어 있지만,
getResponseStatusException
메서드에서snsResponse.sdkHttpResponse().statusText().get()
이 사용되고 있습니다. 이 부분은Optional
에서 값을 꺼내는 것이므로orElse
를 사용하는 것이 더 안전합니다. 예를 들어:
private ResponseStatusException getResponseStatusException(SnsResponse snsResponse) { return new ResponseStatusException( HttpStatus.INTERNAL_SERVER_ERROR, snsResponse.sdkHttpResponse().statusText().orElse("Error") ); }
- 예외 처리 로직이 각 메서드에 잘 적용되어 있지만,
- 프로토콜 고정:
subscribe
메서드에서protocol("https")
로 고정되어 있습니다. 만약 다른 프로토콜(SMS, email 등)을 지원해야 한다면, 이 부분을 파라미터화하여 유연성을 제공할 수 있습니다.
최종 요약
SnsController
클래스는 AWS SNS 주제를 생성하고, 주제에 대한 구독을 관리하며, 메시지를 게시하는 RESTful API를 제공합니다. 이 클래스는 주어진 작업을 효과적으로 수행하지만, SNS 클라이언트 관리 방식과 예외 처리 로직의 일부를 개선함으로써 더 나은 성능과 안정성을 확보할 수 있습니다.
데모
postman을 이용하여 데모를 해보겠습니다.
방법
공통
- POSTMAN 실행.
- HTTP POST 메소드로 설정한다.
/create-topic 엔드포인트
- URL은
localhost:8080/create-topic?topic=notify1
로 한다. - 요청을 하게되면, 정상적으로 200 OK로 나타나고 HTTP Response Body에는 응답이 나타난다.
- AWS Console 웹페이지의 SNS 서비스 대시보드로 이동하여 console(주제)로 이동한다. 그리고
notify1
이 생성되었는지 확인한다.- 이외 board-server, notify2, notify3, test-notify등 여러 파라미터를 넘겨주어 정상적으로 요청과 응답이 이루어 지는지 확인한다.
/subscribe 엔드포인트
- URL은
localhost:8080/create-topic?topic=notify1
로 한다. - 요청을 하게되면, 정상적으로 200 OK로 나타나고 HTTP Response Body에는 응답이 나타난다.
- AWS Console 웹페이지의 SNS 서비스 대시보드로 이동하여 console(주제)로 이동한다. 그리고
notify1
이 생성되었는지 확인한다.- 이외 board-server, notify2, notify3, test-notify등 여러 파라미터를 넘겨주어 정상적으로 요청과 응답이 이루어 지는지 확인한다.
슬랙 알림 구현
라이브러리 설치
// 생략
dependencies {
// slack
implementation 'com.slack.api:bolt:1.18.0'
implementation 'com.slack.api:bolt-servlet:1.18.0'
// implementation 'com.slack.api:bolt-jetty:1.18.0' 의존성 불일치 발생으로 주석 혹은 삭제
// 생략
}
위에 나열된 각 라이브러리는 Slack의 Bolt 프레임워크를 사용하는 Java 애플리케이션을 개발할 때 사용하는 주요 의존성들입니다. 각 라이브러리가 어떤 역할을 하는지 설명드리겠습니다.
1. bolt
- Bolt for Java Core Library:
- 이 라이브러리는 Slack 애플리케이션을 구축하기 위한 핵심 라이브러리입니다.
- Bolt는 Slack에서 제공하는 고수준 API 프레임워크로, Slack의 이벤트, 인터랙티브 컴포넌트(버튼, 메뉴 등), Slash Commands, 메시지 구성 등을 처리하는 것을 쉽게 해줍니다.
- 이 라이브러리를 사용하면 Slack 애플리케이션을 구축하는 데 필요한 많은 작업을 자동으로 처리할 수 있으며, 슬랙 API를 사용한 작업을 더 간결하고 효율적으로 수행할 수 있습니다.
- 주로 Slack 앱에서 이벤트 핸들러와 미들웨어를 작성할 때 사용됩니다.
2. bolt-servlet
- Bolt for Java Servlet Adapter:
- 이 라이브러리는 Bolt for Java를 기존의 Servlet 기반 애플리케이션과 통합하기 위한 어댑터 역할을 합니다.
- Spring Boot와 같은
Java 기반 웹 프레임워크에서 Bolt 애플리케이션을 쉽게 통합
할 수 있도록 지원합니다. - 이를 통해, Slack 이벤트를 처리하기 위한 엔드포인트를 서블릿 기반의 애플리케이션 내에서 쉽게 정의할 수 있습니다.
Spring Boot와 같은 프레임워크에서 Slack 애플리케이션을 구축
할 때 매우 유용합니다.
3. bolt-jetty
- Bolt for Java Jetty Adapter:
- 이 라이브러리는
Jetty 서버
를 사용하여 Bolt 애플리케이션을 실행할 수 있도록 하는 어댑터입니다. - Jetty는 경량화된 Java 서블릿 컨테이너로, 내장형 웹 서버로 자주 사용됩니다.
- Bolt 애플리케이션을 독립적으로 실행하거나, 서버리스 환경 또는 경량화된 서비스로 배포하려는 경우,
Jetty와의 통합
이 유용할 수 있습니다. - 이 라이브러리를 사용하면 Jetty 서버에서 Slack 앱을 쉽게 실행할 수 있습니다.
- 이 라이브러리는
요약
bolt
라이브러리는 Slack 애플리케이션을 구축하는 핵심 라이브러리로, Slack 이벤트 및 인터랙션을 쉽게 처리할 수 있는 고수준 API를 제공합니다.bolt-servlet
라이브러리는 Bolt 애플리케이션을 서블릿 기반의 Java 애플리케이션에 통합할 수 있도록 도와줍니다.bolt-jetty
라이브러리는 Jetty 서버에서 Bolt 애플리케이션을 실행할 수 있도록 하는 어댑터 역할을 합니다.
구현 코드 톺아보기
package com.example.boardserver.service;
import org.springframework.stereotype.Service;
@Service
public interface SlackService {
void sendSlackMessageByBot(String message, String channel);
void sendSlackMessageByWebhook(String message, String channel);
}
package com.example.boardserver.service.impl;
import com.example.boardserver.service.SlackService;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Slf4j
@Service
public class SlackServiceImpl implements SlackService {
@Value("${slack.token}")
String slackToken;
@Value("${slack.webhook.url}")
String slackWebhookUrl;
@Override
public void sendSlackMessageByBot(String message, String channel) {
try {
MethodsClient methodsClient = Slack.getInstance().methods(slackToken);
ChatPostMessageRequest request = ChatPostMessageRequest.builder()
.channel(channel)
.text(message)
.build();
ChatPostMessageResponse response = methodsClient.chatPostMessage(request);
if (!response.isOk()) {
log.error("Slack API Error: {}", response.getError());
} else {
log.info("Message sent to channel: {}", channel);
}
} catch (SlackApiException | IOException e) {
log.error("Error sending Slack message to channel {}: {}", channel, e.getMessage());
}
}
@Override
public void sendSlackMessageByWebhook(String message, String channel) {
try {
Slack slack = Slack.getInstance();
slack.send(slackWebhookUrl, message); // Webhook URL을 통해 메시지 전송
log.info("Message sent via webhook to channel: {}", channel);
} catch (IOException e) {
log.error("Error sending message via webhook: {}", e.getMessage());
}
}
}
1. SlackService 인터페이스 및 구현
SlackService
인터페이스는 메시지를 보낼 때 두 가지 방법, 즉 봇을 통한 메시지 전송과 웹훅을 통한 메시지 전송을 정의하고 있습니다.SlackServiceImpl
구현체에서 이 인터페이스를 구현하여 Slack API를 통해 메시지를 전송하는 기능을 제공하고 있습니다.- 여기에서 큰 문제는 없지만, 다음 사항을 고려해볼 수 있습니다.
개선사항
- 채널 처리 로직 개선:
- 채널 이름을 전달받아
#
이 없으면 추가하는 로직은 컨트롤러에서 수행하고 있습니다. 이 로직을 서비스 레이어로 옮기는 것도 고려해볼 수 있습니다. 이렇게 하면 SlackService가 Slack 메시지 전송과 관련된 모든 처리를 담당하게 되므로, 책임의 분리가 더 명확해집니다.
- 채널 이름을 전달받아
- WebHook 메시지 전송 기능:
sendSlackMessageByWebhook
메서드에서 특정 채널을 사용하지 않고 있습니다. WebHook이 특정 채널에 바인딩되어 있다면 이 부분은 문제가 되지 않지만, 바인딩되지 않은 WebHook을 사용하는 경우 채널별로 WebHook URL을 관리해야 할 수 있습니다.
2. SnsController 클래스
@Log4j2
@RestController
@RequestMapping("/sns")
@RequiredArgsConstructor
public class SnsController {
private final SlackService slackService;
@GetMapping("/slack")
public void sendSlack(
@RequestParam(value="message") String message,
@RequestParam(value="channel") String channel
) {
channel = (!channel.startsWith("#") ? "#".concat(channel) : channel;
slackService.sendSlackMessageByBot(message, channel);
}
}
- 이 클래스는 AWS SNS와 Slack 메시징 기능을 모두 제공하는 REST API 엔드포인트를 구현하고 있습니다.
- 각 메서드에서 AWS SNS와 Slack API를 통해 작업을 수행하고, 결과를 로그에 기록하고 있습니다.
개선사항
- 필드 주입:
SnsController
에서@RequiredArgsConstructor
를 사용하고 있으나, 생성자가 직접 정의되어 있습니다. 생성자를 정의할 필요가 없으므로, 직접 정의한 생성자를 제거하고@RequiredArgsConstructor
를 활용하는 것이 더 깔끔한 코드 작성 방법입니다.
@Log4j2 @RestController @RequestMapping("/sns") @RequiredArgsConstructor public class SnsController { private final SnsService snsService; private final SlackService slackService; // Remaining code... }
- 에러 핸들링 개선:
- 에러 핸들링은 현재 적절히 이루어지고 있으나, HTTP 상태 코드와 메시지를 명확히 전달하고자 할 때,
throw new ResponseStatusException(...)
구문 대신@ExceptionHandler
를 사용한 글로벌 예외 처리를 고려할 수 있습니다.
- 에러 핸들링은 현재 적절히 이루어지고 있으나, HTTP 상태 코드와 메시지를 명확히 전달하고자 할 때,
- API의 일관성:
@GetMapping("/slack")
메서드에서 채널에 메시지를 보낼 때 사용하는GET
메서드는 데이터를 생성하는 동작을 수행하고 있습니다. 이는 REST API 설계 원칙에 위배됩니다.POST
를 사용하는 것이 RESTful한 설계에 더 적합합니다.
@PostMapping("/slack") public void sendSlack(@RequestParam(value="message") String message, @RequestParam(value="channel") String channel) { if (!channel.startsWith("#")) { channel = "#".concat(channel); } slackService.sendSlackMessageByBot(message, channel); }
- 로깅의 유용성:
- 현재의 로깅은 적절하게 이루어지고 있지만, 특정한 상황에서는 더 많은 정보가 필요할 수 있습니다. 예를 들어, Slack API 호출이 실패할 때 어떤 메시지를 전송하려 했는지, 어떤 채널로 시도했는지 등의 정보도 포함되면 문제 해결이 더 용이할 수 있습니다.
최종 검토
코드는 전체적으로 잘 작성되었으며, Slack API와 AWS SNS를 사용하는 기능도 잘 구현되었습니다. 위에서 언급한 개선 사항들을 고려하여 코드를 좀 더 개선할 수 있습니다.
특히, 다음 사항을 고려하십시오:
- 컨트롤러에서 서비스 레이어로 책임을 이동하는 구조.
- RESTful API의 일관성 유지.
- 에러 핸들링을 더 구조적으로 개선하기.
- 로깅에 더 많은 정보를 포함하여 디버깅 용이성을 높이기.
이러한 개선사항을 통해 코드의 유지보수성과 확장성을 높일 수 있을 것입니다.
'프레임워크 > 자바 스프링' 카테고리의 다른 글
접속자 대기열 시스템 #1: 시스템 설계와 Spring WebFlux, Redis (3) | 2024.09.09 |
---|---|
대규모 트래픽 게시판 구축 시리즈 #14: 배포 자동화 (1) | 2024.09.07 |
대규모 트래픽 게시판 구축 시리즈 #12: 성능 테스트 (5) | 2024.09.07 |
대규모 트래픽 게시판 구축 시리즈 #11: 로깅, 예외처리 (0) | 2024.09.07 |
대규모 트래픽 게시판 구축 시리즈 #10: 게시판 검색 API (0) | 2024.09.07 |