XplentyでDynamoDBのデータをAppSync経由で扱ってみる

2020.08.14

はじめに

データアナリティクス事業本部のkobayashiです。

XplentyはETL、ELTツールとして様々なデータソースを扱え、また処理もXplentyのGUIで直感的に作成できます。 今回はDynamoDBをソースにしたデータを取り扱ってみたいと思います。DynamoDBの場合Xplentyには直接データソースとして接続することはできませんが、AppSyncでGraphQL APIを作成しそのエンドポイントに対してリクエストを送ることでデータを扱う事ができます。

今回はDynamoDBをデータソースとするAppSyncを作成し、XplentyでETL処理してRedshiftへデータを登録してみたいと思います。

変換元となるDynamoDBのデータ

DynamoDBのから移行するデータはDyamoDBのチュートリアルを行うことで登録できるサンプルデータの映画テーブル を対象とします。

テーブルの作成やサンプルデータのロードはAWS公式のドキュメントをご確認ください。

映画テーブルの中身は以下の様になります。

[
  {
    title: "King Kong",
    year: 1933,
    info: {
      image_url: "http://ia.media-imdb.com/images/M/MV5BMTkxOTIxMDU2OV5BMl5BanBnXkFtZTcwNjM5NjQyMg@@._V1_SX400_.jpg",
      plot: "A film crew goes to a tropical island for an exotic location shoot and discovers a colossal giant gorilla who takes a shine to their female blonde star.",
      rank: 3551,
      rating: 8,
      release_date: "1933-03-07T00:00:00Z",
      running_time_secs: 6000,
      actors: [
        "Fay Wray",
        "Robert Armstrong",
        "Bruce Cabot"
      ],
      directors: [
        "Merian C. Cooper",
        "Ernest B. Schoedsack"
      ],
      genres: [
        "Adventure",
        "Fantasy",
        "Horror"
      ]
    }
  },
  {
    title: "Arsenic and Old Lace",
    year: 1944,
    info: {
      image_url: "http://ia.media-imdb.com/images/M/MV5BMTI3NTYyMDA0NV5BMl5BanBnXkFtZTcwMjEwMTMzMQ@@._V1_SX400_.jpg",
      plot: "A drama critic learns on his wedding day that his beloved maiden aunts are homicidal maniacs, and that insanity runs in his family.",
      rank: 4025,
      rating: 8,
      release_date: "1944-09-01T00:00:00Z",
      running_time_secs: 7080,
      actors: [
        "Cary Grant",
        "Priscilla Lane",
        "Raymond Massey"
      ],
      directors: [
        "Frank Capra"
      ],
      genres: [
        "Comedy",
        "Crime",
        "Romance",
        "Thriller"
      ]
    }
  },
...
]

DynamoDBへ作成した映画テーブルのテーブル名はcm-kobayashi-Moviesで作成したものとして以降の作業を進めます。

AppSyncにDynamoDBをデータソースとするAPIを作成

AWS AppSyncはGraphQLのマネージド・サービスでAmazon DynamoDBを始め、Amazon Aurora、Amazon Elasticsearch Service、AWS Lambda、他のAPIをデータソースとしてAPIを作成し、アプリからGraphQLを使用してデータソースとやり時できます。

今回はデータソースとしてDynamoDBを指定してAPIを作成します。すでに作成してあるDynamoDBをデータソースとするのでインポート機能を使ってAppSync APIを作成します。

手順1-1).DynamoDBテーブルをインポートするを選択し開始を押下する。

手順1-2).テーブル名でインポートしたいテーブル(今回はcm-kobayashi-Movies)を選択し、インポートを押下してAPIを作成する。

以上でDynamoDBをデータソースとするAppSyncのAPIが作成されます。ただこのままではインポートできない属性があったり、欲しいリゾルバーを作成したりしたいのでスキーマとリゾルバーを修正します。

スキーマの修正

AppSyncのスキーマ設計はAWSの公式ドキュメントに丁寧に書かれているのでこれを読めばすぐに理解できます。

今回はデータの取得だけできれば良いのでQueryを残してMutationSubscriptionsは削除します。 またinfo属性の型の定義をする必要があるので修正します。加えてキーを指定してデータを取得するクエリ(getCmKobayashiMovies)とScanに相当するクエリ(listCmKobayashiMovies)はスキーマ定義に作成されているのですが、DynamoDBのマネージメントコンソールで行える下記の様なクエリも欲しかったので定義に加えます。

