AWS 기술 블로그

AWS Amplify 애플리케이션을 AWS CDK와 함께 확장하기

AWS Amplify는 AWS에서 클라우드 기반 모바일 및 웹 앱을 구축하는 가장 빠르고 쉬운 방법입니다. Amplify는 프런트엔드 웹 및 모바일 개발자가 AWS 서비스를 활용하여 다양한 기능과 혁신적인 애플리케이션을 구축할 수 있도록 하는 툴과 서비스로 구성됩니다. AWS Amplify CLI는 프런트엔드 개발자가 클라우드에서 앱의 백엔드를 생성하는데 도움이 되는 명령 줄 도구 체인입니다.

AWS Amplify를 사용하여 직접 애플리케이션과 백앤드를 만들 수 있지만, 생성할 수 있는 백앤드는 인증, 저장, API 등과 같은 카테고리 별로 나눠저 있으며 지속적으로 기능들과 서비스들이 추가되고 있습니다. 제한적인 리소스만 생성이 가능했기 때문에 필요한 리소스를 추가하기 위해 , AWS Cloud Development Kit (CDK)를 함께 사용하여 Amplify에서 애플리케이션을 동작하는데 필요한 인프라 및 리소스 구성이 가능합니다. AWS CDK로 인프라를 배포하면 DevOps 팀이 다음을 수행할 수 있습니다. 인프라 구성 요소의 표준화와 반복 가능한 방식으로 배포하고 친숙한 프로그래밍 언어로 개발합니다. 웹사이트 호스팅 서비스를 제공하는 관리형 서비스 제공 업체는 다양한 고객에 걸쳐 여러 Amplify 애플리케이션을 자동으로 배포하고 관리하는 이점도 누릴 수 있습니다.
최근 AWS Amplify는 새로운 가능을 발표했습니다. amplify add custom 명령을 사용하면 AWS Cloud Development Kit (CDK) 또는 AWS CloudFormation을 사용하여 Amplify 백엔드에 175개 이상의 AWS 서비스를 추가합니다. 사용자 지정 리소스를 추가하는 새로운 기능을 통해 개발자는 단일 명령으로 Amplify에서 사용할 수 있는 기본적인 리소스 외에 추가 리소스를 추가할 수 있습니다.
이 블로그에서는 Amplify Client Configuration 과 Custom AWS Resource를 이용하는 두 가지 방법에 대해서 보여드리고자 합니다.

전제 조건

AWS Amplify와 AWS CDK를 사용하여 애플리케이션을 배포하려면 다음을 설정해야 합니다.

  • 노드 패키지 관리자(NPM)는 여기를 참고하여 설치하세요.
  • AWS CLI를 관리자 권한으로 설치구성

AWS Amplify

amplify add custom 명령을 사용하려면 버전 7 이상이 필요합니다. 설치하려면 아래 명령을 실행합니다.

npm i -g @aws-amplify/cli

Amplify CLI가 구성되어 있어야 합니다. Amplify CLI를 아직 구성하지 않은 경우 설명서 페이지에서 이 가이드를 참조하세요.

AWS CDK

CLI 명령 cdk인 AWS CDK 도구 키트는 AWS CDK 앱과 상호 작용하기 위한 기본 도구입니다. 대상 AWS 환경에 CDK 코드를 배포하는 데 사용됩니다. 설치하려면 아래 명령을 사용하십시오.

npm install -g aws-cdk

구성 및 자격 증명 파일 설정

블로그의 내용을 수행을 위해서는 IAM User(Access, Secret Key) 설정이 필요합니다. 아직 구성하지 않은 경우 설명서 페이지에서 이 가이드를 참조하세요.

이메일 구독 애플리케이션

먼저, AWS Amplify 버전 7 이상을 기준으로 AWS Amplify와 AWS CDK를 사용하여 애플리케이션 백앤드를 구성하는 방법을 알아보겠습니다. 이 블로그에서 만들어볼 애플리케이션은 이메일 구독을 신청하는 간단한 웹사이트와 이메일을 추가할 때 메시지를 전달하는 애플리케이션으로 전체 아키텍처는 아래 그림과 같습니다.

