[DevOps] Amazon DynamoDB 간단한 CRUD 예제(Kotlin)

Doccimann·2022년 5월 8일
0

AWS

목록 보기
2/2

🔥 우선은 테이블을 만들고 시작하겠습니다

일단은 aws console에서 dynamoDB를 검색해서 dynamoDB 서비스로 이동합니다

그리고 테이블을 생성해주면 되는데요, 저는 테이블의 partition key를 shop_name, sort key를 branch로 설정해서 테이블을 만들 계획입니다.

테이블을 생성하실 때 다음의 설정 옵션이 제공되는데요, 기본 설정과 설정 사용자 지정 옵션은 다음의 특징을 가집니다

  • 기본 설정 : 별도의 쓰기 용량, 읽기 용량을 설정하지 않고 테이블을 즉시 생성하는 기능. 테이블 클래스를 범용 테이블 클래스로 지정하고, 사용자의 사용 용량에 따른 비용만 지불하면 되는 옵션이다
  • 사용자 지정 옵션 : 사용자가 스스로 테이블의 클래스와 사용 용량을 직접 지정하는 옵션이다. 사용자가 운영하고자 하는 서비스의 트래픽 용량이 예측 가능한 경우 권장되는 옵션이다

저는 간단한 예제만 구현할 것이며, 예상되는 트래픽은 0에 가깝기 때문에 기본 설정으로 테이블을 생성하도록 하겠습니다.

테이블을 생성하고나면 아래와 같이 dynamoDB table 콘솔에 뜰겁니다!

이제부터 예제를 작성하도록 하겠습니다!


🔨 이제 예제를 작성하기 시작할겁니다.

우선은 DynamoDB Query API를 사용하기 위해서는 해당 계정의 테이블에 접근하기 위한 권한 설정이 필요합니다. 따라서 일단은 iam을 통해서 사용자를 하나 만든 다음에, 그리고 프로젝트를 생성하여 예제를 작성하는 순서로 진행하겠습니다.

👉 일단 IAM을 이용해서 DynamoDB Full Acccess 권한을 가진 하나의 사용자를 만들겠습니다

우선 아마존 콘솔에서 IAM으로 들어가서 사용자 추가를 해줍니다.

저희는 프로그램을 통해서 테이블을 제어할 계획이기 때문에 AWS 자격 증명 유형 선택에서 액세스 키 - 프로그래밍 방식 액세스 를 선택하겠습니다.

그리고 사용자 추가에서 DynamoDBFullAccess 권한을 선택하여 정책을 연결합니다.

이후에 추가적인 과정들을 거쳐서 해당 권한을 가진 사용자의 Access key, Secret Key를 복사해서 보관하면 되겠습니다.

🚨 IAM을 다룰때는 액세스키, 시크릿키의 유출을 정말로 조심해야합니다
Access Key, Secret Key의 경우에는 절대로 외부에 유출을 시켜서는 안됩니다. 예를 들어서, 프로젝트 작성 과정에서 권한 정보를 config에 하드코딩을 한 상태로 깃헙에 push를 날려서는 안 된다는 뜻입니다.
Access Key, Secret Key가 만일 해커의 손에 넘어가게 된다면 해당 계정의 권한을 이용해서 코인을 채굴한다는 등의 행위를 통해 심각한 금전적 손해 를 입을 수 있습니다.
사례 : AWS 해킹 당해서 사용료 3억이 넘게 나왔습니다. 제발 도와주세요ㅠㅠㅠ

👉 다음으로 프로젝트의 build.gradle.kts에 의존성을 추가하겠습니다

build.gradle.kts에 아래와 같이 의존성을 부여하겠습니다.

DynamoDB Mapper 기능을 이용하기 위해서 java 버전의 sdk를 가져온 모습을 확인할 수 있습니다!

👉 다음으로 DynamoDBConfiguration을 작성하겠습니다

일단 코드부터 확인해봅시다. 이거는 외우지 않으셔도 되고, 사용하고 싶을때마다 복사해서 사용하시면 되겠습니다. 이거까지 도대체 어떻게 외움!

@Configuration
class DynamoDbConfiguration {

    val accessKey = "Your Access Key"
    val secretKey = "Your Secret Key"

    @Bean
    fun dynamoDbMapper(): DynamoDBMapper = DynamoDBMapper(buildAmazonDynamoDB())

    private fun buildAmazonDynamoDB(): AmazonDynamoDB = AmazonDynamoDBClientBuilder
        .standard()
        .withEndpointConfiguration(
            AwsClientBuilder.EndpointConfiguration(
                "dynamodb.ap-northeast-2.amazonaws.com",
                "ap-northeast-2"
            )
        )
        .withCredentials(
            AWSStaticCredentialsProvider(
                BasicAWSCredentials(accessKey, secretKey)
            )
        ).build()
}

일단 저희는 DynamoDBMapper를 이용하기 위해서 dynamoDbMapper()를 Bean으로 등록해줍니다.

그리고 DynamoDB에 액세스를 하기 위해서 권한 정보를 등록해줘야하는데요. 과정은 다음과 같습니다.


