Amazon Web Services ブログ

Amazon DynamoDB、AWS Lambda、および Go を使用してエンタープライズアプリケーションを構築する

Amazon DynamoDB は、あらゆる規模で 1 桁のミリ秒のパフォーマンスを提供する、完全マネージド型サービスです。完全マネージド型で、舞台裏のマルチ AZ データレプリケーションを通じて高可用性を実現し、Amazon DynamoDB Accelerator (DAX) および複数のグローバルセカンダリインデックスを使用したネイティブライトスルーキャッシングをサポートします。開発者は、この投稿の焦点である Go を含む豊富なプログラミング言語のセットで AWS SDK を使用し、DynamoDB と対話できます。

この投稿では、CRUD を集中的に使用するアプリケーションに対して固有の使用例と、DynamoDB、AWS Lambda、Go を使用してそれらを効率的に処理する方法について説明します。CRUD は、作成、読み取り、更新、削除を表します (Wikipedia の作成、読み取り、更新、削除を参照してください)。この用語は、多数の異種オブジェクトを管理するアプリケーション、複雑なビジネスモデル、高度な自動化を伴う業界で一般的に使用されます。この投稿では、ホスピタリティ業界の例を使用していますが、基本的な設計原則はさまざまなエンタープライズアプリケーションに適用されます。

この投稿は、DynamoDB と Go を使用してエンタープライズアプリケーションを設計および構築するための、優れたプラクティスと例を探しているソフトウェアエンジニアを対象としています。詳細については、GitHub リポジトリを参照してください。GitHub リポジトリのコードは、本番稼働での使用が推奨されないクイックプロトタイピング用に作成されています。

前提条件

このチュートリアルを完了するには、AWS CLI アクセス権を持つ AWS アカウントを持ち、AWS CDK をインストールする必要があります。詳細については、AWS CDK の使用開始を参照してください。API Gateway エンドポイントと Lambda 関数の構築については、GitHub リポジトリを参照してください。

ユースケースとアプリケーション設計

最新のホテルの中央予約システムにより、ホテルチェーンと独立したプロパティは、コンテンツ (写真、動画、部屋の説明、部屋と料金、流通ルールなど) を管理し、さまざまな流通チャネル (オンライン旅行代理店、チェーンウェブサイト、内部予約エンジンなど) を通じて商品を検索し、予約できるようにします。これらの特殊なアプリケーションは、ホテルのフロントデスクのエージェントから最終ゲストまで、チェーン企業の従業員など、何百ものユースケースを多数のユーザーに公開します。これらのユースケースは、一般的に次のファミリーに適合します。

  • 管理ユースケース – 通常、ホテル経営者やオフィスの従業員は、それぞれが相互に依存する多数の属性で構成される CHAINBRANDPROPERTYROOMROOM_TYPEPRICING_RULE などのオブジェクトを作成、更新、削除できます。
  • 運用ユースケース – これらの機能により、モバイルおよびウェブアプリケーションは、前述の属性、計算料金、ビジネスルールの結果の組み合わせとして定義されたホテル製品を検索および予約できます。

運用ユースケースには限られたフローセットと高トラフィック/低レイテンシー要件があり、一部のフローは結果整合性に耐えることができます。管理ユースケースでは、一貫した読み取りと多数の小さなオブジェクト CRUD が必要です。

運用ユースケース 管理ユースケース
ユースケース

SearchHotelAvailability

SearchRoomAvailability, BookRoom

listHotels, saveHotel, getHotel, deleteHotel, addPicture, deletePicture, saveRooms, addSellable, addRoomType, addTag, deleteSellable, deleteRoomType, deleteTag, addSellablePicture, addRoomTypePicture, deleteSellablePicture, deleteRoomTypePicture, publishChanges
クライアント ホテルチェーン、ウェブサイト、モバイル予約アプリケーション、サードパーティーのオンライン旅行代理店… 管理ポータル
ペルソナ 旅行代理店、ホテル経営者、ゲスト ホテル経営者、ホテルチェーン企業従業員
制約

高トラフィック

低レイテンシー