以下が映画テーブルでクエリを行うスキーマ定義になります。

# (修正)info属性にCmKobayashiMoviesInfoを指定する
type CmKobayashiMovies {
    title: String!
    year: Int!
    info: CmKobayashiMoviesInfo
}

type CmKobayashiMoviesConnection {
    items: [CmKobayashiMovies]
    nextToken: String
}

# (追加)CmKobayashiMoviesInfo型を定義する
## actors、directors、genresは文字列型のリスト
## release_dateは日付型なのでAWSDateTimeを指定
type CmKobayashiMoviesInfo {
    actors: [String]
    directors: [String]
    genres: [String]
    image_url: String
    plot: String
    rank: Int
    rating: Float
    release_date: AWSDateTime
    running_time_secs: Int
}

# (修正)パーティションキー、ソートキーで検索を行う用にlistCmKobayashiMoviesQueryを追加
type Query {
    getCmKobayashiMovies(year: Int!, title: String!): CmKobayashiMovies
    listCmKobayashiMoviesQuery(query: TableCmKobayashiMoviesQueryInput, limit: Int, nextToken: String): CmKobayashiMoviesConnection
    listCmKobayashiMovies(filter: TableCmKobayashiMoviesFilterInput, limit: Int, nextToken: String): CmKobayashiMoviesConnection
}

input TableBooleanFilterInput {
    ne: Boolean
    eq: Boolean
}

# (追加)listCmKobayashiMoviesQueryで使うパーティションキー、ソートキーの指定
input TableCmKobayashiMoviesQueryInput {
    year: TableYearInput!
    title: TableTitleInput
}

# (追加)パーティションキーの検索条件の設定
input TableYearInput {
    eq: Int
}

# (追加)ソートキーの検索条件の設定
input TableTitleInput {
    eq: String
    le: String
    lt: String
    ge: String
    gt: String
    between: [String]
    beginsWith: String
}

input TableCmKobayashiMoviesFilterInput {
    year: TableIntFilterInput
    title: TableStringFilterInput
}

input TableFloatFilterInput {
    ne: Float
    eq: Float
    le: Float
    lt: Float
    ge: Float
    gt: Float
    contains: Float
    notContains: Float
    between: [Float]
}

input TableIDFilterInput {
    ne: ID
    eq: ID
    le: ID
    lt: ID
    ge: ID
    gt: ID
    contains: ID
    notContains: ID
    between: [ID]
    beginsWith: ID
}

input TableIntFilterInput {
    ne: Int
    eq: Int
    le: Int
    lt: Int
    ge: Int
    gt: Int
    contains: Int
    notContains: Int
    between: [Int]
}

input TableStringFilterInput {
    ne: String
    eq: String
    le: String
    lt: String
    ge: String
    gt: String
    contains: String
    notContains: String
    between: [String]
    beginsWith: String
}

input TableStringQueryInput {
    eq: String
    le: String
    lt: String
    ge: String
    gt: String
    between: [String]
    beginsWith: String
}

DynamoDBのリゾルバーの修正

getCmKobayashiMovieslistCmKobayashiMoviesのリゾルバーはインポートした際に自動作成されたもので問題ありませんが、listCmKobayashiMoviesQueryは新規に作成したのでリゾルバーを新しく作成してアタッチする必要があります。リゾルバのマッピングリファレンスもAWSの公式ドキュメントを読めば作成できると思います。

DynamoDB のリゾルバーのマッピングテンプレートリファレンス - AWS AppSync

listCmKobayashiMoviesQueryのリクエストマッピングテンプレート

Queryリクエストマッピングドキュメントを使うのとqueryセクションにキー条件式を指定しています。

{
    "version": "2017-02-28",
    "operation" : "Query",
    "query" : $util.transform.toDynamoDBFilterExpression($ctx.args.query),
    "limit": $util.defaultIfNull($ctx.args.limit, 20),
    "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null))
}

listCmKobayashiMoviesQueryのレスポンスマッピングテンプレート

こちらもほぼデフォルトのままです。

$util.toJson($ctx.result)

APIのURLとKEYの確認

XplentyからはAPIキーを使ってAppSyncにアクセスするので設定 > プライマリ認証モードでAPIのURLとAPIキーを確認しておきます。

以上でDynamoDBとAppSyncの設定は終わりましたので後はXplentyからAppSyncに接続してDynamoDBのデータを取得します。

