Spring-Data-DynamoDB를 사용하여 SpringBoot와 AWS DynamoDB 연동하기

2024. 1. 28. 23:39·SpringBoot

 

들어가며

현재 진행하고 있는 실시간 채팅방 관련 프로젝트에서

채팅방에서 나눈 이전 대화들을 저장하여 사용자가 다시 채팅방에 접속하면

이전 대화 내용들을 보여주는 기능을 추가적으로 구현해야 했다.

 

이를 위해서는 채팅방 메시지들을 저장하고 조회, 삭제를 수행하게 될 데이터베이스를 무엇을 사용해야 할지 선택해야 했다.

 

💁‍♂️ RDB...? 아니면 NoSQL?? 💁‍♂️

기존 데이터베이스는 RDB인 MySQL을 사용하고 있었는데, 원래 사용하던 MySQL을 사용할지

아니면 채팅방 메시지 관리를 위한 추가적인 NoSQL DB를 사용할지에 대한 고민이 깊어졌다.

 

그러나 NoSQL이 기존의 RDB보다 더 빠른 읽기 쓰기 성능을 가지고 있기 때문에,
각 채팅방 별, 채팅 내용을 저장하고 불러오기에는 NoSQL을 추가적으로 사용하는게 성능상 더 효과적이라고 판단하였다.

대표적인 NoSQL로 AWS DynamoDB와 MongoDB가 존재하는데 각자의 장단점이 존재하였고,

같이 개발하는 멤버와의 논의 끝에 AWS DynamoDB를 사용하게 되었다.

 

가격 측면에서 두 데이터베이스를 비교해보자면

 

MongoDB의 경우 Serverless와 Dedicated, Shared에 따라서 가격이 다르게 측정되는데,

Shared Cluster의 경우 512MB storage, shared RAM, shared vCPUs가 무료고

2GB storage는 한달에 9달러, 5GB는 한달에 25달러 정도 하였다.

자세한 내용은 아래 링크에서 참고

 

Pricing

MongoDB Product Pricing

www.mongodb.com

 

반면 AWS DynamoDB의 경우

무료 스토리지 25GB에, 매월 최대 2억 건의 읽기/쓰기 요청을 AWS 프리 티어로 제공하는데

심지어 이건 1년의 프리 티어가 끝나도 계속 적용이 된다.... 허허

(이게 바로 대기업?)

물론 글로벌 보조 인덱스를 추가하고 설정하는데에 있어서는 추가 요금이 부가된다.

 

프로젝트를 진행하는데 있어서 채팅 메시지에 대한 용량 걱정이 별로 없고, 단순 기능 구현이 목적이라면

MongoDB를 사용해도 좋을 거 같다.

Spring에서 공식으로 Spring Data MongoDB를 지원하고 있기 때문에, 설정하는데 있어서 훨씬 편리하다!

 

Spring Data MongoDB

Spring Data for MongoDB is part of the umbrella Spring Data providing integration with the MongoDB document database, offering a familiar and consistent Spring based programming model while retaining store specific features and capabilities. Features Mongo

spring.io

 

 

 

DynamoDBMapper vs Spring-Data-DynamoDB

Spring-Data-DynamoDB를 통해 연동하는데에 있어서 먼저 살펴보고 넘어가야 할 점이 있다.

바로 DynamoDBMapper와 Spring-Data-DynamoDB에 대한 내용이다.

우선 DynamoDBMapper는 AWS에서 공식적으로 제공해주는 AWS JAVA SDK에 들어있는 클래스의 일종이다.

DynamoDBMapper를 사용하고 싶다면 아래 AWS 공식 문서를 살펴보도록 하자!

 

DynamoDBMapper CRUD 작업 - Amazon DynamoDB

DynamoDBMapper CRUD 작업 다음 Java 코드 예제에서는 Id, Title, ISBN 및 Authors 속성을 가진 CatalogItem 클래스를 선언합니다. 이 예제에서는 주석을 사용하여 이러한 속성을 DynamoDB의 ProductCatalog 테이블로 매

docs.aws.amazon.com

 

 

그렇다면 Spring-Data-DynamoDB는....?

AWS에서 제공하는 서비스가 아니다. 그리고 Spring Data에서 공식으로 제공하는 기술도 아니다.

michaellavelle/spring-data-dynamodb -> derjust/spring-data-dynamodb -> boostchicken/spring-data-dynamodb 로 계속 fork 하며 이어진 개인이 진행하는 프로젝트이다.

Spring은 Spring-data-JPA, Spring-data-mongoDB, Spring-data-cassandra 등 