結果整合性

中トラフィックから低トラフィック

強い一貫性

柔軟なデータモデル

多数の相互依存オブジェクト (外部キーを持つクラスに簡単にマッピング可能) があるため、アプリケーション設計への古典的なアプローチでは、リレーショナルデータベースの使用により適した管理ユースケースを識別できます。DynamoDB は、この一連のユースケースで特に役立ちます。迅速にデータモデルを進化させ、独立したオブジェクト CRUD 管理を提供し、ネイティブで効率的な読み取りのユースケースを公開します。さらに、オブジェクトの相互依存性にもかかわらず、通常、スケーラビリティとパフォーマンスを妨げる結合と複数の並列クエリの使用を回避します。

これらのユースケースを説明するために、この投稿では、DynamoDB テーブルにアクセスする Go で記述された Lambda 関数と統合されたエンドポイントのセットを備えた、Amazon API Gateway の RESTful API で構成されるサーバーレスアプリケーションを紹介します。次の図は、このアーキテクチャを示しています。

API Gateway レイヤーは、RESTful API 定義とアクセス制御を管理します。また、各メソッド/REST エンドポイントの組み合わせに対して呼び出される Lambda 関数と、すべての個別オブジェクトの管理に 1 つの Lambda 関数を使用できる変換テンプレートも定義します。これらはマイクロサービス設計の原則に反するように見えるかもしれませんが、1 つの Lambda アプリケーション (および 1 つの DB テーブルのストレージ) で相互依存する多くの単純なオブジェクトを管理し続けることで、アプリケーションの操作性と保守性において優れた利点を得ることができます。

Lambda 関数は、オブジェクトモデルを DynamoDB 最適化モデルに変換します。2 つのモデルを疎結合化することをお勧めします。下位互換性の管理を改善し、データストアの上に抽象化レイヤーを提供するためです。これにより、将来はより簡単に変更を加えることができます。もう 1 つの良い方法は、DynamoDB SDK 関数の上にラッパーを構築することです。これにより、データモデルが変更された場合に抽象化が行われ、ユースケースに合わせて関数の定義を調整できるため、コードの可読性と保守性が大幅に向上します。

データモデルの設計

DynamoDB のテーブルは、パーティションキー (PK) とオプションのソートキー (SK) で構成されます。PK/SK の組み合わせは、テーブル内の項目のプライマリキーを構成します。また、パーティションキーの選択は、基となるデータベースノードへの項目の配布方法に直接影響するため、PK には高いカーディナリティセットを選択する必要があります。ただし、SCAN 操作のみが異なる PK を持つ項目を返すことができるため、トレードオフが生じます。SCAN 操作はテーブル全体を読み取るため、特定の PK の項目のみをフェッチする QUERY 操作と比較すると非効率的です。

各ホテルはコードで識別され、次のモデルのマスター構成レコードを持ちます。

 type Hotel struct {
        Code   string `json:"code"`                                 //ホテルの固有識別子 
        Name string `json:"name"`                                   //ホテル名      
        Description string `json:"description"`                     //ホテルの説明
        CurrencyCode string `json:"currencyCode"`                   //ホテルの通貨コード
        DefaultMarketting HotelMarketing `json:"defaultMarketting"` //マーケティングテキストを含むオブジェクト
        Options HotelOptions `json:"options"`                       //トランザクションオプションを含むオブジェクト (検索、予約のために開きますか?)
        Pictures []HotelPicture `json:"pictures"`                   //画像コンテンツとタグの URL を含む画像オブジェクトのリスト
        Buildings []HotelBuilding `json:"buildings"`                //ホテルビル一覧 (各ビルには階層と部屋があります)
        RoomTypes []HotelRoomType `json:"roomTypes"`                //部屋タイプのリスト (クイーン、スイート、プレジデンシャルスイート...)
        Tags []HotelRoomAttribute `json:"tags"`                     //部屋タイプの価格に影響するタグのリスト (シービューやミニバーなど)
        Sellables []HotelSellable `json:"sellables"`                //販売可能なアイテムのリスト (食事、遠足など...)
        Rules []HotelBusinessRule `json:"businessRules"`            //部屋の価格と空室、販売可能なタグを定義するビジネスルールのリスト
        PendingChanges []HotelConfigChange `json:"pendingChanges"`  //ホテル構成レコードの変更リスト
}

