본문 바로가기
IT/Serverless Stack

[AWS] 서버리스 스택-2

by 밤톨엽 2022. 3. 22.

해당 글은 [AWS] 서버리스 스택-1 과 이어집니다.

이번 글에서는 DynamoDB 정의, S3 Bucket 정의, 메모 생성 API 작성 등에 대해서 작성을 하였습니다.


일단 시작하기 앞서서 개인 Github에 Repository를 생성하여 프로젝트 소스를 올리고 시작하자.

프로젝트 루트 경로에서 터미널에 아래 명령어를 실행시켜 개인이 만든 Github Repository와 연결 및 소스를 업로드한다.

git init
git add .
git commit -m "Serverless App 프로젝트 생성"
git branch -M main
git remote add origin "깃헙 주소"
git remote -v
git push -u origin main

Storage Stack 만들기

stacks/StorageStack.ts 경로로 파일을 만들고 DynamoDB를 생성하기 위한 정보를 만들어보자.

// StorageStack.ts
import * as sst from "@serverless-stack/resources";

export default class StorageStack extends sst.Stack {
  // 외부에서 접근할 수 있도록 선언
  table;

  constructor(scope: sst.App, id: string, props?: sst.StackProps) {
    super(scope, id, props);

    // DynamoDB 테이블 생성 (두번째 의자에 본인 이니셜로 ID를 넣어주자)
    this.table = new sst.Table(this, "notes-sykim", {
      dynamodbTable: {
        // 여기서 tableName을 본인 이니셜을 포함해 설정한다.
        tableName: `notes-sykim`,
      },
      fields: {
        userId: sst.TableFieldType.STRING,
        noteId: sst.TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
    });
  }
}

해당 코드는 SST의 Table구성을 사용하여 DynamoDB Table을 생성하는 코드이다.

DynamoDB Table을 생성할 때 두 개의 필드를 선언하는데 정보는 아래와 같다.

  • userId: 사용자 ID
  • noteId: 메모의 고유한 ID

이어서, DynamoDB Table에 대한 primaryIndex를 생성하는데 이것은 DynamoDB Table에 키를 설정하는 것이며 해당 키에 대한 특징은 아래와 같다.

  • DynamoDB 테이블에는 기본 키가 존재한다.
  • 한 번 설정하면 변경할 수 없다.
  • 기본 키는 테이블의 각 항목을 고유하게 식별하므로 두 항목이 동일한 키를 가질 수 없다.
  • DynamoDB는 두 가지 종류의 기본 키를 지원하는데 sortKey는 데이터를 검색할 때 유용하게 사용된다.
partitionKey 한 개만 생성할 수도 있지만 데이터를 쿼리 할 때 유연하게 데이터를 가져오기 위해 sortKey를 추가하여 복합 기본 키를 설정하여 생성한다. 

위처럼 키를 생성하면 DynamoDB에 userId의 값을 보내서 데이터를 조회하면 해당 사용자의 모든 메모를 검색할 수 있으며, 또는 userId, noteId의 두 개의 값을 보내면 특정 메모를 검색할 수 있다.

템플릿 파일 제거

터미널에서 아래 명령어를 실행하여 이전에 만든 Hello World API를 제거하고, stacks/MyStack.ts 파일도 제거한다.

npx sst remove

(실행하는데 1분 정도 소요된다.)


stacks/index.ts 파일을 아래처럼 수정한다.

// stacks/index.ts
import * as sst from "@serverless-stack/resources";
import StorageStack from "./StorageStack";

export default function main(app: sst.App): void {
  // 두번째 인자에 본인 이름이나 이니셜로 생성해주자
  // ex) storage-xxx
  new StorageStack(app, "storage-sykim");
}

터미널에서 아래 명령어를 통해 DynamoDB Table 정보를 AWS에 생성해주자.

npx sst start

명령어 실행이 완료되면 AWS에 DynamoDB Table 생성이 완료된 것이니 Ctrl + C로 실행환경을 종료한다.

S3 Bucket 추가

파일을 업로드하기 위한 저장공간인 S3 Bucket을 추가해야 한다.

이전에서 작업해놓은 stacks/StorageStack.ts 파일에 이어서 S3 Bucket 관련 코드를 추가해주자.

// StorageStack.ts
import * as sst from "@serverless-stack/resources";

export default class StorageStack extends sst.Stack {
  // 외부에서 접근할 수 있도록 선언
  table;
  bucket;