React 및 Amplify 프로젝트 초기화

새 디렉토리 생성을 실행하고 Amplify 프로젝트를 초기화합니다.

npx create-react-app amplify-add-custom
cd amplify-add-custom
amplify init -y

이메일 목록 항목을 저장하도록 앱 데이터 모델 구성

Amplify는 앱 백엔드 개발을 쉽게 할 수 있습니다. Amplify 프로젝트가 초기화된 후 , amplify add api 명령을 이용해 Amazon DynamoDB에서 지원하는 GraphQL API와 같은 백엔드 리소스를 쉽게 추가할 수 있습니다.

amplify add api

이 블로그의 목적을 위해 모두 기본값으로 선택하였습니다. 앱의 schema.graphql 파일에 다음 데이터 모델을 사용합니다.

[프로젝트]/amplify/backend/api/amplifyaddcustom/schema.graphql

type SubscriptionList @model  @auth(rules: [{ allow: public }]) { #Creates a database for Subscription List 
  id: ID!
  email: String
}

type Mutation {
  sendSubscriptionEmail: Boolean @function(name: "sendSubscriptionEmail-${env}")
}

다음 명령을 실행하여 앱의 백앤드를 배포합니다.

amplify push -y

Custom AWS Resources

새롭게 출시된 Custom AWS Resource를 이용한 방법입니다. 사용자 지정 AWS 리소스를 추가하려면 최신 Amplify CLI(버전 7 이상)를 사용해야 합니다. Amplify 프로젝트에서 “amplify add custom”을 실행할 수 있고, 이 명령은 CDK 또는 CloudFormation 스택이 제공됩니다.

Amazon SNS 주제를 Amplify 프로젝트에 사용자 지정 AWS 리소스로 추가

이제 이메일 발송을 위해 Amazon SNS 주제를 생성합니다. SNS 주제는 메시지를 수신할 때마다 미리 지정된 이메일 주소로 메시지를 전달합니다. SNS 주제 추가는 현재 Amplify에 기본 제공되지 않기때문에 다음 명령을 실행하여 사용자 지정 AWS 리소스를 추가할 수 있습니다.

amplify add custom

데모 앱의 경우 AWS CDK를 사용하여 사용자 지정 AWS 리소스를 정의합니다. 하지만 CloudFormation에서도 이를 쉽게 정의할 수 있습니다. Amplify CLI는 모든 사용자 지정 리소스 정의를 포함하는 새로운 “cdk-stack.ts” 파일(파일 위치 : [프로젝트]/amplify/backend/custom/customResourcexxxx)을 엽니다.

“cdk-stack.ts” 파일을 편집하여 SNS 주제와 이메일 구독을 생성해 보겠습니다. (아래 코드에서  <YOUR_EMAIL_ADDRESS_HERE>를 수정하세요)

import * as cdk from '@aws-cdk/core';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import * as sns from '@aws-cdk/aws-sns';
import * as subs from '@aws-cdk/aws-sns-subscriptions';

export class cdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
    super(scope, id, props);
    /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
    new cdk.CfnParameter(this, 'env', {
      type: 'String',
      description: 'Current Amplify CLI env name',
    });
    
    // Create the SNS topic 
    const topic = new sns.Topic(this, 'sns-topic', {
      // Reference the Amplify env to ensure multi-env workflow function correctly
      topicName: `sns-topic-${AmplifyHelpers.getProjectInfo().projectName}-${cdk.Fn.ref('env')}`
    });

    // Add an email subscription
    topic.addSubscription(new subs.EmailSubscription("<YOUR_EMAIL_ADDRESS_HERE>"));
  }
}