マスター構成オブジェクトには、相互依存関係を持つ一連のネストされた単純オブジェクト (10 個未満のフィールド) があります。業界のユースケースでは、通常、数百 (場合によっては数千) のネストされたオブジェクトを使用できます。次の図は、クラシック UML を使用したこのようなデータモデルの部分バージョンを示しています。

UML 表現は、この投稿の DynamoDB モデルを視覚的に明確に説明していません。各項目のパーティションキー、ソートキー、専用属性を説明する表形式を使用することをお勧めします。この表現は、この投稿の後半で使用されます。

このモデルで対処する管理ユースケースには、listHotelssaveHotelgetHoteldeleteHoteladdPicturedeletePicturesaveRoomsaddSellableaddRoomTypeaddTagdeleteSellabledeleteRoomTypedeleteTagaddSellablePictureaddRoomTypePicturedeleteSellablePicturedeleteRoomTypePicture、および publishChanges があります。1 つまたは複数のデータアクセスパターンがそれぞれに対応しています。

前述のビジネスモデルに対応し、DynamoDB で効率的に実行するデータモデルを定義するには、必要な制約を確認する必要があります。

書き込み操作

コスト、パフォーマンス、アプリケーションアクセスパターンを最適化するには、オブジェクトを同じテーブル内の複数の項目に分割します。ネストされたオブジェクトの読み取り/書き込みが必要な場合、完全なオブジェクトは必要ありません。これらの要件をサポートするには、ネストされたデータを独自の項目に保存します。以前に定義済みのネストされたオブジェクトは小さいため (10 フィールド未満)、項目の粒度の良い候補になります。

読み込み操作

この投稿ではより詳細な取得ユースケースを定義していないため、ホテルの構成全体を返す getHotel ユースケースは、最も頻繁に使用される可能性があります (この設計上の決定は、さらなる議論によって利益を得ますが、この投稿の範囲外です)。したがって、最適化して、スキャン操作と複数の並列クエリを回避する必要があります。これを実現するには、ホテルコードをすべてのレコードのパーティションキーとして使用し、ソートキーを使用して、レコードが対応するネストされたオブジェクトを特定します。パーティションキーとしてホテルコードを選択することは、管理ユースケースが中トラフィックから低トラフィックをサポートするという仮説の下で機能します。トラフィックは主に管理フロントエンドでの手動アクションから発生するため、これは賢明です。

運用ユースケースと管理ユースケースで DynamoDB 機能を使用する方法には、いくつかの重大な違いがあります。運用ユースケースからの高スループットトラフィックは、スケーラビリティと伸縮性のために DynamoDB を使用します。管理ユースケースは、ネストされたオブジェクトを追加および更新する柔軟性、小規模な書き込み操作のコスト効率、DynamoDB のサーバーレス性質を利用しています。

DB モデルを設計するには、ソートキーに次の命名規則を使用します。

“cfg”-<”general” |”history” | nested_object_type>-<”picture”>-<id>

次の表に、マッピングの例をいくつか示します。

ビジネスオブジェクト DynamoDB レコードのソートキー
Hotel cfg-general
HotelSellable cfg-sellable-<id>
HotelRoom cfg-room-<id>
HotelRoomAttribute cfg-tag-<id>
HotelRoomType cfg-roomType-<id>
HotelPicture

cfg-general-picture-<id>

cfg-sellable-picture-<id>

cfg-roomType-picture-<id>

HotelConfigChange cfg-history-<id>

前述のように、次の表形式の表現を使用して、DynamoDB モデルを視覚的に変換します。

Lambda 関数のアーキテクチャ

次の図は、Lambda 関数のアーキテクチャを示しています。

API Gateway からのリクエストはハンドラーに渡され、ハンドラーが実行するユースケースを選択します。各ユースケースは、アダプターレイヤーを介して DynamoDB AWS SDK とやり取りし、ビジネスオブジェクトモデルを DynamoDB 最適化モデルに変換します。