  constructor(scope: sst.App, id: string, props?: sst.StackProps) {
    super(scope, id, props);

    // DynamoDB 테이블 생성 (두번째 의자에 본인 이니셜로 ID를 넣어주자)
    this.table = new sst.Table(this, "notes-sykim", {
      dynamodbTable: {
        // 여기서 tableName을 본인 이니셜을 포함해 설정한다.
        tableName: `notes-sykim`,
      },
      fields: {
        userId: sst.TableFieldType.STRING,
        noteId: sst.TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "userId", sortKey: "noteId" },
    });
    
    // S3Bucket 생성
    this.bucket = new sst.Bucket(this, "Uploads");
  }
}

코드를 추가한 후 아래 명령어를 터미널에서 명령어를 실행시켜 AWS에 생성해주자.

npx sst start

작업이 완료되었으면 Ctrl + C로 종료 후 터미널에서 명령어를 실행시켜 Github에 커밋을 진행해준다.

git add .
git commit -m "Storage Stack 구성"
git push

백엔드 아키텍처

이제 실제 메모 웹 백엔드 API를 만들기에 앞서 구조를 잠깐 살펴본다.

Memo Web App architecture
Memo Web App architecture

위 그림을 보면 아래 사항들을 확인할 수 있다.

  1. 데이터베이스는 공개적으로 노출되지 않으며 Lambda 함수에 의해서만 호출된다.
  2. 사용자는 우리가 생성한 S3 버킷에 직접 파일을 업로드 한다.

일반적으로 파일을 서버에 업로드한 다음 파일 서버로 이동시키는 것이 보편적이다.
하지만 지금 방식은 S3 버킷에 직접 업로드 하는 방식을 택했으며 자세한 것은 파일 업로드쪽에서 살펴보도록 하겠다.

마지막으로 이러한 리소스에 대한 액세스를 보호하는 방법도 추가 할 예정인데 목표는 인증된 사용자만 이러한 리소스에 액세스할 수 있도록 설정을 추가 할 예정이다.

메모 생성 API 추가

매모 생성 API는 메모 정보를 받아와 새 ID와 함께 메모 정보를 데이터베이스에 저장하는 역할이다.

메모 정보는 content필드(메모의 내용)와 attachment필드(업로드된 파일의 URL)를 포함한다.

stacks/ApiStack.ts 파일을 생성하고 아래처럼 코드를 추가해주자.

// stacks/ApiStack.ts
import * as sst from "@serverless-stack/resources";

export default class ApiStack extends sst.Stack {
  // 다른 스택에서 접근할 수 있도록 선언
  api;

  constructor(scope: sst.App, id: string, props?: any) {
    super(scope, id, props);

    const { table } = props;

    // API 생성 (두번째 인자에 본인 이니셜을 포함해 ID를 넣어주자)
    this.api = new sst.Api(this, "api-sykim", {
      defaultFunctionProps: {
        environment: {
          TABLE_NAME: table.tableName,
        },
      },
      routes: {
        "POST /notes": "src/create.main",
      },
    });

    // API가 DynamoDB 테이블에 접근할 수 있도록 권한 설정
    this.api.attachPermissions([table]);

    // API의 EndPoint Url을 노출
    this.addOutputs({
      ApiEndpoint: this.api.url,
    });
  }
}

위에 코드중에 몇가지 특이사항

  • TABLE_NAME 이라는 환경 변수로 DynamoDB Table Name을 전달
    (DynamoDB Table 에서 데이터를 쿼리할려면 이름이 필요하다.)
  • API에 추가하는 첫 번째 경로는 HTTP Method: POST, 경로: /notes 로 설정했다.
    (메모를 작성하는 API로 사용된다.)
  • POST /notes 경로로 동작하는 함수(AWS Lambda)는 src/create.ts 에서 main function에 정의한다.
    (API의 요청 방법과 경로 등을 정의한 것이고, src/create.ts 파일을 만들어 실제 메모 생성 관련 로직을 구현한다.)

API 스택 추가

stacks/index.ts 파일에 아래처럼 코드를 수정하자.

// stacks/index.ts
import * as sst from "@serverless-stack/resources";
import StorageStack from "./StorageStack";
import ApiStack from "./ApiStack";

export default function main(app: sst.App): void {
  // 두번째 인자에 본인 이름이나 이니셜로 생성해주자
  // ex) storage-sykim
  const storageStack = new StorageStack(app, "storage-sykim");

  // 두번째 인자에 본인 이름이나 이니셜로 생성해주자
  // ex) api-sykim
  new ApiStack(app, "api-sykim", {
    table: storageStack.table,
  });
}

메모 생성 기능 추가

메모 생성 기능을 추가를 진행하기 전 몇가지 패키지를 사용하기 위해 터미널에서 아래 명령어를 실행하자.

npm install aws-sdk uuid@7.0.3
  • aws-sdk 를 통해 다양한 AWS 서비스를 가져다 쓸 수 있다.
  • uuid 는 고유 ID를 생성하기 위해 설치한다.

src/create.ts 경로에 파일을 생성하여 메모 생성 로직을 작성하자.

import * as uuid from "uuid"
import AWS from "aws-sdk";

const dynamoDb = new AWS.DynamoDB.DocumentClient();

export async function main(event: any) {
  // JSON으로 데이터를 넘겨 'event.body' 에서 파싱이 필요하다
  const data = JSON.parse(event.body);

  const params = {
    TableName: process.env.TABLE_NAME!,
    Item: {
      // 실제 DynamoDB에 저장되는 정보들
      userId: "123", // 사용자 ID
      noteId: uuid.v1(), // 메모를 생성할 때 생성되는 고유ID (uuid 라이브러리 사용)
      content: data.content, // event.body 쪽에서 받은 정보
      attachment: data.attachment, // event.body 쪽에서 받은 정보
      createdAt: Date.now(), // 생성된 시간
    },
  };

  try {
    // DynamoDB에 데이터 입력
    await dynamoDb.put(params).promise();

    // 사용자에게 응답 Return
    return {
      statusCode: 200,
      body: JSON.stringify(params.Item),
    };
  } catch (e: any) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: e.message }),
    };
  }
}