아직까지 AWS Amplify에서 지원하는 Custom Resource로 지원하는 CDK 버전은 v1 으로 만들어야만 합니다. 이제, 앱의 백엔드 배포합니다.

amplify push -y

이메일 구독이 설정되면 수신을 위해서 구독을 확인해야만 합니다. Confirm subscription 을 클릭하여 구독을 확인하세요.

API를 처리할 Lambda 함수 구성

이제 남은 것은 이메일 발송을 위해서 Lambda 함수를 구성하는 것입니다. 다음 명령어로 람다 함수를 생성합니다.

GraphQL API의 Mutation에 액세스할 수 있는 Node.js 함수를 생성하면서 주의할 사항은 함수 이름을 GraphQL 스키마에 정의한대로 “sendSubscriptionEmail“를 사용해야만 합니다.

? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: sendSubscriptionEmail
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the categories you want this function to have access to. api
? Select the operations you want to permit on amplifyaddcustom Mutation

You can access the following resource attributes as environment variables from your Lambda function
        API_AMPLIFYADDCUSTOM_GRAPHQLAPIENDPOINTOUTPUT
        API_AMPLIFYADDCUSTOM_GRAPHQLAPIIDOUTPUT
        API_AMPLIFYADDCUSTOM_GRAPHQLAPIKEYOUTPUT
        ENV
        REGION
? Do you want to invoke this function on a recurring schedule? No
? Do you want to enable Lambda layers for this function? No
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? No
Successfully added resource sendSubscriptionEmail locally.

Lambda 함수의 코드를 편집하기 전에 디렉터리로 이동하여 다음을 추가합니다. GraphQL API를 더 쉽게 호출할 수 있도록 종속성(depenency)  “node-fetch“를 추가합니다.

cd amplify/backend/function/sendSubscriptionEmail/src
npm install node-fetch@2

Lambda 함수의 코드를 편집하기 전에 디렉터리로 이동하여 다음을 추가합니다. GraphQL API를 더 쉽게 호출할 수 있도록 종속성(depenency)  “node-fetch“를 추가합니다.

[프로젝트]/amplify/backend/function/sendSubscriptionEmail/src/index.js

Lambda 함수 소스 index.js 파일을 아래 코드로 수정합니다.

/* Amplify Params - DO NOT EDIT
    API_AMPLIFYADDCUSTOM_GRAPHQLAPIENDPOINTOUTPUT
    API_AMPLIFYADDCUSTOM_GRAPHQLAPIIDOUTPUT
    API_AMPLIFYADDCUSTOM_GRAPHQLAPIKEYOUTPUT
    ENV
    REGION
Amplify Params - DO NOT EDIT */

const fetch = require("node-fetch")
const AWS = require('aws-sdk')
const sns = new AWS.SNS()
const graphqlQuery = `query listSubscriptionLists {
  listSubscriptionLists {
    items {
      email
      id
    }
  }
}
`

exports.handler = async (event) => {
    const response = await fetch(process.env.API_AMPLIFYADDCUSTOM_GRAPHQLAPIENDPOINTOUTPUT, {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "x-api-key": process.env.API_AMPLIFYADDCUSTOM_GRAPHQLAPIKEYOUTPUT
        },
        body: JSON.stringify({
            query: graphqlQuery,
            operationName: "listSubscriptionLists",
        })
    })

    const result = await response.json()
    const emailList = result.data.listSubscriptionLists.items

    await sns.publish({
        // For demo purposes hard-coded, normally recommended to use environment variable
        TopicArn: "<YOUR-SNS-TOPIC-ARN-HERE>",
        Message: `Here's subscription confirmed mail - ${new Date().toDateString()}:\n` +
            `${emailList.map(item => `${item.id}- ${item.email}`)
                .join('\n')}`
    }).promise().catch(e => console.log(e))
    
    return true
};

위의 소스코드에서 “<YOUR-SNS-TOPIC-ARN-HERE>” 수정했는지 확인하세요 . SNS 주제 ARN은 구독 확인 이메일 또는 SNS 콘솔에서 찾을 수 있습니다. 다음과 같이 표시되어야 합니다.

