【Amplify iOS】DynamoDBのGlobal Secondary Indexを使いたい
AmplifyのDataStoreはとてもすごい。
どうすごいかは公式のブログに書いてある。
そんな便利ですごいDataStoreだけど、データのソートってどうやるの?
例えばゲームスコアのランキング。
DataStoreでデータ持ってきてから、こちらでソートすることもできるけど、
全部のデータ持ってきてソートするとか、ちょっと。
予めソートされたデータを持ってきたい。
DataStoreのデータはDynamoDBと同期されているので、
DynamoDBのGlobal Secondary Index(以下、GSI)を使ってデータ持って来れればイケる気がする。
調べてみると、schema.graphqlでtypeに@keyをつけるとGSIを設定できるみたい。
こちらの記事、とても参考になりました。ありがとうございました。
qiita.com
実際に以下のようなスキーマを定義してみると、、、
type GameScore @model @key(name: "rankingIndex", fields: ["gameId", "score"], queryField: "rankingQuery") { id: ID! userId: String! gameId: String! score: Int! }
amplify pushして、DynamoDBを見るとrankingIndex
というGSIが作られている!
AppSyncの管理画面でSchemaを見ると rankingQuery
という名前のQueryがあり、そのリゾルバーを見るとrankingIndex
を使っているっぽい!
と、ここまで順調だったのだけど、DataStoreでGSIのクエリをどう呼び出せばよいのか・・・
ググってもズバリな情報は見つけられず。
こうなったらと、Amplify iOSのgithubに質問を投げてみた。
DataStoreでソートする機能はいつか実装されるようです。
DataStoreに拘らず、他に方法はないのかという質問は返ってこず、
機能リクエストのタグが付けられて終わってしまいました。。
英語が何かダメだったのかな。。?
DataStoreはもう無理そうなので諦めて、APIの機能で rankingQuery
を呼び出せないかということで、
Amplify.API.query周りのコードを読み進めてみた。
けどAmplify.APIのquery処理に任意のクエリrankingQuery
を呼び出せるような仕組みは無さそうだった。 *1
ただ、少し弄ればできそうだったので、自作することにした。
GraphQLRequestを生成するとこで、いろんなDecoratorクラスが生成され、
生成されたDecoratorクラスたちがGraphQLを呼び出す際のクエリ文字列などを生成していた。
なので、rankingQuery
呼び出し用のDecoratorクラスを作り、GraphQLRequestを拡張してrankingメソッドを追加した。
こんな感じ。
public struct RankingIndexDecorator: ModelBasedGraphQLDocumentDecorator { enum SortDirection: String { case asc = "ASC" case desc = "DESC" } let gameId: String let queryField = "rankingQuery" var greaterThan: Double = 0 var sortDirection: SortDirection = .asc public func decorate(_ document: SingleDirectiveGraphQLDocument, modelType: Model.Type) -> SingleDirectiveGraphQLDocument { var inputs = document.inputs inputs["gameId"] = GraphQLDocumentInput(type: "String", value: .scalar(gameId)) inputs["score"] = GraphQLDocumentInput(type: "ModelFloatKeyConditionInput", value: .object(["gt" : greaterThan])) inputs["sortDirection"] = GraphQLDocumentInput(type: "ModelSortDirection", value: .scalar(sortDirection.rawValue)) return document.copy(name: queryField, inputs: inputs) } }
extension GraphQLRequest { public static func ranking<M: Model>(_ modelType: M.Type, gameId: String, scoreGreaterThan: Double = 0, where predicate: QueryPredicate? = nil) -> GraphQLRequest<[M]> { var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelType: modelType, operationType: .query) documentBuilder.add(decorator: RankingIndexDecorator(gameId: gameId, greaterThan: scoreGreaterThan)) if let predicate = predicate { documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter)) } documentBuilder.add(decorator: PaginationDecorator()) let document = documentBuilder.build() return GraphQLRequest<[M]>(document: document.stringValue, variables: document.variables, responseType: [M].self, decodePath: document.name + ".items") } }
で、呼び出すのは
Amplify.API.query(request: .ranking(GameScore.self, gameId: "HogeGame"))
で動かしてみると、、、ソートされてデータが取得できた!!
めでたし。めでたし。
他にちゃんとした方法があるようでしたら、どなたか教えて欲しいです><
*1:podのAmplifyのバージョンは1.0.3