Skip to content

Commit

Permalink
fix(appsync): add caching config to AppSync resolvers (#17815)
Browse files Browse the repository at this point in the history
While trying to add caching config to some of my application's resolvers, I discovered that the BaseResolverProps do not include caching configuration like the CfnResolver does.

This PR adds this missing caching configuration to the BaseResolverProps and adds the configuration as part of the creation of the CfnResolver. 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kylevillegas93 authored Dec 6, 2021
1 parent 5737c33 commit 52b535b
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 1 deletion.
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/caching-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Duration } from '@aws-cdk/core';

/**
* CachingConfig for AppSync resolvers
*/
export interface CachingConfig {
/**
* The caching keys for a resolver that has caching enabled.
* Valid values are entries from the $context.arguments, $context.source, and $context.identity maps.
*
* @default - No caching keys
*/
readonly cachingKeys?: string[];

/**
* The TTL in seconds for a resolver that has caching enabled.
* Valid values are between 1 and 3600 seconds.
*
* @default - No TTL
*/
readonly ttl?: Duration;
}
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/caching-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const CONTEXT_ARGUMENTS_CACHING_KEY = '$context.arguments';
export const CONTEXT_SOURCE_CACHING_KEY = '$context.source';
export const CONTEXT_IDENTITY_CACHING_KEY = '$context.identity';
export const BASE_CACHING_KEYS = [CONTEXT_ARGUMENTS_CACHING_KEY, CONTEXT_SOURCE_CACHING_KEY, CONTEXT_IDENTITY_CACHING_KEY];
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// AWS::AppSync CloudFormation Resources:
export * from './appsync-function';
export * from './appsync.generated';
export * from './caching-config';
export * from './caching-key';
export * from './key';
export * from './data-source';
export * from './mapping-template';
Expand Down
25 changes: 24 additions & 1 deletion packages/@aws-cdk/aws-appsync/lib/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Construct } from 'constructs';
import { IAppsyncFunction } from './appsync-function';
import { CfnResolver } from './appsync.generated';
import { CachingConfig } from './caching-config';
import { BASE_CACHING_KEYS } from './caching-key';
import { BaseDataSource } from './data-source';
import { IGraphqlApi } from './graphqlapi-base';
import { MappingTemplate } from './mapping-template';

// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch.
// eslint-disable-next-line
import { Construct as CoreConstruct } from '@aws-cdk/core';
import { Construct as CoreConstruct, Token } from '@aws-cdk/core';

/**
* Basic properties for an AppSync resolver
Expand Down Expand Up @@ -40,6 +42,12 @@ export interface BaseResolverProps {
* @default - No mapping template
*/
readonly responseMappingTemplate?: MappingTemplate;
/**
* The caching configuration for this resolver
*
* @default - No caching configuration
*/
readonly cachingConfig?: CachingConfig;
}