여러가지 Spring data 인터페이스를 제공하지만 이 DynamoDB에 대해서는 제공하지 않고 있다.

그러므로 본인의 Spring Boot 버전이 Spring-Data-DynamoDB과 호환이 되는지 확인이 필요하다.

 

GitHub - boostchicken/spring-data-dynamodb: This module deals with enhanced support for a data access layer built on AWS DynamoD

This module deals with enhanced support for a data access layer built on AWS DynamoDB. - GitHub - boostchicken/spring-data-dynamodb: This module deals with enhanced support for a data access layer ...

github.com

 

 

 

프로젝트에 Spring-Data-DynamoDB 적용

build.gradle

AWS에 연결하는 Configuration 설정하고 Spring-Data-Dynamodb의 쿼리를 사용하기 위해서 아래 dependency를 가져온다.

 

JAVA : 17

SpringBoot version : 2.7.15

// dynamodb
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.268'
implementation 'io.github.boostchicken:spring-data-dynamodb:5.2.5'

 

application.yml

aws:
  dynamodb:
    accessKey: {AWS access-key 값}
    secretKey: {AWS secret-key 값}
    region: {AWS DynamoDB를 사용하는 리전명}

 

AWS IAM 액세스 키 발급에 대한 내용은 아래 링크를 참고하자

 

[AWS] AWS Access Key란? (엑세스 키 생성 방법)

Access Key는 AWS 계정에 액세스하기 위한 인증 정보 중 하나입니다. Access Key ID와 Secret Access Key 두 부분으로 구성되어 있습니다. AWS 자원에 접근하거나, API를 호출할 때 사용됩니다. 보통 백엔드에서

hyunki99.tistory.com

 

DynamoDBConfig

액세스키와 비밀키를 활용하여 로컬 코드에서 AWS의 DynamoDB 서비스로 접근할 수 있도록 Config 코드를 작성해야 한다.