複数の Lambda 関数を使用するシナリオでは、Lambda コアフォルダを、別のリポジトリで公開される共有 GO コンポーネントに指定できます。これにより、次の Lambda 関数をすぐに開始して、同じモデルおよびラッパー関数を再利用できます。

アダプターは、AWS SDK の上に構築された一連のラッパー関数を使用して、最も頻繁に使用されるケースのコードを簡素化します。この投稿では、次のラッパー関数を使用します。

  • Init – 接続構造を作成する
  • Save – 項目を Upsert する
  • FindStartingWith – 特定の文字列で始まる特定の PK および SK 項目を検索する

GitHub リポジトリでは、次の機能も利用できます。

  • SaveMany – 複数の項目を保存する (SDK BatchWriteItems から最大 25 個の項目を処理します)
  • DeleteMany – 複数の項目を削除する
  • Delete – PK と SK で 1 つの項目を削除する
  • Get – PK と SK で項目を取得する
  • FindByGsiグローバルセカンダリインデックスで項目を見つける

Lambda 関数ハンドラー

Lambda 関数ハンドラーは、インフラストラクチャスクリプトでマッピングしたリクエスト本文の subFunction パラメータを使用して、API Gateway エンドポイントからのリクエストを適切なユースケースにルーティングします。次にコードを示します。

  if(wrapper.SubFunction == FN_CONFIG_LIST_HOTEL) {
            log.Printf("Selected Use Case %v",FN_CONFIG_LIST_HOTEL)
            res, err = usecase.ListHotel(wrapper,configDb);
        } else if(wrapper.SubFunction == FN_CONFIG_SAVE_HOTEL) {
            log.Printf("Selected Use Case %v",FN_CONFIG_SAVE_HOTEL)
            res, err = usecase.SaveHotel(wrapper,configDb);
        } else if(wrapper.SubFunction == FN_CONFIG_GET_HOTEL) {
            log.Printf("Selected Use Case %v",FN_CONFIG_GET_HOTEL)
            res, err = usecase.GetHotel(wrapper,configDb);
        } else if(wrapper.SubFunction == FN_CONFIG_ADD_TOOM_TYPE) {
            log.Printf("Selected Use Case %v",FN_CONFIG_ADD_TOOM_TYPE)
            res, err = usecase.AddRoomType(wrapper,configDb);
        } else if(wrapper.SubFunction == FN_CONFIG_DELETE_ROOM_TYPE) {
            log.Printf("Selected Use Case %v",FN_CONFIG_DELETE_ROOM_TYPE)
            res, err = usecase.DeleteRoomType(wrapper,configDb);
        } else {
            log.Printf("No Use Case Found for %v",wrapper.SubFunction)
            err = errors.New("No Use Case Found for sub function " + wrapper.SubFunction)
        }

Lambda が接続を再利用できるように、Lambda 関数ハンドラーの外側で DynamoDB クライアントを初期化することをお勧めします。詳細については、ベストプラクティスを参照してください。次にコードを示します。

//AWS Lambda 関数ハンドラーの外部で Amazon DynamoDB クライアントを
//インスタンス化することをお勧めします。
//https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.BestPracticesWithDynamoDB.html
var configDb =  db.Init(DB_TABLE_CONFIG_NAME,DB_TABLE_CONFIG_PK, DB_TABLE_CONFIG_SK)

Lambda コアパッケージに実装するには、次のコードを参照してください。

//init はセッションを設定し、テーブル名、プライマリキー、ソートキーを定義します
func Init(tn string, pk string, sk string) DBConfig{
        // SDK が共有認証情報ファイル ~/.aws/credentials から認証情報をロードし
        // 共有構成ファイル ~/.aws/config からリージョンをロードするために
        // 使用するセッションを初期化します。
        dbSession := session.Must(session.NewSessionWithOptions(session.Options{
            SharedConfigState: session.SharedConfigEnable,
        }))
        // Amazon DynamoDB クライアントを作成する
        return DBConfig{
            DbService : dynamodb.New(dbSession),
            PrimaryKey : pk,
            SortKey : sk,
            TableName : tn,
        }
}

