amplify-category-api
amplify-category-api copied to clipboard
Gen 2 DX for per-resolver caching
Describe the feature you'd like to request
Today, in Amplify Gen 2, enabling AppSync per-resolver caching is possible under limited circumstances. The specific use case I'm trying to accomplish is per-resolver caching for a pipeline resolver that fetches a secret from Secrets Manager and then calls an external API using that secret, which I have implemented as a pipeline HTTP resolver with two AppSync JavaScript functions--but as shown below, the limitations are broader than just that use case. I think the developer experience can be improved.
To enable this behavior, one must first define a cache resource for the AppSync API in backend.ts:
new CfnApiCache(backend.data, 'AmplifyGqlApiCache', {
apiId: backend.data.apiId,
apiCachingBehavior: 'PER_RESOLVER_CACHING',
ttl: 60,
type: 'SMALL',
})
Then per-resolver caching can be defined in one of three ways:
If using a.model
Assuming type MyModel: a.model({ myField: a.string() }) in data/resource.ts, one can enable caching for that field from backend.ts with the following:
backend.data.resources.cfnResources.cfnResolvers['MyModel.myField'].cachingConfig = { ttl: 60 }
By using a.model, one is limited in what kind of resolver is possible for that type. It cannot, for example, be an HTTP resolver or a pipeline resolver, as my use case entails.
If using a.customType
Assuming MyCustomType: a.customType({ myField: a.string() }), example backend.ts code necessary for a pipeline HTTP resolver that retrieves a secret from Secrets Manager and then does something with that secret:
const fetchSecretFunction = backend.data.addFunction('FetchSecretFunction', {
name: 'fetchSecretFunction',
dataSource: secretsManagerHttpDataSourceDefinedSeparately,
code: Code.fromAsset('./fetch-secret.js'),
runtime: FunctionRuntime.JS_1_0_0
})
const doSomethingFunction = backend.data.addFunction('DoSomethingFunction', {
name: 'doSomethingFunction',
dataSource: doSomethingHttpDataSourceDefinedSeparately,
code: Code.fromAsset('./do-something.js'),
runtime: FunctionRuntime.JS_1_0_0
})
const myResolver = backend.data.addResolver('MyResolver', {
typeName: 'MyCustomType',
fieldName: 'myField',
pipelineConfig: [fetchSecretFunction, doSomethingFunction],
code: Code.fromInline(`
export const request = (ctx) => { return {} }
export const response = (ctx) => { return ctx.prev.result }
`),
runtime: FunctionRuntime.JS_1_0_0,
cachingConfig: {
ttl: Duration.minutes(1)
}
})
In my opinion, this is too verbose. By bringing the entire resolver definition into backend.ts, it also establishes an additional place that a developer must look to find the definitions of the GraphQL types.
If using a.handler.custom
Per-resolver caching appears not to be possible here. But this is where I'd like it to be.
Describe the solution you'd like
First preference
Allow a way to configure per-resolver caching within data/resource.ts for custom types and custom handlers, e.g.:
const schema = a.schema({
doSomething: a.query().returns(a.string()).handler([
a.handler.custom({
dataSource: 'SecretsManagerHttpDataSource',
entry: '../fetch-secret.js'
}),
a.handler.custom({
dataSource: 'DoSomethingHttpDataSource',
entry: '../do-something.js'
})
]).cachingConfig({ ttl: 60 })
}).authorization((allow) => [allow.authenticated()])
(cachingConfig being the key part of the above example)
Second preference
Allow a way to reference, from within backend.ts, the resolvers that have been defined in data/resource.ts with either a.handler.custom() or a.customType() notation. Using the same example as above, one might define the caching this way in backend.ts:
backend.data.resources.cfnResources.cfnResolvers['Query.doSomething'].cachingConfig = { ttl: 60 }
Describe alternatives you've considered
Intuitively, one might assume that in the "Second preference" example above, my custom type and/or custom handler would already be present in the backend.data.resources.cfnResources.cfnResolvers object; however, empirically, cfnResovers seems to contain only "models" from the data construct. The way I verified this was by adding an example model, custom type, and custom handler to data/resource.ts (see the examples above) and then adding the keys of cfnResolvers to amplify-outputs.json (the below would be in backend.ts):
backend.addOutput({
custom: {
resolvers: Object.keys(backend.data.resources.cfnResources.cfnResolvers)
}
})
Additional context
No response
Is this something that you'd be interested in working on?
- [ ] 👋 I may be able to implement this feature request
Would this feature include a breaking change?
- [ ] ⚠️ This feature might incur a breaking change