arn:aws:sns:<your-region>:<your-aws-account-id>:sns-topic-amplifyaddcustom-dev

다음으로 Lambda 함수가 이 SNS 주제에 대한 게시 작업에 액세스할 수 있도록 사용자 지정 IAM 정책을 추가해야 합니다. Lambda 함수 폴더: “amplify/backend/function/sendSubscriptionEmail/”에서 “custom-policies.json”을 찾을 수 있습니다. 여기에서 이 파일에 추가하려는 사용자 지정 IAM 정책을 정의할 수 있습니다. SNS 주제에 메시지를 게시할 수 있는 액세스 권한을 제공하려면 custom-policies.json 파일을 다음으로 바꿉니다.

[
  {
    "Action": ["sns:Publish"],
    "Resource": ["arn:aws:sns:*:*:sns-topic-amplifyaddcustom*"]
  }
]

이제 모든 것이 설정되었으므로 백엔드를 배포합니다.

amplify push -y

Frontend React 화면 구성

화면을 만들기 위해서 필요한 모든 종속성을 설치해야 합니다. React 앱의 루트 디렉터리에서 다음을 실행합니다.

npm install aws-amplify

블로그를 쓰고 있는 6월에는 React, React-Dom의 버전이 높아서, 설치하려는 라이브러리가 오류가 발생하는 경우 아래와 같이 라이브러리 버전에 맞게 다운그레이드하여 설치합니다.

npm install react@17.0.0 react-dom@17.0.0 @material-ui/core --legacy-peer-deps

React 앱에서 Amplify를 구성하여 AWS 앱 백엔드에 연결하기 위해서, src/index.js 파일로 이동하여 바로 앞에 다음 세 줄의 코드를 추가하고 아래와 같이 변경하세요.

[프로젝트]/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Amplify from 'aws-amplify'
import awsconfig from './aws-exports'

Amplify.configure(awsconfig)

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
reportWebVitals();

[프로젝트]/src/App.js

다음으로 src/App.js를 다음 소스로 수정합니다.

import React from 'react';
import { API } from 'aws-amplify';
import { Box, Button, TextField } from "@material-ui/core";
import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import './App.css';

function App() {
  const [values, setValues] = React.useState({
    email: '',
    emailList:[],
  });
  
  const handleChange = (prop) => (event) => {
    setValues({ ...values, [prop]: event.target.value });
  };

  const fetchEmailList = React.useCallback(async () => {
    const response = await API.graphql({ query: queries.listSubscriptionLists })
    setValues({ ...values, emailList: response.data.listSubscriptionLists.items });
  })

  React.useEffect(() => {
    fetchEmailList()
  }, [])
  
  return (
      <Box
        border={1}
        elevation={"large"}
        width={"500px"}
        height={{ min: "max-content" }}
        padding={'10px 10px 10px 10px'}
        margin={'15px 15px 15px 15px'}
      >
        <h2>Email Subscription with Amplify + CDK</h2>
        <div>
        Get an email that makes reading the news about AWS new features.
        </div>
        <br/>
        <TextField
          id="outlined-basic"
          type={"email"}
          label={"Enter your email"}
          placeholder={"Enter your email"}
          variant="outlined"
          onChange={handleChange('email')}
          width={"300px"}
        />
        <Button variant="contained" color="primary"
          type="submit"
          onClick={async() => {
            // console.log(values.email);
            if (values.email) {
              try {
                await API.graphql({
                  query: mutations.createSubscriptionList,
                  variables: {
                    input: {
                        email: values.email
                    }
                  }
                })
                await API.graphql({
                  query: mutations.sendSubscriptionEmail
                })
              } catch (e) {
                console.log(e) // caught
              }
            }
            fetchEmailList()
          }}
          style={{
            paddingTop: "16px",
            paddingBottom: "16px",
            marginLeft: "15px"
          }}
        >
          Add Email
        </Button>
        <br/>
        <ul>
          {values.emailList.map(({ email, id }) => <li>
            <div>{email} - {id}</div>
          </li>)}
        </ul>
      </Box>
  );
}