この手順では、DynamoDB テーブルと対話するクライアントオブジェクトを作成します。この戦略により、複数のクライアントオブジェクトを維持することで、1 つの関数が複数のテーブルと簡単に対話できるようになります。

ユースケースを入手

次のコードを使用して、GetHotel のユースケースを記述できるようになりました。

func GetHotel(wrapper model.RqWrapper,configDb db.DBConfig) (model.ResWrapper, error) {
    dynRes := []model.DynamoHotelRec{}
    dbr := dbadapter.IdToDynamo(wrapper.Id)
    err := configDb.FindStartingWith(dbr.Code, dbadapter.ITEM_TYPE_CONFIG_PREFIX, &dynRes)
    return model.ResWrapper{Response: []model.Hotel{ dbadapter.DynamoListToBom(dynRes)}}, err
}

コア DB パッケージで記述された FindStartingWith ラッパー関数を使用します。次にコードを示します。

func (dbc DBConfig) FindStartingWith(pk string, value string, data interface{}) error{
    var queryInput = &dynamodb.QueryInput{
          TableName: aws.String(dbc.TableName),
          KeyConditions: map[string]*dynamodb.Condition{
          dbc.PrimaryKey: {
            ComparisonOperator: aws.String("EQ"),
            AttributeValueList: []*dynamodb.AttributeValue{
             {
              S: aws.String(pk),
             },
            },
           },
           dbc.SortKey: {
            ComparisonOperator: aws.String("BEGINS_WITH"),
            AttributeValueList: []*dynamodb.AttributeValue{
             {
              S: aws.String(value),
             },
            },
           },
          },
         }

    var result, err = dbc.DbService.Query(queryInput)
    if err != nil {
        fmt.Println("DB:FindStartingWith> NOT FOUND")
        fmt.Println(err.Error())
        return  err
    }

    err = dynamodbattribute.UnmarshalListOfMaps(result.Items, data)
    if err != nil {
        panic(fmt.Sprintf("Failed to unmarshal Record, %v", err))
    }
    return err
}

このラッパー関数を使用すると、指定されたパーティションキーと、指定された文字列で始まるソートキーですべての DynamoDB 項目を検索できます。この実装には、次の重要なポイントがあります。

  • SDK クエリ関数を使用します (スキャンよりも効率的です)。
  • 前述のソートキーの規則通りに、すべてのソートキーが共通のプレフィックスで始まるため、この操作が機能します。
  • ソートキーのコンポーネントの順序でモデル階層のオブジェクトの深さを反映することにより、ネストされた構造のサブパートを効率的にクエリできます。

DB アダプターのコードは次の通りです。

func DynamoListToBom(dbrList []model.DynamoHotelRec) model.Hotel{
    var bom model.Hotel
    var hotelConfigChanges []model.HotelConfigChange = make([]model.HotelConfigChange,0,0)

    for _, dbr := range dbrList {
            if(strings.HasPrefix(dbr.ItemType,ITEM_TYPE_CONFIG_INVENTORY_ROOM_TYPE)) {
                bom.RoomTypes  = append(bom.RoomTypes,DynamoToBomRoomType(dbr))
            } else if(dbr.ItemType == ITEM_TYPE_CONFIG_GENERAL) {
                bom = DynamoToBom(dbr)
            } else if(strings.HasPrefix(dbr.ItemType,ITEM_TYPE_CONFIG_HISTORY)) {
                hotelConfigChanges = append(hotelConfigChanges,DynamoToBomConfigChange(dbr))
            }
    }       
    bom.PendingChanges = hotelConfigChanges
    return bom
}

コードは、DynamoDB が返すすべてのレコードに対して 1 回反復し、オブジェクトを 1 つずつ再構築します。複数のテーブルをクエリしたり、1 つのテーブルで複数回クエリを実行したり、結果にジョイントを適用したりするのではなく、1 つのクエリでオブジェクト全体を再構築できます。これは、ネストされたオブジェクトを個別に書き込むことができるため、書き込みの効率が低下するような犠牲を払うことはありません。これにより、スループットコストを節約し、1 つの大きな構成オブジェクトを保存するときに発生する同時アクセス問題の可能性を低減できます。