1. EndPointConfiguration에 다이나모디비의 Endpoint 정보를 기입합니다. 이에 해당하는 정보는 DynamoDB가 위치한 리전 정보, 그리고 해당 dynamoDB의 엔드포인트 주소를 기입합니다
2. Credential 정보를 기입합니다. Credential 정보에는 accessKey, secretKey를 기입하면 됩니다

실제 프로젝트를 작성할 때에는 accessKey, Secret Key는 매우 예민한 정보이기 때문에 config store를 private subnet에 올린 상태로 config store로부터 메시지를 발행받아서 사용하는게 옳은 행위입니다

👉 다음으로 엔티티 객체를 만들겠습니다

이번 프로젝트에서는 shop table만 다룰 생각입니다. shop에 대응하는 클래스는 다음과 같습니다.

@DynamoDBTable(tableName = "shop")
data class Shop(
    @DynamoDBHashKey(attributeName = "shop_name")
    @JsonProperty("shop_name")
    var shopName: String = "",
    @DynamoDBRangeKey(attributeName = "branch")
    var branch: String = "",
    @DynamoDBAttribute(attributeName = "description")
    var description: String = ""
)

저희는 클래스를 작성할 때 Partition Key로 shop_name을, sort key로 branch를 설정하였기 때문에 위와 같이 작성을 하였습니다.

그리고 DynamoDB의 Attribute에 대해서는 자유도가 높기 때문에 원하는 속성을 부여해서 사용을 해주면 되겠습니다. 저는 여기서 description만을 Attribute로 사용하도록 하겠습니다.

👉 Repository를 작성합시다

Repository는 create, read, delete만 구현하겠습니다. DynamoDB의 경우 update 방식으로 특정 attribute만을 갈아끼우는 방식이 아니라, 해당 item 자체를 갈아끼우는 방식으로 동작하기 때문입니다.

@Repository
class ShopRepository(private val dynamoDBMapper: DynamoDBMapper) {

    fun saveShop(shop: Shop): Shop {
        dynamoDBMapper.save(shop)

        return shop
    }

    fun findShopByShopNameAndBranch(shopName: String, branch: String): Shop? = dynamoDBMapper.load(Shop::class.java, shopName, branch)

    fun delete(shopName: String, branch: String) {
        val shop = findShopByShopNameAndBranch(shopName, branch)

        dynamoDBMapper.delete(shop)
    }
}

dynamoDbMapper를 이용해서 Repository를 구현한 모습입니다.

👉 Service layer, Controller layer를 작성하도록 하겠습니다

여기에 대해서는 설명을 생략하도록 하겠습니다. 별도의 특별한 로직이 존재하지 않고, Service에서는 repository를 주입받고, Controller에서는 Service를 주입받고 간단하게 구현만 하기 때문입니다.

ShopApiService.kt

@Service
class ShopApiService(private val shopRepository: ShopRepository) {

    fun createShop(shop: Shop): Shop = shopRepository.saveShop(shop)

    fun findShopByNameAndBranch(shopName: String, branch: String) = shopRepository.findShopByShopNameAndBranch(shopName, branch)

    fun deleteShopByNameAndBranch(shopName: String, branch: String) = shopRepository.delete(shopName, branch)
}

ShopApiController.kt

@RestController
@RequestMapping("/api/shop")
class ShopApiController(private val shopApiService: ShopApiService) {

    @PostMapping("/")
    fun createShop(@RequestBody shop: Shop) = shopApiService.createShop(shop)

    @GetMapping("/get")
    fun findShopByName(@RequestParam(name = "name") shopName: String, @RequestParam(name="branch") branch: String) = shopApiService.findShopByNameAndBranch(shopName, branch)

    @DeleteMapping("/delete")
    fun deleteShopByName(@RequestParam(name = "name") shopName: String, @RequestParam(name="branch") branch: String) = shopApiService.deleteShopByNameAndBranch(shopName, branch)
}

이번 예제에서 사용하는 테이블은 Partition Key, Sort Key를 조합하여 PK로 사용하기 때문에 하나의 item을 탐색해서 가져오기 위해서는 둘의 정보를 함께 전달해야합니다. 따라서 findShopByName, deleteShopByName에서는 shopName, branch를 파라미터로 전달받습니다.


🌲 글 마무리

여기까지 DynamoDB를 이용한 CRUD 구현 예제를 작성해보았습니다. DynamoDB를 이용하면 이번에 저희 팀에서 진행중인 프로젝트에서 빠른 반응을 요구하는 로직을 처리할 수 있을 것으로 기대됩니다.

다음 포스트에서는 저희 프로젝트에서 어떻게 DynamoDB를 활용할 것인지에 대해서 다뤄볼 예정입니다.

다음 포스트에서 뵙겠습니다! 감사합니다.

profile
Hi There 🤗! I'm college student majoring Mathematics, and double majoring CSE. I'm just enjoying studying about good architectures of back-end system(applications) and how to operate the servers efficiently! 🔥

0개의 댓글