export default App;

이제 다음을 실행하여 앱을 테스트합니다.

npm start


Submit 버튼을 누르면 이메일이 정상 발송되는지 확인을 합니다.

Lambda 함수 소스 index.js 파일을 아래 코드로 수정합니다.

Client Configuration을 사용하여 Amplify 확장

CDK에서 생성한 리소스들을 사용하는 다른 방법으로 Client Configuration 를 이용할 수 있습니다. 예를 들어, Amazon Cognito를 사용하는 애플리케이션이 있고, AWS Amplify로는 설정할 수 없는 Cognito의 기능이나 속성이 필요한 경우가 있습니다. Amplify 프로젝트에서  CDK를 이용하여 Cognito 리소스를 만들고 Configuration 설정을 통해 기존에 Amplify로 만든 리소스와 같이 사용할 수 있습니다. Client Configuration은 Custom AWS Resource와는 달리 기존 AWS Amplify 버전이 7 이하인 경우에도 사용이 가능합니다.

AWS CDK로 인프라 구축

다음으로, AWS CDK를 이용하여 SNS를 추가하기 위해서 Amplify 프로젝트 폴더에서 다음 명령을 실행합니다.

mkdir cdk-custom
cd cdk-custom
cdk init sample-app --language typescript
npm install @aws-amplify/cli-extensibility-helper

[프로젝트]/cdk-custom/lib/cdk-custom-stack.ts 파일을 편집하여 SNS 주제와 이메일 구독을 생성해 보겠습니다. (아래 코드에서  <YOUR_EMAIL_ADDRESS_HERE> 를 수정하세요)

위에서 Custom AWS Resources로 만든 코드와 거의 유사합니다. 여기서, 주의 깊게 봐야 하는 부분은 맨 아래 CfnOutput (https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_core.CfnOutput.html) 코드 입니다. 이 API는 다른 스택에서 값을 가져오거나 로컬 파일 시스템의 파일로 리디렉션할 수 있게 해줍니다. 아래 코드에서 SNS 이름이나 ARN을 출력할 수 있습니다.

import { CfnOutput, Fn, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';

export class CdkCustomStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    
      // Create the SNS topic 
    const topic = new sns.Topic(this, 'sns-topic', {
      // Reference the Amplify env to ensure multi-env workflow function correctly
      topicName: `sns-topic-${AmplifyHelpers.getProjectInfo().projectName}-cdk-${AmplifyHelpers.getProjectInfo().envName}`
    });

    // Add an email subscription
    topic.addSubscription(new subs.EmailSubscription("<YOUR_EMAIL_ADDRESS_HERE>"));
    
    new CfnOutput(this, 'aws_project_region', { value: Stack.of(this).region });
    new CfnOutput(this, 'aws_sns_topic_arn', { value: topic.topicArn});
    new CfnOutput(this, 'aws_sns_topic_name', { value: topic.topicName});
    
  }
}

AWS CDK를 이용하여 리소스를 클라우드에 배포하면서 출력 파일을 만듭니다. AWS CDK 앱을 이용하여 AWS 리소스를 배포하기전에 AWS CDK에 프로비저닝해야 할 수 있습니다. CDK를 사용한적이 없는 계정, 리전인 경우, 다음 문서를 참고하여 부트스트래핑을 하세요.

cdk deploy -O ../src/cdk-exports.json

배포가 성공하면 [프로젝트]/src 폴더 아래에 cdk-exports.json 파일이 아래와 같이 생성됩니다.