Xplentyのパッケージの実行と確認

Xplentyのパッケージの作成

Xplentyのパッケージの中身は大まかに以下になります。

  1. AppSync APIにリクエストを送りレスポンスを取得する
  2. 受け取ったJsonデータを加工してRedshiftへ保存できる形に加工する
  3. データを保存するRedshiftコンポーネントを作成する

これらのコンポーネントを組み合わせてパッケージを作成していきます。

はじめにサイドバーからPackagesを選択してNew Packageを押下し新しいパッケージを作成します。

1.AppSync APIにリクエストを送りレスポンスを取得する

AppSync APIを使ってデータを取得し、加工・保存を行います。

手順1-1).画面上部の...を押下し編集設定画面へ進む。

手順1-2).Add componetを押下し、SourcesでREST APIを選択する

手順1-3).作成されたアイコンをダブルクリックするとREST APIコンポーネント設定用のモーダルが表示されるので、AuthenticationNoneを選択する。

手順1-4).Request and responseの設定を行いNextを押下する。

  • Method : POSTを選択
  • URL : {AppSyncの設定で確認したAPI URL}を入力

  • Headers

    • APIキーの指定
      • Key : x-api-key
      • VALUE : {AppSyncの設定で確認したAPI KEY}を入力
    • Content-Typeの指定
      • Key : Content-Type
      • VALUE : application/graphqlを入力
  • Body : { "query": "query listAllMovies { listCmKobayashiMovies(limit:99999) { items { title year info { image_url plot rank rating release_date running_time_secs actors directors genres} } nextToken } }" }
  • Use Pagination : チェックしない
  • Base record JSONPath Expression : $.data.listCmKobayashiMoviesを入力

他の設定値はデフォルトのままにします。

補足

BodyはGraphQLの記法とAppSyncで設定したスキーマに従ってGraphQLクエリを記述します。 今回の例ですと、listCmKobayashiMoviesクエリですべての属性を取得しています。

Base record JSONPath Expressionはレスポンスで返ってきたJsonから目的のデータを取得するように設定しています。これは次のResponse Schemaでレスポンスの内容を確認しながら設定すれば大丈夫です。

手順1-6).Response Schemaへ進み、Selected Fieldsで右下のAddを押下し下記の設定値でSelected Fieldsを設定する。その後Saveを押下し、設定を保存する。

Key Alias Type
items items BAG

この画面ではレスポンスの内容を確認できるので必要なデータを確認しながら設定を行えます。

これでREST APIコンポーネントの設定は終わりますので、次にデータ加工用のコンポーネントをを設定します。

2. 受け取ったJsonデータを加工してRedshiftへ保存できる形に加工する

次にデータを加工してRedshiftへ取り込める状態にします。Selectコンポーネントを2つ繋げて、1つ目のコンポーネントでレスポンスをフラット化し、2つ目のコンポーネントでJson文字列を扱う関数を駆使して欲しい形に加工します。

手順2-1).Add componetを押下し、TransformationsでSelectを選択する。この操作を2回行い2つSelectコンポーネントを作成する。

手順2-2).作成したSelectコンポーネントを前段のREST APIコンポーネントと接続する。(接続していないとカラムのマッピングができないため)

手順2-3).作成したSelectコンポーネントの1つ目を選択して設定を行いSaveを押下し保存する。

  • EXPRESSION、ALIASに以下のように入力
EXPRESSION ALIAS
Flatten(items) item
ToString(SwitchTimeZone(CurrentTime(),'Asia/Tokyo'), 'yyyy-MM-dd\'T\'HH:mm:ss') created_datetime

手順2-4).作成したSelectコンポーネントの2つ目を選択して設定を行いSaveを押下し保存する。

  • EXPRESSION、ALIASに以下のように入力