ユースケースを書き込む

RoomType のようなネストされたオブジェクトを直接書き込むには、次のコードを参照してください。

func AddRoomType(wrapper model.RqWrapper,configDb db.DBConfig) (model.ResWrapper, error) {
        dbr := dbadapter.BomRoomTypeToDynamo(wrapper.Request, wrapper.UserInfo)
        _, err := configDb.Save(dbr)
        return model.ResWrapper{Response: []model.Hotel{wrapper.Request}},err
}

Save wrapper 関数については、次のコードを参照してください。

func (dbc DBConfig) Save(prop interface{}) (interface{}, error){
        av, err := dynamodbattribute.MarshalMap(prop)
            if err != nil {
                fmt.Println("Got error marshalling new property item:")
                fmt.Println(err.Error())
            }
        input := &dynamodb.PutItemInput{
            Item:      av,
            TableName: aws.String(dbc.TableName),
        }

        _, err = dbc.DbService.PutItem(input)
        if err != nil {
            fmt.Println("Got error calling PutItem:")
            fmt.Println(err.Error())
        }
        return prop, err
}

DB アダプター機能については、次のコードを参照してください。

func BomRoomTypeToDynamo(bom model.Hotel, user core.User) model.DynamoRoomTypeRec{
    return model.DynamoRoomTypeRec{ 
        Code : bom.Code,
        Name : bom.RoomTypes[0].Name,
        Description : bom.RoomTypes[0].Description,
        LowPrice : bom.RoomTypes[0].LowPrice,
        MedPrice : bom.RoomTypes[0].MedPrice,
        HighPrice : bom.RoomTypes[0].HighPrice,
        ItemType: ITEM_TYPE_CONFIG_INVENTORY_ROOM_TYPE + "-" +  bom.RoomTypes[0].Code,
        LastUpdatedBy : user.Username,
        }
}

デモコードの実行

CDK スクリプトを機能させることに興味がない場合、またはスタックを更新する予定がない場合は、GitHub リポジトリから AWS CloudFormation テンプレートを使用できます。