@Configuration
@EnableDynamoDBRepositories(basePackages = {"com.dku.council.domain.chatmessage.repository"},
        includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
public class DynamoDBConfig {

    @Value("${aws.dynamodb.accessKey}")
    private String awsAccessKey;

    @Value("${aws.dynamodb.secretKey}")
    private String awsSecretKey;

    @Value("${aws.dynamodb.region}")
    private String awsRegion;

    public AWSCredentials amazonAWSCredentials() {
        return new BasicAWSCredentials(awsAccessKey, awsSecretKey);
    }

    public AWSCredentialsProvider amazonAWSCredentialsProvider() {
        return new AWSStaticCredentialsProvider(amazonAWSCredentials());
    }

    @Bean
    public AmazonDynamoDB amazonDynamoDB() {
        return AmazonDynamoDBClientBuilder.standard().withCredentials(amazonAWSCredentialsProvider())
                .withRegion(awsRegion).build();
    }

    /**
     * Java DynamoDB SDK가 Java의 기본 Date 타입만 허용하므로
     * LocalDateTimeType과 Date를 상호 변환할 수 있는 컨버터 추가
     */
    public static class LocalDateTimeConverter implements DynamoDBTypeConverter<Date, LocalDateTime> {
        @Override
        public Date convert(LocalDateTime source) {
            ZoneId seoulZoneId = ZoneId.of("Asia/Seoul");
            return Date.from(source.atZone(seoulZoneId).toInstant());
        }

        @Override
        public LocalDateTime unconvert(Date source) {
            return source.toInstant().atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime();
        }
    }

}

 

 

 

DynamoDB Entity 작성

ChatRoomMessage

@DynamoDBTable(tableName = "ChatRoomMessage")
@Getter
@Setter
@NoArgsConstructor()
public class ChatRoomMessage {

    @Id
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private ChatRoomMessageId chatRoomMessageId;

    @DynamoDBHashKey(attributeName = "roomId")
    public String getRoomId() {
        return chatRoomMessageId != null ? chatRoomMessageId.getRoomId() : null;
    }

    public void setRoomId(String roomId) {
        if (chatRoomMessageId == null) {
            chatRoomMessageId = new ChatRoomMessageId();
        }
        chatRoomMessageId.setRoomId(roomId);
    }

    @DynamoDBRangeKey(attributeName = "createdAt")
    @DynamoDBTypeConverted(converter = DynamoDBConfig.LocalDateTimeConverter.class)
    public LocalDateTime getCreatedAt() {
        return chatRoomMessageId != null ? chatRoomMessageId.getCreatedAt() : null;
    }

    @DynamoDBTypeConverted(converter = DynamoDBConfig.LocalDateTimeConverter.class)
    public void setCreatedAt(LocalDateTime createdAt) {
        if (chatRoomMessageId == null) {
            chatRoomMessageId = new ChatRoomMessageId();
        }
        chatRoomMessageId.setCreatedAt(createdAt);
    }

    @DynamoDBAttribute
    private String messageType;

    @DynamoDBAttribute
    private Long userId;

    @DynamoDBAttribute
    private String userNickname;

    @DynamoDBAttribute
    private String content;
}

 

ChatRoomMessageId

@Getter
@Setter
public class ChatRoomMessageId implements Serializable {
    private static final long serialVersionUID = 1L;

    @DynamoDBHashKey
    private String roomId;

    @DynamoDBRangeKey
    @DynamoDBTypeConverted(converter = DynamoDBConfig.LocalDateTimeConverter.class)
    private LocalDateTime createdAt;
}

 

DynamoDB 테이블을 작성하는데 있어서 필요한 기본 개념들은

아래 공식 문서를 참고해주세요!

 

Amazon DynamoDB의 핵심 구성 요소 - Amazon DynamoDB

Amazon DynamoDB의 핵심 구성 요소 DynamoDB에서 테이블, 항목 및 속성이 작업하는 핵심 구성 요소입니다. 테이블은 항목의 컬렉션이고 각 항목은 속성의 컬렉션입니다. DynamoDB는 기본 키를 사용하여 테

docs.aws.amazon.com

 

 

엔티티 작성 후, 여기서 스프링을 많이 써보신 분들이면 아래와 같은 의문이 들 수 있을 것 같다.

💁‍♂️1. Entity 작성에 있어서 굳이 @Setter를 사용하는 이유는?? 💁‍♂️

Entity를 구현하는 데 있어서 Setter 메소드를 구현하는 것은 객체의 일관성을 유지하기 위해서 지양해야 한다.

하지만 위에 DynamoDBMapper로 CRUD 구현하는 링크에서도 보다시피,

DynamoDB 공식 Docs에서도 Entity 구현 시 Setter 메소드를 필수적으로 사용한다.

DynamoDB Java SDK에서 DynamoDB를 편리하게 다루라고 제공하는 DynamoDBMapper가 

객체 매핑 시 Setter를 사용하기 때문이다.

초반에 해당 내용을 모르고 @Builder로 평소처럼 Entity를 작성하다, 여러 오류를 마주치게 되어 며칠을 고생했다... ㅠ

 

💁‍♂️2. ChatRoomMessageId를 별도로 생성해 준 이유는?? 💁‍♂️

결론부터 말하자면, DynamoDB에서 사용하는 개념인 Composite Key (복합키)를 Entity에 적용했기 때문이다.

DynamoDB에서 복합키는 Partition Key와 Range Key를 같이 사용하는 것을 의미하는데

위와 같이 작성하지 않으면 Bean 등록 오류가 발생하니, 복합키를 사용한다면 반드시 위와 같은 방식으로 작성하자!

작성은 아래 Spring-Data-DynamoDB 깃허브 내용을 보고 참고하였다.

 

Use Hash Range keys

This module deals with enhanced support for a data access layer built on AWS DynamoDB. - derjust/spring-data-dynamodb

github.com

 

 

 

AWS Console에서 DynamoDB 생성

이제 AWS console에 로그인하여 DynamoDB 서비스에 들어가서

스프링부트에서 작성한 엔티티 내용들을 기반으로 테이블을 생성해 주자.

 

 

테이블 생성을 누르면 해당 테이블이 생성되고 원격으로 사용할 수 있게 되며,
해당 작업은 AWS Console 뿐만 아니라 AWS CLI를 사용하여 수행할 수도 있다.

 

 

 

DynamoDB Repository 작성

ChatRoomMessageRepository

@EnableScan
@Repository
public interface ChatRoomMessageRepository extends CrudRepository<ChatRoomMessage, ChatRoomMessageId> {

    List<ChatRoomMessage> findAllByRoomIdOrderByCreatedAtAsc(String roomId);

    void deleteAllByRoomId(String roomId);
}

 

Spring에서 제공하는 CrudRepository를 상속받아 인터페이스를 작성하면 메소드로 쿼리를 동작시킬 수 있다.

DynamoDBMapper 사용하다가 이거 쓰면 너무 편안....

 

 

 

 

DynamoDB Service 작성

사실 Service 로직부터는 특별히 다른 부분은 없어서 각자의 기능에 맞게 코드를 작성해주면 된다.

나의 경우 채팅방 메시지를 관리하기 위한 서비스 로직을 작성해야 하므로 아래와 같이 작성했다. 

@Service
@RequiredArgsConstructor
@Slf4j
public class ChatRoomMessageService {

    private final ZoneId seoulZoneId = ZoneId.of("Asia/Seoul");

    private final ChatRoomMessageRepository chatRoomMessageRepository;

    public void create(String roomId,
                       String messageType,
                       Long userId,
                       String userNickname,
                       String content) {

        ChatRoomMessage chatRoomMessage = new ChatRoomMessage();
        chatRoomMessage.setRoomId(roomId);
        chatRoomMessage.setMessageType(messageType);
        chatRoomMessage.setUserId(userId);
        chatRoomMessage.setUserNickname(userNickname);
        chatRoomMessage.setContent(content);
        chatRoomMessage.setCreatedAt(LocalDateTime.now().atZone(seoulZoneId).toLocalDateTime());

        chatRoomMessageRepository.save(chatRoomMessage);
    }

    public List<ChatRoomMessage> findAllChatRoomMessages(String roomId) {
        return chatRoomMessageRepository.findAllByRoomIdOrderByCreatedAtAsc(roomId);
    }

    public void deleteChatRoomMessages(String roomId) {
        chatRoomMessageRepository.deleteAllByRoomId(roomId);
    }
}

 

 

 

 

AWS Console을 통해 테이블에 저장된 내용 확인하기 

 

컨트롤러 로직을 구성하고 API 요청을 보내면 아래와 같이 잘 들어오는걸 확인할 수 있다. 

 

 

DynamoDB를 다루는데 있어서 정말 많은 오류를 직면했던 것 같다.

자료도 별로 없고..

뭔가 음... 뭐라 해야 할까... 스프링부트와 DynamoDB를 설정하는데 있어서

다른 데이터베이스에 비해 옛날의 향기가 느껴진다고 해야하나... 

물론 DynamoDB 자체는 정말 정말 편리하고 좋다.

 

 

 

이제 채팅방 관련해서 구체적인 기능들을 적용하고

DynamoDB 쪽 수정 사항이 있다면 수정해 나가야겠다...

 

 

 

🌟DynamoDB 넌 이제 내꺼야!🌟

 

 

 

 

 

'SpringBoot' 카테고리의 다른 글

Mockito 에러 - UnnecessaryStubbingException 해결  (0) 2024.03.21
쿠폰 발급 요청 시, 확인하는 쿠폰 정보를 Redis Cache에 담아 개선하기  (0) 2024.03.19
[Spring Boot] 채팅방에서 사용자가 업로드한 파일을 NHN Cloud의 Object Storage를 통해 관리하기  (0) 2024.02.07
SpringBoot와 Kafka 연동 간의 SASL을 사용한 암호화 적용하기  (0) 2024.01.19
[Spring Boot] 엑셀(xlsx) 파일 파싱 후, 서비스 로직에 추가하는 코드 작성하기  (0) 2023.12.21
'SpringBoot' 카테고리의 다른 글
  • 쿠폰 발급 요청 시, 확인하는 쿠폰 정보를 Redis Cache에 담아 개선하기
  • [Spring Boot] 채팅방에서 사용자가 업로드한 파일을 NHN Cloud의 Object Storage를 통해 관리하기
  • SpringBoot와 Kafka 연동 간의 SASL을 사용한 암호화 적용하기
  • [Spring Boot] 엑셀(xlsx) 파일 파싱 후, 서비스 로직에 추가하는 코드 작성하기
개발이조아용
개발이조아용
IT 개발에서 배운 성장의 기록을 작성합니다.
  • 개발이조아용
    계속 하다 보면?!
    개발이조아용
  • 전체
    오늘
    어제
    • 분류 전체보기 (65) N
      • Tibero DB (Tmax AI Bigdata .. (7)
      • Git (2)
      • CI CD (2)
      • Redis (3)
      • SpringBoot (15)
      • SQL 문제 풀이 (8)
      • Apache Kafka (8)
        • 오류 해결 (3)
        • 개념 정리 (4)
        • 보안 (1)
      • Nginx (3)
      • SW마에스트로 (3)
      • Kubernetes (4)
      • AWS (5)
      • gRPC (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    SQL
    redis
    nginx
    Tibero
    sql 문제
    K8S
    Git
    grpc
    소프트웨어 마에스트로
    Kafka 오류
    DynamoDB 연동
    Kafka SASL
    Kafka 개념
    SpringBoot
    redis script
    Redis 개념
    MSA
    SASL 인증
    KAFKA
    leetcode
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
개발이조아용
Spring-Data-DynamoDB를 사용하여 SpringBoot와 AWS DynamoDB 연동하기
상단으로

티스토리툴바