EXPRESSION ALIAS
ParseIntOrDefault(JsonStringToMap(item)#'year',null) year
JsonStringToMap(item)#'title' title
ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'rank',null) rank
ParseDoubleOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'rating',null) rating
ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'running_time_secs',null) ParseIntOrDefault(JsonStringToMap(JsonStringToMap(item)#'info')#'running_time_secs',null)
JsonStringToMap(JsonStringToMap(item)#'info')#'plot' plot
JsonStringToMap(JsonStringToMap(item)#'info')#'image_url' image_url
ToDate(JsonStringToMap(JsonStringToMap(item)#'info')#'release_date') release_date
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'actors'),';') actors
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'directors'),';') directors
BagToString(JsonStringToBag(JsonStringToMap(JsonStringToMap(item)#'info')#'genres'),';') genres
created_datetime created_datetime

actors,directors,genresは中身を確認した際に文字列型のリストでした。今回は子テーブルを作らず簡単にそのまま;セミコロンで繋げています。 その際にBAG型を指定した文字列で結合するBAGXplentyのBagToString関数を使用しています。

3. 保存先としてRedshiftコンポーネント作成

前回までと同様に簡単に行うためテーブルは予め作成せずXplentyのジョブで自動作成し、データは毎回洗い替えを行う設定にします。

手順3-1).Add componetを押下し、DestinationsでAmazon Redshiftを選択する

手順3-2).画面上にRedshiftのアイコンが作成されるので先に作成したSelectコンポーネントのアイコンとDrag&Dropで紐付ける。

手順3-3).RedshiftのアイコンをダブルクリックするとRedshiftコンポーネント設定用のモーダルが表示されるので、設定済みのRedshiftのconnectionを選択する。

手順3-4).移行先のRedshiftのスキーマとテーブルを入力し各種設定を行い、Nextを押下する。

  • Automatically create table if it doesn't exist : チェックする
  • Automatically add missing columns : チェックする
  • Operation type : Overwrite (Truncate and insert)を選択する

他の設定はデフォルトで問題ありません。

これでパッケージの作成は完了です。最終的なパッケージの形は下図になります。

Xplentyのパッケージの実行と確認

実際にジョブを実行して結果を確認します。

Redshitでデータを確認

year title rank rating running_time_secs plot image_url release_date actors directors genres created_datetime
2013 +1 401 5.6 5700 Three college friends hit the biggest party of the year, where a mysterious phenomenon disrupts the night, quickly descending into a chaos that challenges their friendships -- and whether they can stay alive. http://ia.media-imdb.com/images/M/MV5BMTQwOTA5Mzc3Ml5BMl5BanBnXkFtZTgwOTkxODAxMDE@._V1_SX400_.jpg 2013-03-10 00:00:00.000000 Rhys Wakefield;Logan Miller;Ashley Hinshaw Dennis Iliadis Sci-Fi;Thriller 2020-07-03T05:11:42
2013 A Madea Christmas 2374 NULL NULL Madea dispenses her unique form of holiday spirit on rural town when she's coaxed into helping a friend pay her daughter a surprise visit in the country for Christmas. http://ia.media-imdb.com/images/M/MV5BMTYxMTE0NzMwOV5BMl5BanBnXkFtZTgwNDI1MzkxMDE@._V1_SX400_.jpg 2013-12-13 00:00:00.000000 Tyler Perry;Chad Michael Murray;Tika Sumpter Tyler Perry Comedy 2020-07-03T05:11:42
2013 All Is Bright 464 5.3 6420 While out on parole, Dennis reluctantly takes a job selling Christmas trees with his old buddy Rene in order to make enough money to buy his estranged daughter the piano she's always wanted. http://ia.media-imdb.com/images/M/MV5BMjE2ODg4MDUzOF5BMl5BanBnXkFtZTgwMzk5MjkxMDE@._V1_SX400_.jpg 2013-04-18 00:00:00.000000 Paul Giamatti;Paul Rudd;Sally Hawkins Phil Morrison Comedy;Drama 2020-07-03T05:11:42
2013 Battle of the Year 37 3.5 6600 Battle of the Year attracts all the best teams from around the world, but the Americans haven't won in fifteen years. Dante enlists Blake to assemble a team of the best dancers and bring the Trophy back to America where it started. http://ia.media-imdb.com/images/M/MV5BOTY2OTcyNzM3NF5BMl5BanBnXkFtZTgwNzgxMjkxMDE@._V1_SX400_.jpg 2013-09-19 00:00:00.000000 Josh Holloway;Laz Alonso;Josh Peck Benson Lee Music 2020-07-03T05:11:42

この様な形でDynamoDBのデータが登録できました。

まとめ

DynamoDBのデータをXplentyで扱うためAppSyncでAPIを作成し、XplentyのREST APIコンポーネントを使ってデータを取得してみました。今回始めてAppSyncを触ってみましたがGraphQL使いやすいですね。積極的に使っていきたいと思います。

最後まで読んで頂いてありがとうございました。