{
  "CdkCustomStack": {
    "aws_sns_topic_name": "sns-topic-amplifyaddcustom-cdk-dev",
    "aws_sns_topic_arn": "arn:aws:sns:<your-region>:<your-aws-account-id>:sns-topic-amplifyaddcustom-cdk-dev",
    "aws_project_region": "<your-region>"
  }
}

이제 AWS CDK에서 생성한 백앤드를 프론트앤드에서도 사용할 수 있게 위에서 만든 Lambda를 수정하여 배포를 하면 Client Configuration 을 이용한 리소스도 사용할 수 있습니다.

[프로젝트]/amplify/backend/function/sendSubscriptionEmail/src/index.js

   await sns.publish({
        // For demo purposes hard-coded, normally recommended to use environment variable
        TopicArn: "<YOUR-CDK-SNS-TOPIC-ARN-HERE>",
        Message: `Here's subscription confirmed mail - ${new Date().toDateString()}:\n` +
            `${emailList.map(item => `${item.id}- ${item.email}`)
                .join('\n')}`
    }).promise().catch(e => console.log(e))

다음 단계

AWS CDK를 사용하여 인프라를 배포하면 개발 프로세스의 일부로 인프라 코드를 자동으로 테스트할 수 있습니다. 그리고 새로 추가된 사용자 지정 AWS 리소스에서 Amplify 생성 리소스를 참조하기 위해 개발자는 CDK에 제공된 “amplifyParams” 파라미터를 사용하거나 CloudFormation에서 “Parameters” 객체를 사용할 수 있습니다. 기존 프로젝트에서 AWS Amplify만을 이용하기에 부족함을 느끼셨다면, 이제 AWS CDK와 함께 다양한 방법으로 통합하여 백앤드를 만드실 수 있습니다.

CDK 생성 리소스를 Amplify 백엔드 리소스 재정의

만약, Amplify 에서 지원하는 Cognito, Lambda, S3 등과 같은 리소스를 CDK를 생성한 경우에는, React 앱에서 만들어진 Amplify 환경 설정을 통해서 바로 사용이 가능합니다. [프로젝트]/src/index.js 파일로 이동하여 바로 다음 코드를 추가하십시오.

import { CdkCustomStack } from './cdk-exports.json';

const CDKConfig = {
  aws_appsync_graphqlEndpoint: CdkCustomStack.awsappsyncgraphqlEndpoint,
  aws_appsync_authenticationType: CdkCustomStack.awsappsyncauthenticationType,
  aws_appsync_apiKey: CdkCustomStack.awsappsyncapiKey
  ...
}

기존 Amplify.configure 코드를 수정하면 Amplify CLI를 통해 만든 백앤드와 동일하게 사용이 가능합니다.

Amplify.configure({ ...awsconfig, ...CDKConfig });

CDK 기반 파이프라인과 통합

이 블로그에서는 풀 스택 애플리케이션을 구축하고 확장하기 위해 Amplify CLI 및 Amplify Libraries를 사용하였으며, AWS CDK를 사용하여 클라우드 리소스를 만들는 방법을 다루었습니다.

최근 AWS Amplify는 또 다른 기능을 발표하였습니다. Amplify CLI가 생성한 백엔드를 Cloud Development Kit(CDK) 스택으로 내보내기 하고 기존 CDK 배포 파이프라인에 통합하는 기능입니다. 이 신규 기능은 프런트엔드 개발자가 앱 백엔드를 빠르게 구축하도록 하며, 내보낼 준비가 되었을 때마다 DevOps 팀으로 보내 프로덕션으로 배포할 수 있습니다.

Woochul Jung

Woochul Jung

정우철 프로토타이핑 엔지니어는 소프트웨어 엔지니어 및 아키텍트로 다양한 서비스를 설계하고 개발 및 운영을 경험하였습니다. 현재는 아마존 웹서비스(AWS)에서 이러한 경험을 바탕으로 고객분들의 AWS를 활용한 개발 운영, 아키텍처 설계 및 앱 현대화에 도움을 드리고 있습니다.