ããã«ã¡ã¯ãAndroidã¨ã³ã¸ãã¢ã®@morux2ã§ããã¹ã¿ãã£ãµããªå°å¦ã»ä¸å¦è¬åº§ã§ã¯ãã¼ã¿éä¿¡ã«GraphQLãæ¡ç¨ãã¦ãããéçºè ãä¿¡é ¼ããã¯ã¨ãªã®ã¿ãå¦çããä»çµã¿ã¨ãã¦Trusted Documentsãå®è£ ãã¦ãã¾ãããã®åº¦ãå¾æ¥ã®Persisted QueryããSigned Queryã¨ããæ°ããTrusted Documentsã®å®è£ ãã¿ã¼ã³ã«ç§»è¡ããã®ã§ãAndroidå´ã®å®è£ ã«ã¤ãã¦ãç´¹ä»ã§ããã°ã¨æãã¾ãã
Signed Query ã®è©³ç´°ãªèª¬æã¯ä»¥ä¸ã®ããã°ãã覧ãã ããã blog.studysapuri.jp
- åæ
- 移è¡ã®èæ¯
- Signed Queryãç¨ãããã¼ã¿éä¿¡
- å®è£ æ¹é
- å®è£ 詳細
- ãããã«
åæ
æã ã¯GraphQLã¯ã©ã¤ã¢ã³ãã¨ãã¦Apollo Kotlinãæ¡ç¨ãã¦ãã¾ããApollo Kotlinã¯GraphQLã¯ã¨ãªããKotlinã®ã¢ãã«ãçæããGraphQLã¯ã©ã¤ã¢ã³ãã§ããç¾æç¹ã§ä½¿ç¨ãã¦ãããã¼ã¸ã§ã³ã¯v3.8.3ã§ãã
èªåçæãããã¢ãã«ã®ã¤ã¡ã¼ã¸
query Sample { grade { code } }
public class SampleQuery() : Query<SampleQuery.Data> { // (çç¥) @ApolloAdaptableWith(SampleQuery_ResponseAdapter.Data::class) public data class Data( public val grade: Grade, ) : Query.Data }
ã¯ã¨ãªãå©ããµã³ãã«ã³ã¼ã
fun getSample(): Flow<ApolloResponse<SampleQuery.Data>> { val apolloClient = ApolloClient.Builder() .serverUrl("https://your.graphql.endpoint/") // å¿ è¦ãªè¨å®ãããã«è¿½å .build() return apolloClient.query(SampleQuery()).toFlow() }
Apollo Kotlin(æ§Apollo Android) ã«ã¤ãã¦ã¯ããã¡ãã®ããã°ãåç §ãã ããã blog.studysapuri.jp
移è¡ã®èæ¯
å¾æ¥ã®Persisted Queryã®ä»çµã¿
Persisted Query ã¯ããããããã¢ããªã±ã¼ã·ã§ã³ã§å©ç¨ããã¯ã¨ãªãGraphQLãµã¼ãã¼ã«ç»é²ããç»é²ãããã¯ã¨ãªã®ã¿ãªã¯ã¨ã¹ããåãä»ããä»çµã¿ã§ããApollo-Kotlinã§ã¯ãenableAutoPersistedQueriesãtrueã«è¨å®ãããã¨ã§ãã¯ã©ã¤ã¢ã³ãããã®ãªã¯ã¨ã¹ãã«Persisted Query Hash(ã¯ã¨ãªã®ããã·ã¥å¤)ãä»ä¸ãããã¨ãã§ãã¾ãã*1
Apollo Clientã®ãµã³ãã«ã³ã¼ã
val apolloClient = ApolloClient.Builder() .serverUrl("https://your.graphql.endpoint/") .enableAutoPersistedQueries(true) .build()
GraphQL Over HTTPã§ã¯ããªã¯ã¨ã¹ããã©ã¡ã¼ã¿ã«extensionsã¨ããä»»æã®æ¡å¼µãè¡ãããã®ãã£ã¼ã«ããç¨æããã¦ãããenableAutoPersistedQueriesãtrueã«ãã㨠extensionsã«Persisted Query Hashãã»ããããã¾ãã*2
ãªã¯ã¨ã¹ãã®ã¤ã¡ã¼ã¸
{ "operationName": "Sample", "variables": {}, "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "xxxxxxxxxxxxxxxxxxxxx" } } }
Persisted Queryã®èª²é¡
Persisted Queryã®èª²é¡ã¯ãã¯ã©ã¤ã¢ã³ããå®è¡ããã¯ã¨ãªãããããããµã¼ãã¼ã«ç»é²ããå¿ è¦ããããã¨ã§ããæ®æ®µããã¯ã¨ã³ãã®éçºãæ å½ããªãã¢ããªã¨ã³ã¸ãã¢ã«ã¨ã£ã¦ãGraphQLãµã¼ãã¼ã®ãããã¤ã¯å¿ççãã¼ãã«ãé«ããã®ã§ããããµã¼ãã¼ã®ãããã¤ãä¸è¦ã«ãªãã°ãã¢ããªã®ãªãªã¼ã¹ãããæ軽ã«ãªããä½æ¥æéã®ç縮ãæå¾ ã§ãã¾ãã
Signed Queryãç¨ãããã¼ã¿éä¿¡
ããã§èæ¡ãããã®ãSigned Queryã§ããSigned Queryã¯ãµã¼ãã¼ã®ãããã¤ãä¸è¦ãªã¯ã¨ãªã®æ¤è¨¼ã®ä»çµã¿ã§ããæåã¯æ¬¡ã®ããã«ãªãã¾ãã
- ã¯ã©ã¤ã¢ã³ãã¯å ±ééµãç¨ãã¦ããããã®ã¯ã¨ãªã«å¯¾ããç½²åãä½æãããªã¯ã¨ã¹ãæã«ã¯ã¨ãªã¨ç½²åãåããã¦éä¿¡ãã
- ãµã¼ãã¼ãµã¤ãã§ããªã¯ã¨ã¹ããããã¯ã¨ãªã«å¯¾ãã¦å ±ééµãç¨ãã¦ç½²åãä½æãããªã¯ã¨ã¹ãã«ä»ä¸ãããç½²åã¨ä¸è´ããã確èªãã
ãªã¯ã¨ã¹ãã®ã¤ã¡ã¼ã¸
{ "operationName": "Sample", "variables": {}, "query": "query Sample { grade { code } }", "extensions": { "signedQuery": { "signature": "yyyyyyyyyyyyyyyyyyyyy" } } }
å®è£ æ¹é
ããããã¯Signed Queryã®å®è£ ãç´¹ä»ãã¦ããã¾ããPersisted Queryã®ä»çµã¿ãåèã«ãã¤ã¤ãèªåã§çæããç½²åãã¯ã¨ãªã«ä»ä¸ãã¾ãããªããURLãã©ã¡ã¼ã¿ã«è¼ããã«ã¯ã¯ã¨ãªãé·ããããããSigned Queryã§ã¯ä¸å¾POSTãªã¯ã¨ã¹ããç¨ãã¾ãã
ãã«ãæ
- ã¢ããªããå©ãã¯ã¨ãªã®ä¸è¦§ãåå¾ãã
- å ±ééµãç¨ãã¦ããããã®ã¯ã¨ãªã®ç½²åãä½æããéçãªjsonãã¡ã¤ã«ã«ä¿åãã
ã©ã³ã¿ã¤ã
å®è£ 詳細
1. ã¢ããªããå©ãã¯ã¨ãªã®ä¸è¦§ãåå¾ãã (ãã«ã)
apollo-gradle-pluginã§generateOperationOutputãtrueã«è¨å®ããã¨ãã¯ã¨ãªã®æ å ±ãå«ãoperationOutput.jsonã¨ãããã¡ã¤ã«ããã«ãæã«åã¢ã¸ã¥ã¼ã«ã«çæããã¾ããoperationOutput.jsonã¯ãã¢ã¸ã¥ã¼ã«ãã¨ã®generateApolloSourcesã¨ããGradleã¿ã¹ã¯ã®ä¸ã§çæããã¾ãã詳細ã¯Apollo Kotlinã®å®è£ ãã確èªãã ããã
çæãããoperationOutput.jsonã®ã¤ã¡ã¼ã¸
{ "xxxxxxxxxxxxxxxxxxxxx": { // query Sample { grade { code } } ã®ããã·ã¥å¤ "name": "Sample", "source": "query Sample { grade { code } }", "type": "query" } }
æã ã®ããã¸ã§ã¯ãã¯ãã«ãã¢ã¸ã¥ã¼ã«æ§æãªã®ã§ã次ã®ãããªGradleã®Convention pluginsãå®ç¾©ããåã¢ã¸ã¥ã¼ã«ã«é©ç¨ãã¦ãã¾ãã*3
Apolloç¨ã®Gradle Convention pluginsã®ã¤ã¡ã¼ã¸
plugins { id 'com.apollographql.apollo3' } dependencies { implementation libs.apollo.runtime implementation projects.schema apolloMetadata(projects.schema) } apollo { generateOperationOutput = true }
2. å ±ééµãç¨ãã¦ããããã®ã¯ã¨ãªã®ç½²åãä½æããéçãªjsonãã¡ã¤ã«ã«ä¿åãã (ãã«ã)
ç½²åä½æã®ã¿ã¹ã¯ããã«ãæã«å®è¡ãã¾ãããã®ã¿ã¹ã¯ã¯å è¿°ã®generateApolloSourcesã®ã¿ã¹ã¯ã«ä¾åãããã¨ã§ãoperationOutput.jsonã®çæãå®äºãã¦ããç½²åãä½æãããã¨ãæ ä¿ãã¾ããç½²åã®çæã¯goã®ã¹ã¯ãªããã§è¨è¼ããã¦ãããã¯ã¨ãªã®HMACãè¨ç®ãã¦ãã¾ãã
ç½²åçæã®Gradleã¿ã¹ã¯
task generateSignedQuery(type: Exec) { doFirst { // ããã§goã®ã¹ã¯ãªãããå®è¡ãã // operationOutput.jsonãèªã¿åããããããã®ã¯ã¨ãªã®ç½²åãä½æããéçãªjsonãã¡ã¤ã«ã«ä¿åãã } } // generateSignedQueryã«é¢ããã¿ã¹ã¯ä¾åé¢ä¿ã®è¨å® allprojects { project -> project.afterEvaluate { // å ¨ã¢ã¸ã¥ã¼ã«ã®generateApolloSourceså®äºå¾ã«generateSignedQueryãå®è¡ãã if (project.tasks.findByName('generateApolloSources') != null) { generateSignedQuery.dependsOn project.tasks.findByName('generateApolloSources') } // å ¨ã¢ã¸ã¥ã¼ã«ã®preBuildåã«generateSignedQueryãå®è¡ãã if (project.tasks.findByName('preBuild') != null) { project.tasks.findByName('preBuild').dependsOn generateSignedQuery } } }
çæãããç½²åã¯ã¯ã¨ãªã®ããã·ã¥å¤ãkeyã¨ããç½²åæ
å ±ãvalueã«æã¡ã¾ãããªããæ¬çªç°å¢ã¨ã¹ãã¼ã¸ã³ã°ç°å¢ã§ç°ãªãç½²åãå©ç¨ãã¦ãã¾ãã
ç½²åæ
å ±ãä¿åãããjsonãã¡ã¤ã«ã®ã¤ã¡ã¼ã¸
{ "xxxxxxxxxxxxxxxxxxxxx": { // query Sample { grade { code } } ã®ããã·ã¥å¤ "production": "yyyyyyyyyyyyyyyyyyyyy", // query Sample { grade { code } } ã®HMAC "staging": "zzzzzzzzzzzzzzzzzzzzz" } }
3. ã¯ã¨ãªã«ç½²åãä»ä¸ãã (ã©ã³ã¿ã¤ã )
GraphQLãªã¯ã¨ã¹ããHTTPãªã¯ã¨ã¹ãã«å¤æããã¿ã¤ãã³ã°ã§ãextensionsãæ¿å ¥ãããããªç¬èªã®HttpRequestComposerãä½æãã¾ããHttpRequestComposerã®å®è£ ã¯Apolloã®DefaultHttpRequestComposerãåèã«ãã¦ãã¾ãã
val serverUrl = "https://your.graphql.endpoint/" val apolloClient = ApolloClient.Builder() .serverUrl(serverUrl) .enableAutoPersistedQueries(false) .networkTransport( HttpNetworkTransport.Builder() .httpRequestComposer(SignedQueryComposer(serverUrl)) .build() ) .build() class SignedQueryComposer( private val serverUrl: String, ) : HttpRequestComposer { override fun <D : Operation.Data> compose(apolloRequest: ApolloRequest<D>): HttpRequest { val operation = apolloRequest.operation val customScalarAdapters = apolloRequest.executionContext[CustomScalarAdapters] ?: CustomScalarAdapters.Empty val sendDocument = apolloRequest.sendDocument ?: true val query = if (sendDocument) operation.document() else null return HttpRequest.Builder( // Signed Queryãç¨ããå ´åã¯ãå ¨ã¦POSTãªã¯ã¨ã¹ãã¨ãªã method = HttpMethod.Post, url = serverUrl, ).body( buildPostBody(operation, customScalarAdapters, query) { name("extensions") writeObject { // extensionsã«æ¸¡ããã£ã¼ã«ãã¯äºåã«ãµã¼ãã¼ãµã¤ãã¨ç¸è«ãã¦æ±ºå®ãã¾ãã name("signedQuery") writeObject { name("signature") // éçãªãã¡ã¤ã«ããç½²åãèªã¿è¾¼ãã§ããã«æ¿å ¥ãã¾ã // operation.id()ã§ã¯ã¨ãªã®ããã·ã¥å¤ãåå¾å¯è½ãªã®ã§ãããã·ã¥å¤ãkeyã«ãã¦ç½²åæ å ±ãåãåããã¨ãã§ãã¾ã value("yyyyyyyyyyyyyyyyyyyyy") } } } ).build() } }
Apollo Kotlinããç¬èªã®extensionsãæ¿å ¥ããããããFeature Requestãæ¡ç¨ãã¦ãã ãã£ãã®ã§ãã·ã³ãã«ãªå®è£ ã«ãããã¨ãã§ãã¾ããã github.com
ãããã«
ãã®åº¦ãå¾æ¥ã®Persisted QueryããSigned Queryã«ç§»è¡ãã¾ããã移è¡ã¯Firebase Remote Configã§ãã©ã°ç®¡çãã¦ã大ããªåé¡ãªãå®äºãã¾ããããã£ã¬ã³ã¸ã³ã°ãªèª²é¡ã§ããããApollo Kotlinã®æ¹ã ã親åãã¤ã¹ãã¼ãã£ã¼ã«å¯¾å¿ãã¦ãã ãã£ããã¨ããããã·ã³ãã«ãªå®è£ ã«çãããã¨ãã§ãã¾ããããããã¨ããããã¾ãããSigned Queryã«ç§»è¡ãããã¨ã«ãã£ã¦ãä»ã¾ã§ãããããå®å ¨ãã¤é«éãªãªãªã¼ã¹ãå®ç¾ã§ãããã§ãããã²åèã«ãªãã°å¬ããã§ãã
*1:Automatic Persisted Queries(APQ)ã¨Persisted Queryã¯å¥ç©ã§ãããAPQã¯ã¯ã¨ãªä¿¡é ¼ã®ä»çµã¿ãå«ã¿ã¾ããã
*2:APQãç¨ããã¨ãã¯ã¨ãªã®ããã·ã¥å¤ã ãã§ãªã¯ã¨ã¹ããå¯è½ã§ãã
*3:åè : https://www.apollographql.com/docs/kotlin/advanced/multi-modules/