/**
Expand Down Expand Up @@ -86,6 +94,17 @@ export class Resolver extends CoreConstruct {
throw new Error(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`);
}

if (props.cachingConfig?.ttl && (props.cachingConfig.ttl.toSeconds() < 1 || props.cachingConfig.ttl.toSeconds() > 3600)) {
throw new Error(`Caching config TTL must be between 1 and 3600 seconds. Received: ${props.cachingConfig.ttl.toSeconds()}`);
}

if (props.cachingConfig?.cachingKeys) {
if (props.cachingConfig.cachingKeys.find(cachingKey =>
!Token.isUnresolved(cachingKey) && !BASE_CACHING_KEYS.find(baseCachingKey => cachingKey.startsWith(baseCachingKey)))) {
throw new Error(`Caching config keys must begin with $context.arguments, $context.source or $context.identity. Received: ${props.cachingConfig.cachingKeys}`);
}
}

this.resolver = new CfnResolver(this, 'Resource', {
apiId: props.api.apiId,
typeName: props.typeName,
Expand All @@ -95,6 +114,10 @@ export class Resolver extends CoreConstruct {
pipelineConfig: pipelineConfig,
requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined,
responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined,
cachingConfig: {
cachingKeys: props.cachingConfig?.cachingKeys,
ttl: props.cachingConfig?.ttl?.toSeconds(),
},
});
props.api.addSchemaDependency(this.resolver);
if (props.dataSource) {
Expand Down
109 changes: 109 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-caching-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as path from 'path';
import { Template } from '@aws-cdk/assertions';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import { Duration } from '@aws-cdk/core';
import * as appsync from '../lib';

let stack: cdk.Stack;
let api: appsync.GraphqlApi;

beforeEach(() => {
// GIVEN
stack = new cdk.Stack();
api = new appsync.GraphqlApi(stack, 'api', {
name: 'api',
schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.lambda.graphql')),
});
});

describe('Lambda caching config', () => {
// GIVEN
let func: lambda.Function;

beforeEach(() => {
func = new lambda.Function(stack, 'func', {
code: lambda.Code.fromAsset(path.join(__dirname, 'verify/lambda-tutorial')),
handler: 'lambda-tutorial.handler',
runtime: lambda.Runtime.NODEJS_12_X,
});
});

test('Lambda resolver contains caching config with caching key and TTL', () => {
// WHEN
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);

lambdaDS.createResolver({
typeName: 'Query',
fieldName: 'allPosts',
cachingConfig: {
cachingKeys: ['$context.arguments', '$context.source', '$context.identity'],
ttl: Duration.seconds(300),
},
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::AppSync::Resolver', {
TypeName: 'Query',
FieldName: 'allPosts',
CachingConfig: {
CachingKeys: ['$context.arguments', '$context.source', '$context.identity'],
Ttl: 300,
},
});
});

test('Lambda resolver throws error when caching config with TTL is less than 1 second', () => {
// WHEN
const ttlInSconds = 0;
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);

// THEN
expect(() => {
lambdaDS.createResolver({
typeName: 'Query',
fieldName: 'allPosts',
cachingConfig: {
cachingKeys: ['$context.identity'],
ttl: Duration.seconds(0),
},
});
}).toThrowError(`Caching config TTL must be between 1 and 3600 seconds. Received: ${ttlInSconds}`);
});

test('Lambda resolver throws error when caching config with TTL is greater than 3600 seconds', () => {
// WHEN
const ttlInSconds = 4200;
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);

// THEN
expect(() => {
lambdaDS.createResolver({
typeName: 'Query',
fieldName: 'allPosts',
cachingConfig: {
cachingKeys: ['$context.identity'],
ttl: Duration.seconds(ttlInSconds),
},
});
}).toThrowError(`Caching config TTL must be between 1 and 3600 seconds. Received: ${ttlInSconds}`);
});

test('Lambda resolver throws error when caching config has invalid caching keys', () => {
// WHEN
const invalidCachingKeys = ['$context.metadata'];
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);

// THEN
expect(() => {
lambdaDS.createResolver({
typeName: 'Query',
fieldName: 'allPosts',
cachingConfig: {
cachingKeys: invalidCachingKeys,
ttl: Duration.seconds(300),
},
});
}).toThrowError(`Caching config keys must begin with $context.arguments, $context.source or $context.identity. Received: ${invalidCachingKeys}`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
},
"FieldName": "getTests",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "ds",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}",
Expand All @@ -160,6 +161,7 @@
},
"FieldName": "addTest",
"TypeName": "Mutation",
"CachingConfig": {},
"DataSourceName": "ds",
"Kind": "UNIT",
"RequestMappingTemplate": "\n #set($input = $ctx.args.test)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
Expand Down Expand Up @@ -223,6 +225,7 @@
},
"FieldName": "version",
"TypeName": "test",
"CachingConfig": {},
"Kind": "PIPELINE",
"PipelineConfig": {
"Functions": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
},
"FieldName": "getPost",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "LambdaDS",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"Invoke\", \"payload\": { \"field\": \"getPost\", \"arguments\": $utils.toJson($context.arguments)}}",
Expand All @@ -135,6 +136,7 @@
},
"FieldName": "allPosts",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "LambdaDS",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"Invoke\", \"payload\": { \"field\": \"allPosts\"}}",
Expand All @@ -156,6 +158,7 @@
},
"FieldName": "addPost",
"TypeName": "Mutation",
"CachingConfig": {},
"DataSourceName": "LambdaDS",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"Invoke\", \"payload\": { \"field\": \"addPost\", \"arguments\": $utils.toJson($context.arguments)}}",
Expand All @@ -177,6 +180,7 @@
},
"FieldName": "relatedPosts",
"TypeName": "Post",
"CachingConfig": {},
"DataSourceName": "LambdaDS",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"BatchInvoke\", \"payload\": { \"field\": \"relatedPosts\", \"source\": $utils.toJson($context.source)}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
},
"FieldName": "getTests",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "testDataSource",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}",
Expand All @@ -153,6 +154,7 @@
},
"FieldName": "addTest",
"TypeName": "Mutation",
"CachingConfig": {},
"DataSourceName": "testDataSource",
"Kind": "UNIT",
"RequestMappingTemplate": "\n #set($input = $ctx.args.test)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
},
"FieldName": "getTests",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "ds",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\":\"2017-02-28\",\"operation\":\"GET\",\"path\":\"/id/post/_search\",\"params\":{\"headers\":{},\"queryString\":{},\"body\":{\"from\":0,\"size\":50}}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
},
"FieldName": "getTest",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "testDataSource",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\": \"2017-02-28\", \"operation\": \"GetItem\", \"key\": {\"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id)}}",
Expand All @@ -184,6 +185,7 @@
},
"FieldName": "getTests",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "testDataSource",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}",
Expand All @@ -205,6 +207,7 @@
},
"FieldName": "addTest",
"TypeName": "Mutation",
"CachingConfig": {},
"DataSourceName": "testDataSource",
"Kind": "UNIT",
"RequestMappingTemplate": "\n #set($input = $ctx.args.test)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
},
"FieldName": "getPlanets",
"TypeName": "Query",
"CachingConfig": {},
"DataSourceName": "planets",
"Kind": "UNIT",
"RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}",
Expand All @@ -152,6 +153,7 @@
},
"FieldName": "addPlanet",
"TypeName": "Mutation",
"CachingConfig": {},
"DataSourceName": "planets",
"Kind": "UNIT",
"RequestMappingTemplate": "\n #set($input = $context.arguments)\n $util.qr($input.put(\"name\", $context.arguments.name))\n$util.qr($input.put(\"diameter\", $context.arguments.diameter))\n$util.qr($input.put(\"rotationPeriod\", $context.arguments.rotationPeriod))\n$util.qr($input.put(\"orbitalPeriod\", $context.arguments.orbitalPeriod))\n$util.qr($input.put(\"gravityPeriod\", $context.arguments.gravity))\n$util.qr($input.put(\"population\", $context.arguments.population))\n$util.qr($input.put(\"climates\", $context.arguments.climates))\n$util.qr($input.put(\"terrains\", $context.arguments.terrains))\n$util.qr($input.put(\"surfaceWater\", $context.arguments.surfaceWater))\n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
Expand Down
Loading

0 comments on commit 52b535b

Please sign in to comment.