위에 메모를 생성하는 코드에 대한 정보는 아래와 같다.

  • event.body 는 HTTP POST 요청 본문이 담겨있다.
  • content: 메모를 작성한 내용이 담겨있다.
  • attachment: S3 버킷 에 업로드될 파일의 ​​파일 이름이다.
  • process.env.TABLE_NAME: 환경변수 값으로 이전에 작성한 api Stack에서 추가한 DynamoDB Table Name 값이다.
  • userId: 메모를 작성한 사용자 ID (지금은 임의로 123을 입력, 나중에 인증된 사용자를 기반으로 설정한다.)
  • 저장이 성공하면 사용자에게 HTTP 상태코드와 저장된 정보를, 저장이 실패하면 HTTP 상태 코드와 함께 오류를 반환한다.

변경사항 배포

터미널에서 아래 명령어를 통해 AWS에 리소스를 배포하자.

npx sst start

배포가 완료되면 터미널에서 아래와 비슷한 문구가 나와야한다.

Stack dev-notes-api
  Status: deployed
  Outputs:
    ApiEndpoint: https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com

배포 후에 API를 테스트 하기 위해 위에 나와있는 ApiEndPoint 주소로 HTTP Post 요청을 보내 테스트를 진행한다.

터미널로도 진행 가능 하지만, HTTP 요청을 편리하게 보낼 수 있는 툴들이 많으니 해당 툴을 이용하는 것을 추천한다. 

이제 직접 HTTP POST요청을 만들어 ApiEndPoint에 나와있는 주소와 경로 /note를 더해 테스트를 진행하면 아래처럼 응답이 나와야 한다.
(위에 주소대로 한다면 https://5bv7x0iuga.execute-api.us-east-1.amazonaws.com/notes 로 요청해야함.)

HTTP Post 요청을 보낼 때 실제 클라이언트에서 보내는 것 처럼 HTTP Body에 아래처럼 JSON 데이터를 만들어서 보내야 한다. (이해가 되지 않는다면 반드시 이전에 src/create.ts 파일에 선언한 event.body를 어떻게 사용하는지 살펴봐야 한다.)
{"content":"Hello World","attachment":"hello.jpg"}

요청이 잘 되는지 Body에 위에처럼 데이터를 담아 HTTP Post 요청을 전송한다. 필자는 아래처럼 Postman툴을 사용했다.

postman으로 HTTP Post 요청
postman으로 HTTP Post 요청

{
  "userId":"123",
  "noteId":"a46b7fe0-008d-11ec-a6d5-a1d39a077784",
  "content":"Hello World",
  "attachment":"hello.jpg",
  "createdAt":1629336889054
}

실제로는 한줄로 쭉 데이터가 출력 될 것이다.

noteId의 값을 꼭 기록해놓자 (다음 파트에서 조회 시 사용할 ID값)
기억이 안난다면 직접 AWS에 로그인 후 DynamoDB 서비스에서 조회해야 한다.

여기까지 진행한 프로젝트 구조는 아래와 같아야 한다.

프로젝트 구조
프로젝트 구조


서버리스 스택-2 정리

  • DynamoDB 테이블 생성
  • S3 Bucket 생성
  • 메모 생성 API 작성
  • 메모 생성 API 테스트

이번 문서에서는 위와 같이 메모 생성 API를 만들기 위해 DynamoDB 테이블, S3 Bucket을 생성하고 사용해 로컬에서 직접 메모 생성 API를 테스트 해보았다.

다음 내용은 메모 API 수정, 삭제, 조회 등에 대해서 [AWS] 서버리스 스택-3 파트에서 정리하도록 하겠습니다.

'IT > Serverless Stack' 카테고리의 다른 글

[AWS] 서버리스 스택-4  (0) 2022.04.06
[AWS] 서버리스 스택-3  (0) 2022.03.24
[AWS] 서버리스 스택-1  (0) 2022.03.21