Spring Boot에서 AWS DynamoDB를 연동해야할 일이 생겼다.
DynamoDB는 AWS에서 제공하는 NoSQL 데이터베이스이다. key-value 형태로 데이터를 제공한다.
Aurora나 Document DB처럼 DB커넥션을 맺는 형태가 아닌 AWS SDK를 활용하여 인터페이스를 한다.
AWS SDK도 버전마다 설정하는 방법이 상이하여 AWS SDK V2버전을 기준으로 설정을 하였다.
dependencies {
// aws
implementation platform('software.amazon.awssdk:bom:2.20.85')
implementation 'software.amazon.awssdk:dynamodb-enhanced'
}
@Bean
public DynamoDbClient dynamoDbClient(){
return DynamoDbClient.builder().region(Region.AP_NORTHEAST_2).credentialsProvider(StaticCredentialsProvider.create(basicAWSCredentials())).build();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient){
return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
}
DynamoDB 테이블에 해당하는 객체를 설정해준다. 해당 객체는 JPA의 Entity와 같은 역할을 한다. @DynamoDbBean 애노테이션을 선언해주어야 한다. @DynamoDbPartitionKey, @DynamoDbSortKey 애노테이션으로 파티션키, 정렬키를 지정할 수 있으며 @DynamoDbAttribute로 DynamoDB의 key 매칭되는 필드를 지정할 수 있다.
특이한 점은 보통 다른 data 라이브러리는 key관련 애노테이션을 변수 위에다 선언하는데 getter 메소드 위에다 지정한다는 점이다. 왜 그런지는 모르겠다. AWS Developer Guide에서 아래와 같이 가이드가 되어 있다.
@Builder
@DynamoDbBean
@NoArgsConstructor
@AllArgsConstructor
@Setter
public class DynamoDbSample {
private String id;
private String date;
private String desc;
@DynamoDbPartitionKey
@DynamoDbAttribute("id")
public String getId() {
return id;
}
@DynamoDbSortKey
@DynamoDbAttribute("date")
public String getDate() {
return date;
}
@DynamoDbAttribute("desc")
public String getDesc() {
return desc;
}
}
final로 선언한 dynamoDbSampleDynamoDbTable에 dynamoDbEnhancedClient.table메소드를 활용하여 대상 테이블 선언해주면 그 다음부터는 dynamoDbSampleDynamoDbTable에서 제공하는 메소드를 활용하여 데이터를 조회하고 입/출력을 할 수 있다.
하지만 현 프로젝트는 JPA를 위주로 사용하고 있기 때문에 JPA처럼 repository 형태로 쓰고 싶어서 비슷하게 구조를 잡아보았다. getItem, putItem, deleteItem 메소드가 직관적이여서 사용하기는 편했다.
특이한 점은 파티션키로만 대상을 조회할 때 인데 정렬키를 사용할 경우 파티션키와 정렬키를 동시에 입력해줘야 대상값을 조회해온다. 파티션키만 지정하여 getItem을 하면 에러가 발생한다.
그래서 파티션키만으로 데이터를 조회할 때는 QueryConditional, QueryEnhancedRequest 를 사용하여 데이터를 조회하도록 하니깐 조회가 가능했다.
@Repository
public class DynamoDbSampleRepository {
private final DynamoDbTable<DynamoDbSample> dynamoDbSampleDynamoDbTable;
public DynamoDbSampleRepository(DynamoDbEnhancedClient dynamoDbEnhancedClient) {
this.dynamoDbSampleDynamoDbTable = dynamoDbEnhancedClient.table("DYNAMO_DB_SAMPLE", TableSchema.fromBean(DynamoDbSample.class));
}
public void insert(DynamoDbSample dynamoDbSample){
dynamoDbSampleDynamoDbTable.putItem(dynamoDbSample);
}
public DynamoDbSample findBy(DynamoDbSample dynamoDbSample){
return dynamoDbSampleDynamoDbTable.getItem(dynamoDbSample);
}
public DynamoDbSample findById(String id){
QueryConditional conditional = QueryConditional.keyEqualTo(
Key.builder()
.partitionValue(id)
.build()
);
QueryEnhancedRequest queryRequest = QueryEnhancedRequest.builder()
.queryConditional(conditional)
.limit(1)
.build();
return dynamoDbSampleDynamoDbTable.query(queryRequest).items().stream()
.findAny()
.orElseGet(()-> null);
}
public DynamoDbSample findByIdAndDate(String id, String date){
Key key = Key.builder().partitionValue(id).sortValue(date).build();
return dynamoDbSampleDynamoDbTable.getItem(key);
}
public DynamoDbSample update(DynamoDbSample dynamoDbSample){
return dynamoDbSampleDynamoDbTable.updateItem(dynamoDbSample);
}
public void delete(DynamoDbSample dynamoDbSample){
dynamoDbSampleDynamoDbTable.deleteItem(dynamoDbSample);
}
public void deleteByIdAndDate(String id, String date){
Key key = Key.builder().partitionValue(id).sortValue(date).build();
dynamoDbSampleDynamoDbTable.deleteItem(key);
}
}
아래와 같이 Junit 테스트를 해보았고 정상 수행함으로 확인하였다.
@Autowired
DynamoDbSampleRepository dynamoDbSampleRepository;
@Test
void test() {
DynamoDbSample dynamoDbSampleInput = DynamoDbSample.builder()
.id(UUID.randomUUID().toString())
.date(LocalDateTime.now().toString())
.desc("DynamoDb Test")
.build();
dynamoDbSampleRepository.insert(dynamoDbSampleInput);
//DynamoDbSample dynamoDbSampleOutput = dynamoDbSampleRepository.findBy(dynamoDbSampleInput);
DynamoDbSample dynamoDbSampleOutput = dynamoDbSampleRepository.findBy(dynamoDbSampleInput);
DynamoDbSample dynamoDbSampleOutput2 = dynamoDbSampleRepository.findById(dynamoDbSampleOutput.getId());
DynamoDbSample dynamoDbSampleOutput3 = dynamoDbSampleRepository.findByIdAndDate(dynamoDbSampleOutput.getId(), dynamoDbSampleOutput.getDate());
assertThat(dynamoDbSampleOutput.getId()).isEqualTo(dynamoDbSampleInput.getId());
assertThat(dynamoDbSampleOutput.getId()).isEqualTo(dynamoDbSampleOutput2.getId());
assertThat(dynamoDbSampleOutput.getId()).isEqualTo(dynamoDbSampleOutput3.getId());
dynamoDbSampleInput.setDesc("DynamoDb Test2");
DynamoDbSample dynamoDbSampleUpdateOutput = dynamoDbSampleRepository.update(dynamoDbSampleInput);
assertThat(dynamoDbSampleUpdateOutput.getId()).isEqualTo(dynamoDbSampleOutput.getId());
assertThat(dynamoDbSampleUpdateOutput.getDesc()).isNotEqualTo(dynamoDbSampleOutput.getDesc());
dynamoDbSampleRepository.delete(dynamoDbSampleInput);
//dynamoDbSampleRepository.deleteByIdAndDate(dynamoDbSampleInput.getId(), dynamoDbSampleInput.getDate());
DynamoDbSample dynamoDbSampleDeleteOutput = dynamoDbSampleRepository.findBy(dynamoDbSampleOutput);
assertThat(dynamoDbSampleDeleteOutput).isNull();
}