Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Go 언어를 이용해서 Dynamodb에 CRUD하는 방법을 살펴볼 것이다.

개발 환경 및 준비

  • 우분투 리눅스 19.04
  • Go version 1.12
  • AWS Cloud 서울 리전
  • 데이터베이스 : Amazon DynamoDB
DynamoDB는 AWS의 관리형 NoSQL 데이터베이스다. 서버가 필요 없는 서버리스(ServerLess) 환경을 제공한다. 개발자는 가용성, 확장, 성능 등에 대한 고민 없이 개발 할 수 있다.

개발자는 AWS DynamoDB에 접근 할 수 있는 Credential을 가지고 있어야 한다. IAM - AWS Identity and Access Management문서를 참고하자.

테이블 스키마

테스트를 위해서 아래와 같은 테이블을 만들었다. 앨범정보를 정보를 저장하기 위한 테이블이다. 테이블 이름은 Album, 파일이름은 schema.json 이다.
{
    "TableName" : "Album",
    "KeySchema": [       
        { 
            "AttributeName": "artist", 
            "KeyType": "HASH"
        },
        { 
            "AttributeName": "title", 
            "KeyType": "RANGE"
        }
    ],
    "AttributeDefinitions": [
        { 
            "AttributeName": "artist", 
            "AttributeType": "S" 
        },
        { 
            "AttributeName": "title", 
            "AttributeType": "S" 
        }
    ],
    "ProvisionedThroughput": {
        "ReadCapacityUnits": 1, 
        "WriteCapacityUnits": 1
    }
}

aws cli를 이용해서 테이블을 만들었다. DynamoDB는 스키마에 대한 변경이 자유롭기 때문에, 기본키(파티션키와 정렬키) 정도만 설정하면 입력은 형식에 자유롭게 사용 할 수 있다.
# aws dynamodb create-table --cli-input-json file://schema.json

Album 테이블이 만들어졌는지 확인해 보자.
# aws dynamodb list-tables   
{
    "TableNames": [
        "Album", 
        "ToDo"
    ]
}

Put Item

입력하려고 하는 데이터는 아래와 같다.
{
   "title" : "title",
   "artist" : "yundream",
   "createat" : "2020.01.05",
   "tracklist" : [
      {
         "number" : 1,
         "playtime" : 185,
         "Name" : "Hello world"
      },
      {
         "number" : 2,
         "playtime" : 212,
         "Name" : "Wonderful world"
      },
      {
         "number" : 3,
         "Name" : "Hell of Fire",
         "playtime" : 198
      }
   ]
}

테스트를 위해서 아래와 같은 structure를 만들었다.
type Album struct {
    Artist    string  `json:"artist"`
    Title     string  `json:"title"`
    Createat  string  `json:"createat"`
    TrackList []Track `json:"tracklist"`
}

type Track struct {
    Number   int    `json:"number"`
    Playtime int    `json:"playtime"`
    Name     string `json:"Name"`
}

JSON 데이터를 dynamoDB 속성 데이터로 변경하기

DynamoDB는 MongoDB처럼 JSON을 입/출력 데이터 형식으로 사용한다. 하지만 입력된 데이터를 그대로 사용하지 않고, DynamoDB에 맞게 변환한다. 아래와 같은 데이터가 입력된다고 가정해 보자.
{
    "id" : "yundream",
    "email" : "yundream@gmail.com",
    "point" : 17.5 
}
DynamoDB 에 저장하기 위해서는 아래와 같은 속성정보를 포함하는 형태로 변환해야 한다.
{
    "Item": {
        "id": {
            "S": "yundream" 
        },
        "email": {
            "S": "yundream@gmail.com" 
        },
        "point": {
            "N": 17.5
        }
    }
}
Go 언어에서는 map[string]*dynamodb.AttributeValue로 위 형태를 표현한다. dynamodbattribute.MarshalMap(in interface{})메서드를 이용해서 변환 할 수 있다. 아래 코드를 보자.
package main

import (
    "encoding/json"
    "fmt"
    "os"

    "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
)

var sampleData = `
{
    "id" : "yundream",
    "email" : "yundream@gmail.com",
    "point" : 17.5 
}
`

func main() {
    type Sample struct {
        Id    string  `json:"id"`
        Emaim string  `json:"email"`
        Point float64 `json:"point"`
    }

    in := Sample{}

    err := json.Unmarshal([]byte(sampleData), &in)
    if err != nil {
        fmt.Println("IN : ", err.Error())
        os.Exit(1)
    }

    inputMap, err := dynamodbattribute.MarshalMap(in)
    if err != nil {
        fmt.Println("IN : ", err.Error())
        os.Exit(1)
    }
    fmt.Printf("%#v\n", inputMap)
}
코드를 실행해보자.
map[string]*dynamodb.AttributeValue{"email":{
  S: "yundream@gmail.com"
}, "id":{
  S: "yundream"
}, "point":{
  N: "17.5"
}}
Sample struct를 map으로 변환한 걸 확인 할 수 있다. 이 데이터를 dynamodb.PutItem로 입력하면 된다. 아래는 작동하는 완전한 코드다.
package main

import (
	"encoding/json"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"os"
)