CDK を使用してインフラストラクチャを構築するには、次の手順を実行します。

  1. リポジトリのクローンを作成し、install.sh スクリプトを実行します。
    このスクリプトは、必要なすべてのコンポーネント (Node.js、CDK、Go、パッケージ) をインストールします。
  2. deploy.sh スクリプトを実行します。
    これにより、Lambda ハンドラーコードがビルドされ、GO コンパイラーと AWS 提供のパッケージャーを使用して .zip ファイルが作成されます。次に、CDK CLI を使用し、スタックを作成してデプロイします。次にコードを示します。

    git clone git@github.com:aws-samples/aws-dynamodb-enterprise-application.git
    cd aws-dynamodb-enterprise-application
    sh install.sh
    sh deploy.sh <environementName>
    

    すべてが正しい場合は、次の出力が表示されます。

    CloudInfraDemoStack
    
    Outputs:
    CloudInfraDemoStack.awsCrudDemoEndpointE705E337 = https://3yv9bpbow0.execute-api.eu-west-1.amazonaws.com/prod/
    
    Stack ARN:
    arn:aws:cloudformation:eu-west-1:XXXXXXXXXXXXX:stack/CloudInfraDemoStack/efcf48c0-bcc1-11e9-b902-06952668af9c

    これで、このエンドポイントを使用して、curl ユーティリティソフトウェアを使用して、新しく作成された REST API の呼び出しを実行できます。

  3. ホテルを作成し、部屋の種類を追加して、オブジェクト全体を取得します。次にコードを示します。
    curl -d '{"name" : "My First hotel","description" : "This is a great property"}' -H "Content-Type: application/json" -X POST https://3yv9bpbow0.execute-api.eu-west-1.amazonaws.com/prod/config

    次の出力が得られます。

    	{
    	    "error": {
    	        "code": "",
    	        "msg": ""
    	    },
    	    "subFunction": "",
    	    "response": [
    	        {
    	            "code": "364425903",
    	            "name": "My First hotel",
    	            "description": "This is a great property",
    	            "currencyCode": "",
    	            "defaultMarketting": {
    	                "defaultTagLine": "",
    	                "defaultDescription": ""
    	            },
    	            "options": {
    	                "bookable": false,
    	                "shoppable": false
    	            },
    	            "pictures": null,
    	            "buildings": null,
    	            "roomTypes": null,
    	            "tags": null,
    	            "sellables": null,
    	            "businessRules": null,
    	            "pendingChanges": null
    	        }
    	    ]
    	}
  4. 返された ID を使用して、2 番目の呼び出しを実行します。次にコードを示します。
    curl -d '{"code" : "364425903","roomTypes": [{"code" : "DBL","name" : "Room with Double Beds","description" : "Standard Room Type with double bed"}]}' -H "Content-Type: application/json" -X POST https://3yv9bpbow0.execute-api.eu-west-1.amazonaws.com/prod/config/364425903/rooms/type

    成功した 200 件の応答を取得する必要があります。

  1. 簡単な GET 呼び出しを実行します。次にコードを示します。
    curl https://3yv9bpbow0.execute-api.eu-west-1.amazonaws.com/prod/config/364425903

    ブラウザのアドレスバーに URL を貼り付けることもできます。
    次のコードを受け取ります。

    {
        "error": {
            "code": "",
            "msg": ""
        },
        "subFunction": "",
        "response": [
            {
                "code": "364425903",
                "name": "My First hotel",
                "description": "This is a great property",
                "currencyCode": "",
                "defaultMarketting": {
                    "defaultTagLine": "",
                    "defaultDescription": ""
                },
                "options": {
                    "bookable": false,
                    "shoppable": false
                },
                "pictures": null,
                "buildings": null,
                "roomTypes": [
                    {
                        "code": "DBL",
                        "name": "Room with Double Beds",
                        "lowPrice": 0,
                        "medPrice": 0,
                        "highPrice": 0,
                        "description": "Standard Room Type with double bed",
                        "pictures": null
                    }
                ],
                "tags": null,
                "sellables": null,
                "businessRules": null,
                "pendingChanges": []
            }
        ]
    }

    DynamoDB の AWS マネジメントコンソールにログインすることで、2 つのレコードが DynamoDB テーブルに追加されたことを確認できます。 テーブルリンクをクリックして、aws-crud-demo-config-$env という名前のテーブルを選択します ($env は deploy.sh スクリプトに提供される環境の名前です)。 [アイテム] タブでは、アイテムを管理し、クエリとスキャンを実行できます。前述のソートキー (SK) 構造を持つ 2 つの項目が表示されます。

まとめ

この投稿では、DynamoDB の上にサーバーレス CRUD アプリケーションを構築して、ネスト済みの大規模なビジネスオブジェクトデータモデルの管理ユースケースを有効にする効率的な方法について説明しました。重要なポイントは、すべてのネストされたオブジェクトに同じパーティションキーを使用し、ソートキーコンポーネントの順序を使用して、ネストされたオブジェクトの深さを反映することです。これにより、1 つのクエリでオブジェクト全体を取得でき、ネストされたオブジェクトの効率的な書き込みユースケースを実現できます。この方法には次のような利点があります。

  • 読み取り操作は書き込み操作より安いため、DynamoDB の料金設定と整合しています。
  • ネストされたオブジェクトを個別に書き込むため、同時アクセス問題の可能性が低くなります。
  • オブジェクトを小さな部分に分割します。これは、DynamoDB の 400 KB のレコード側制限を効率的に処理します。
  • これにより、1 つのクエリでオブジェクト全体を取得できます。また、DynamoDB の 1 桁のミリ秒の応答時間が利用されます。ネストされたすべてのオブジェクトを同じ PK の下に保持するため、クエリ操作 (スキャンではなく) を使用できます。

