DynamoDB를 이용한 백엔드 구축하기

학습내용


1. Amazon DynamoDB란?

1.1 DynamoDB 핵심 구성 요소

다음은 기본 DynamoDB 구성 요소입니다.

1.2 기본 키


2. Java를 이용한 Amazon DynamoDB 실습

2.1 Eclipse 용 AWS Toolkit에서 DynamoDB 샘플 코드 실행해보기

  1. Eclipse를 시작하고 Eclipse 메뉴에서 파일, 새로 만들기, 기타를 차례대로 선택합니다.

  2. Select a wizard에서 AWS, AWS Java Project, Next를 차례대로 선택합니다.

  3. Create an AWS Java project에서 다음을 수행합니다.

  4. 프로젝트를 생성하려면 Finish를 선택합니다.

  5. 프로그램을 컴파일하고 실행합니다.

2.2 Java와 DynamoDB를 사용하는 AWS Lambda 함수 구현하기

2.2.1 사전준비

2.2.2 DynamoDB 테이블 생성

2.2.3 Lambda 함수 생성

  1. AWS Lambda Java 프로젝트를 Eclipse용 AWS Toolkit을 이용하여 생성한다.
  2. Finish 버튼을 클릭합니다.
  3. Eclipse 프로젝트 탐색기를 사용하여 DynamoDBLambdaJavaProject 프로젝트에서 PuttingPersonHandler.java를 열고, 다음 코드로 바꿉니다.

    import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
    import com.amazonaws.services.dynamodbv2.document.DynamoDB;
    import com.amazonaws.services.dynamodbv2.document.Item;
    import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
    import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
    import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
    import com.amazonaws.services.lambda.runtime.Context;
    import com.amazonaws.services.lambda.runtime.RequestHandler;
    
    public class PuttingPersonHandler implements RequestHandler<Person, String> {
        private DynamoDB dynamoDb;
        private String TABLE_NAME = "People";
        private String REGION = "ap-northeast-2";
    
        @Override
        public String handleRequest(Person input, Context context) {
            this.initDynamoDbClient();
    
            putData(input);
            return "Saved Successfully!!";
        }
    
        private void initDynamoDbClient() {
            AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
                    .withRegion(REGION).build();
             this.dynamoDb = new DynamoDB(client);
        }
    
        private PutItemOutcome putData(Person person) 
                  throws ConditionalCheckFailedException {
                    return this.dynamoDb.getTable(TABLE_NAME)
                      .putItem(
                        new PutItemSpec().withItem(new Item()
                                .withPrimaryKey("id",person.id)
                                .withString("firstName", person.firstName)
                                .withString("lastName", person.lastName)));
                }
    }
    
    class Person {
        public String firstName;
        public String lastName;
        public int id;
    }
    
    1. Lambda에 함수를 업로드하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 [AWS Lambda][Upload function to AWS Lambda]를 차례대로 선택합니다.
  4. [Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.

  5. 새 Lambda 함수 생성을 선택하고 함수 이름(예: PuttingPersonFunction)을 입력합니다.

  6. [Next]를 선택합니다.

  7. 함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.

  8. Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.

  9. Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.

  10. Lambda 함수를 호출하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 AWS Lambda를 선택한 후 Run Function on AWS Lambda(AWS Lambda에서 함수 실행)를 선택합니다.

  11. 입력 상자에서 아래와 같은 JSON 문자열을 입력합니다.

    {
      "id": 1,
      "firstName": "Kwawnoo",
      "lastName": "Lee"
    }
  12. Invoke를 선택하여 Lambda 함수에 입력 데이터를 전송합니다.

  13. 결과를 확인하기 위해서는, DynamoDB 콘솔로 이동합니다.


3. DynamoDB와 AWS Lambda를 이용한 IoT Data 저장 백엔드 구축 실습

사전준비

단계1: DynamoDB 테이블

단계2: Lambda 함수 정의

  1. AWS Lambda 프로젝트를 Eclipse용 AWS Toolkit을 이용하여 생성한다.
  2. Finish 버튼을 클릭합니다.
  3. Eclipse 프로젝트 탐색기를 사용하여 RecordingDeviceDataLambdaJavaProject 프로젝트에서 RecordingDeviceInfoHandler.java를 열고, package 선언 다음의 코드를 아래 코드로 바꿉니다.

    import java.text.SimpleDateFormat;
    import java.util.TimeZone;
    
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
    import com.amazonaws.services.dynamodbv2.document.DynamoDB;
    import com.amazonaws.services.dynamodbv2.document.Item;
    import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
    import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
    import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
    import com.amazonaws.services.lambda.runtime.Context;
    import com.amazonaws.services.lambda.runtime.RequestHandler;
    
    public class RecordingDeviceInfoHandler implements RequestHandler<Thing, String> {
        private DynamoDB dynamoDb;
        private String DYNAMODB_TABLE_NAME = "DeviceData";
    
        @Override
        public String handleRequest(Thing input, Context context) {
            this.initDynamoDbClient();
    
            persistData(input);
            return "Success in storing to DB!";
        }
    
        private PutItemOutcome persistData(Thing thing) throws ConditionalCheckFailedException {
    
            // Epoch Conversion Code: https://www.epochconverter.com/
            SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
            String timeString = sdf.format(new java.util.Date (thing.timestamp*1000));
    
            return this.dynamoDb.getTable(DYNAMODB_TABLE_NAME)
                    .putItem(new PutItemSpec().withItem(new Item().withPrimaryKey("time", thing.timestamp)
                            .withString("temperature", thing.state.reported.temperature)
                            .withString("LED", thing.state.reported.LED)
                            .withString("timestamp",timeString)));
        }
    
        private void initDynamoDbClient() {
            AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion("ap-northeast-2").build();
    
            this.dynamoDb = new DynamoDB(client);
        }
    
    }
    
    class Thing {
        public State state = new State();
        public long timestamp;
    
        public class State {
            public Tag reported = new Tag();
            public Tag desired = new Tag();
    
            public class Tag {
                public String temperature;
                public String LED;
            }
        }
    }
  4. Lambda에 함수를 업로드하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 [AWS Lambda][Upload function to AWS Lambda]를 차례대로 선택합니다.

  5. [Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.

  6. 새 Lambda 함수 생성을 선택하고 함수 이름(예: RecordingDeviceInfoFunction)을 입력합니다.

  7. [Next]를 선택합니다.

  8. 함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.

  9. Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.

  10. Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.

단계3: Lambda 함수 테스트

작성된 Lambda함수가 정상적으로 동작하는 지를 테스트해 보기 위해서 다음 절차를 수행합니다.

  1. Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 AWS Lambda를 선택한 후, Run Function on AWS Lambda를 선택합니다.
  2. 입력 상자에서 다음과 같은 JSON 문자열을 입력합니다.

    {
        "state": {
            "reported": {
                "temperature": "23.0", "LED":"OFF"
            }
        }
    }
  3. Invoke 버튼을 선택하여 Console 창에 다음과 같은 메시지가 출력되는 지 확인합니다.

    ...
    ================== FUNCTION OUTPUT ===================
    "Success in storing to DB!"
    ================== FUNCTION LOG OUTPUT ===============
    ...

단계4: IoT 규칙 설정

  1. AWS IoT 콘솔로 이동하여 탐색 창에서 메시지 라우팅 > 규칙을 선택합니다.
  2. 이전에 생성한 규칙 이름(tempWarningRule)의 체크박스를 선택하고 편집을 클릭합니다.
  3. 규칙 작업 추가를 선택합니다.
  4. [규칙 편집: tempWarningRule] 페이지의 [규칙 작업]항목에서 작업 선택아래에서 Lambda를 선택한 다음, Lambda 함수에서 (RecordingDeviceInfoFunction)를 선택합니다.
  5. 업데이트를 선택합니다.

단계5: 아두이노 연결 테스트

  1. AWS_IoT_DHT11을 다운로드하여 Arduino IDE에서 실행한다.
  2. arduino_secrets.h에서 다음 항목을 사용 환경에 맞도록 수정후, 빌드/업로드 한다.
  3. 아두이노 IDE의 시리얼 모니터를 열고, 접속이 제대로 이루어지는 지 확인해 본다.
  4. DHT-11 센서의 측정값이 26도를 초과하는 경우에 이메일이 수신되는 지 확인한다.
  5. DynamoDB 콘솔로 이동합니다.

4. DynamoDB와 AWS Lambda를 이용한 IoT Data 저장 백엔드 구축 실습 (수정)

이번 실습에서는 3절의 실습 내용을 다음 관점에서 개선해 본다.

  1. 데이터를 저장할 테이블을 파티션 키와 정렬 키로 구성하게 함으로써, 데이블에 저장된 항목을 정렬 키를 기준으로 정렬 시킬 수 있게 한다.

  2. 디바이스로부터 수신된 모든 정보를 테이블에 저장하지 않고, 이전에 수신된 정보와 차이가 나는 경우에만 테이블에 저장하도록 수정한다.

  3. DB 테이블에 정보를 저장하는 규칙(LoggingRule)을 독립적인 규칙으로 분리하여, 해당 규칙을 필요에 따라서 활성화 혹은 비활성화 시킬 수 있도록 수정한다.


단계1: DynamoDB 테이블


단계2: Lambda 함수 정의

  1. AWS Lambda 프로젝트를 Eclipse용 AWS Toolkit을 이용하여 생성한다.
  2. Finish 버튼을 클릭합니다.
  3. Eclipse 프로젝트 탐색기를 사용하여 RecordingDeviceDataLambdaJavaProject2 프로젝트에서 RecordingDeviceInfoHandler2.java를 열고, 다음 코드로 바꿉니다.

    import java.text.SimpleDateFormat;
    import java.util.TimeZone;
    
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
    import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
    import com.amazonaws.services.dynamodbv2.document.DynamoDB;
    import com.amazonaws.services.dynamodbv2.document.Item;
    import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
    import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
    import com.amazonaws.services.lambda.runtime.Context;
    import com.amazonaws.services.lambda.runtime.RequestHandler;
    
    public class RecordingDeviceInfoHandler2 implements RequestHandler<Document, String> {
        private DynamoDB dynamoDb;
        private String DYNAMODB_TABLE_NAME = "Logging";
    
        @Override
        public String handleRequest(Document input, Context context) {
            this.initDynamoDbClient();
            context.getLogger().log("Input: " + input);
    
            //return null;
            return persistData(input);
        }
    
        private String persistData(Document document) throws ConditionalCheckFailedException {
    
            // Epoch Conversion Code: https://www.epochconverter.com/
            SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
            String timeString = sdf.format(new java.util.Date (document.timestamp*1000));
    
            // temperature와 LED 값이 이전상태와 동일한 경우 테이블에 저장하지 않고 종료 
            if (document.current.state.reported.temperature.equals(document.previous.state.reported.temperature) && 
                    document.current.state.reported.LED.equals(document.previous.state.reported.LED)) {
                    return null;
            }
    
            return this.dynamoDb.getTable(DYNAMODB_TABLE_NAME)
                    .putItem(new PutItemSpec().withItem(new Item().withPrimaryKey("deviceId", document.device)
                            .withLong("time", document.timestamp)
                            .withString("temperature", document.current.state.reported.temperature)
                            .withString("LED", document.current.state.reported.LED)
                            .withString("timestamp",timeString)))
                    .toString();
        }
    
        private void initDynamoDbClient() {
            AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion("ap-northeast-2").build();
    
            this.dynamoDb = new DynamoDB(client);
        }
    
    }
    
    /**
     * AWS IoT은(는) 섀도우 업데이트가 성공적으로 완료될 때마다 /update/documents 주제에 다음 상태문서를 게시합니다
     * JSON 형식의 상태문서는 2개의 기본 노드를 포함합니다. previous 및 current. 
     * previous 노드에는 업데이트가 수행되기 전의 전체 섀도우 문서의 내용이 포함되고, 
     * current에는 업데이트가 성공적으로 적용된 후의 전체 섀도우 문서가 포함됩니다. 
     * 섀도우가 처음 업데이트(생성)되면 previous 노드에는 null이 포함됩니다.
     * 
     * timestamp는 상태문서가 생성된 시간 정보이고, 
     * device는 상태문서에 포함된 값은 아니고, Iot규칙을 통해서 Lambda함수로 전달된 값이다. 
     * 이 값을 해당 규칙과 관련된 사물이름을 나타낸다. 
     */
    class Document {
        public Thing previous;       
        public Thing current;
        public long timestamp;
        public String device;       // AWS IoT에 등록된 사물 이름 
    }
    
    class Thing {
        public State state = new State();
        public long timestamp;
        public String clientToken;
    
        public class State {
            public Tag reported = new Tag();
            public Tag desired = new Tag();
    
            public class Tag {
                public String temperature;
                public String LED;
            }
        }
    }
  4. Lambda에 함수를 업로드하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 [AWS Lambda][Upload function to AWS Lambda]를 차례대로 선택합니다.

  5. [Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.

  6. 새 Lambda 함수 생성을 선택하고 함수 이름(예: RecordingDeviceInfoFunction2)을 입력합니다.

  7. [Next]를 선택합니다.

  8. 함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.

  9. Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.

  10. Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.


단계3: IoT 규칙 설정

  1. AWS IoT 콘솔로 이동하여 탐색 창에서 Act (액트)을 선택합니다.
  2. 새로운 규칙을 생성하기 위해 생성을 클릭합니다.
  3. 이름LoggingRule을 입력합니다.
  4. 규칙 쿼리 설명문에 다음을 입력합니다.

    SELECT *, 'MyMKRWiFi1010' as device FROM '$aws/things/MyMKRWiFi1010/shadow/update/documents'
  5. 작업 추가를 선택합니다.

  6. 작업 선택 아래에서 메시지 데이터를 전달하는 Lambda 함수 호출 (Send a message to a Lambda function)을 선택한 다음 작업 구성을 선택합니다.

  7. 선택을 클릭하고, Lambda 함수(RecordingDeviceInfoFunction2)를 선택합니다.

  8. 작업 추가를 선택합니다.

  9. [규칙생성] 페이지에서 규칙 생성 버튼을 클릭한다.

  10. LoggingRule 규칙이 목록에 표시되는 것을 확인한다.


단계4: 테스트

  1. AWS_IoT_DHT11을 다운로드하여 Arduino IDE에서 실행한다.
  2. arduino_secrets.h에서 다음 항목을 사용 환경에 맞도록 수정후, 빌드/업로드 한다.
  3. 아두이노 IDE의 시리얼 모니터를 열고, 접속이 제대로 이루어지는 지 확인해 본다.
  4. DHT-11 센서의 측정값이 26도를 초과하는 경우에 이메일이 수신되는 지 확인한다.
  5. DynamoDB 콘솔로 이동합니다.