type Album struct {
	Artist    string  `json:"artist"`
	Title     string  `json:"title"`
	Createat  string  `json:"createat"`
	TrackList []Track `json:"tracklist"`
}

type Track struct {
	Number   int    `json:"number"`
	Playtime int    `json:"playtime"`
	Name     string `json:"Name"`
}

var testData = `
{  
   "title" : "title",
   "artist" : "yundream",
   "createat" : "2020.01.05",
   "tracklist" : [
      {
         "number" : 1,
         "playtime" : 185,
         "Name" : "Hello world"
      },
      {
         "number" : 2,
         "playtime" : 212,
         "Name" : "Wonderful world"
      },
      {
         "number" : 3,
         "Name" : "Hell of Fire",
         "playtime" : 198
      }
   ]
}
`

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("ap-northeast-2"),
	})
	if err != nil {
		os.Exit(1)
	}
	svc := dynamodb.New(sess)

	in := Album{}

	err = json.Unmarshal([]byte(testData), &in)
	if err != nil {
		fmt.Println("IN : ", err.Error())
		os.Exit(1)
	}

	item, err := dynamodbattribute.MarshalMap(in)
	if err != nil {
		fmt.Println("IN : ", err.Error())
		os.Exit(1)
	}
	input := &dynamodb.PutItemInput{
		Item:      item,
		TableName: aws.String("Album"),
	}
	_, err = svc.PutItem(input)
	if err != nil {
		fmt.Println("input error : ", err.Error())
		os.Exit(1)
	}
}
데이터가 성공적으로 입력했는지 확인해 보자. 웹 콘솔 대신 AWS cli로 확인했다. 내용 일부는 생략했다.
# aws dynamodb scan --table-name Album
{
    "Count": 1, 
    "Items": [
        {
            "createat": {
                "S": "2020.01.05"
            }, 
            "tracklist": {
                "L": [
                    {
                        "M": {
                            "playtime": {
                                "N": "185"
                            }, 
                            "number": {
                                "N": "1"
                            }, 
                            "Name": {
                                "S": "Hello world"
                            }
                        }
                    }
                ]
            }, 
            "artist": {
                "S": "yundream"
            }, 
            "title": {
                "S": "title"
            }
        }
    ], 
    "ScannedCount": 1, 
    "ConsumedCapacity": null
}

GetItem

저장된 아이템을 가져오는 코드다.
package main

import (
	"encoding/json"
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"os"
)

type Album struct {
	Artist    string  `json:"artist"`
	Title     string  `json:"title"`
	Createat  string  `json:"createat"`
	TrackList []Track `json:"tracklist"`
}

type Track struct {
	Number   int    `json:"number"`
	Playtime int    `json:"playtime"`
	Name     string `json:"Name"`
}

func main() {
	sess, err := session.NewSession(&aws.Config{
		Region: aws.String("ap-northeast-2"),
	})
	if err != nil {
		os.Exit(1)
	}
	svc := dynamodb.New(sess)

	in := Album{Artist: "yundream", Title: "title"}

	_, err = dynamodbattribute.MarshalMap(in)
	if err != nil {
		os.Exit(1)
	}

	params := &dynamodb.GetItemInput{
		TableName: aws.String("Album"),
		Key: map[string]*dynamodb.AttributeValue{
			"artist": {
				S: aws.String("yundream"),
			},
			"title": {
				S: aws.String("title"),
			},
		},
	}

	result, err := svc.GetItem(params)
	if err != nil {
		fmt.Println("GetItem ", err.Error())
		os.Exit(1)
	}

	album := Album{}
	dynamodbattribute.UnmarshalMap(result.Item, &album)
	jsonStr, _ := json.Marshal(album)
	fmt.Println(string(jsonStr))
}
Dynamodb.GetItem()는 GetItemOutput struct를 반환한다.
type GetItemOutput struct {
    _ struct{} `type:"structure"`
    
    // The capacity units consumed by the GetItem operation. The data returned includes
    // the total provisioned throughput consumed, along with statistics for the
    // table and any indexes involved in the operation. ConsumedCapacity is only
    // returned if the ReturnConsumedCapacity parameter was specified. For more
    // information, see Read/Write Capacity Mode (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughputIntro.html)
    // in the Amazon DynamoDB Developer Guide.
    ConsumedCapacity *ConsumedCapacity `type:"structure"`

    // A map of attribute names to AttributeValue objects, as specified by ProjectionExpression.
    Item map[string]*AttributeValue `type:"map"`
}                                                                                                           
읽은 아이템은 map[string]*AttributeValue 타입이다. 애플리케이션을 만든다면 JSON으로 반환해야 할테니, dynamodbattribute.UnmarshalMap 메서드와 json.Marshal을 이용해서 JSON 형식으로 변환했다. 출력 값은 아래와 같다.
# go run main.go | json_pp
{
   "artist" : "yundream",
   "tracklist" : [
      {
         "Name" : "Hello world",
         "playtime" : 185,
         "number" : 1
      },
      {
         "playtime" : 212,
         "Name" : "Wonderful world",
         "number" : 2
      },
      {
         "playtime" : 198,
         "Name" : "Hell of Fire",
         "number" : 3
      }
   ],
   "title" : "title",
   "createat" : "2020.01.05"
}