重要事項

このデモの構築から得られる重要なポイント:

  • インフラストラクチャをコードとして使用する – CloudFormation、CDK、または Amplify を使用して、アーキテクチャをスクリプト化します。コードモデルとして AWS コンソールを介して手動で作成された大規模なアプリケーションインフラストラクチャをインフラストラクチャに移動するのはプロセスは退屈です。
  • ユーザー入力をサニタイズする – パーティションキーやソートキーを検索するためのパラメータとして使用する前に行います。そうしないと、単純な入力ミスがあるログを見つけるまでに 1 時間のデバッグを費やすことになります (PK または SK の開始時の相当な空白にご注意ください)。
  • ストレージモデルをビジネスモデルから分離する – これにより、コードの再利用性が大幅に向上します。
  • AWS SDK の上にラッパー関数を構築する – DynamoDB SDK サービスチームは引き続き新しい機能を追加しているため、SDK が時間とともにより複雑になることを期待できます。常にすべての機能が必要なわけではありません。最も頻繁に使用されるケースの単純なラッパー関数を設計します。これにより、同様のプロジェクトのコードを再利用できるため、新しいプロジェクトをすばやく開始できます。
  • 権限の設定に注意する – AWS は IAM を介して権限機能のきめ細かいセットを提供します。すぐに動作しない場合は、まず Amazon EC2 インスタンスまたは Lambda 関数実行ロールに適切な権限があることを確認してください。
  • CORS 設定にご注意ください。 API Gateway の関連エンドポイントで CORS を有効にすることを忘れないでください。これは、API の設計中に頻繁に発生するエラーです。多くの場合、フロントエンドチームとバックエンドチームの間で余分なやり取りが行われることを意味します。

応用

さらに進むには、DynamoDB ストリームを使用して、ビジネスオブジェクトに加えられた変更の履歴を記録する方法を検討する必要があります。監査ログ機能を構築できます。これは、コンプライアンス監査のために大規模な管理ポータルで頻繁に必要になります。

ビジネスオブジェクトモデルの変更リストを追跡すると、ドラフトアンドパブリッシュモデルを構成オブジェクトの変更に適用することもできます。これにより、ユーザーはホテル構成オブジェクトに複数の変更を加えて、選択された (またはスケジュールされた) 時間にのみ一緒に公開することを決定できます。完全に構築されたオブジェクトを Amazon S3 に発行することにより、効果的に実装できます。searchHotelAvailability や BookRoom などの運用ユースケースの最新のスナップショットとして、公開されたオブジェクトを使用できます。

次の図は、ホテルの中央予約システムの単純化されたアーキテクチャを示しています。管理ユースケースアーキテクチャは、この投稿のデモコードと一致します。運用ユースケースは、Amazon API Gateway と AWS Lambda を使用して REST エンドポイントとして公開されていますが、Amazon Aurora を使用します。このユースケースで SQL データベースを使用すると、インベントリの更新に必要な書き込み操作の量によりコスト効率が高くなる可能性が高いためです。Amazon Elasticache を使用して、SearchHotelAvailability ユースケースの応答時間を改善することもできます。

最後に、AWS NoSQL Principal Technologist Rick Houlihan による 2018 AWS re:Invent の優れたプレゼンテーションで、DynamoDB の高度なデザインパターンの推奨事項 (モデルのバージョン管理に関するヒントを含む) を見つけることができます。YouTube でのプレゼンテーション動画をご覧ください。

DynamoDB の上にエンタープライズアプリケーションを構築する場合は、DynamoDB のベストプラクティス通りに最高のパフォーマンスと最低のコストを実現してください。 DynamoDB のベストプラクティスに関するご質問またはご意見については、Twitter アカウント @DynamoDB までご連絡ください。

 


著者について

 

Geoffroy Rollat は、企業顧客をサポートするアマゾン ウェブ サービスのソリューションアーキテクトです。AWS に務める前、Geoffroy はホスピタリティテクノロジーセクターで 10 年近く務め、ホテルチェーンと旅行会社にソフトウェアソリューションを提供していました。