Skip to content

Commit 6dc15d4

Browse files
fix(appsync): unexpected resolver replacement (#23322)
fix(appsync): unstable IDs on resolvers and functions Fixes an issue that would cause unexpected resource replacement for appsync resolvers and functions because of construct nesting and ID generation. Changes `createResolver` and `createFunction` methods on `GraphQlApi` and `DataSource` constructs to require explicitly passing an ID. Additionally changes the scope of the constructs created in `createResolver` and `createFunction` on the `DataSource` construct to be `this.api` instead of `this`. This allows users to change the data sources of resolvers and functions while keeping the IDs stable and avoiding resource replacement. This helps to avoid the `only one resolver per field` error that occurs when deleting a resolver on a field, and adding a new one within the same deployment. BREAKING CHANGE: `DataSource.createResolver`, `DataSource.createFunction`, and `GraphQlApi.createResolver` now require 2 arguments instead of 1. Fixes: #13269 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 3f706e2 commit 6dc15d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2823
-2318
lines changed

packages/@aws-cdk/aws-appsync/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ const demoDS = api.addDynamoDbDataSource('demoDataSource', demoTable);
7979
// Resolver for the Query "getDemos" that scans the DynamoDb table and returns the entire list.
8080
// Resolver Mapping Template Reference:
8181
// https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html
82-
demoDS.createResolver({
82+
demoDS.createResolver('QueryGetDemosResolver', {
8383
typeName: 'Query',
8484
fieldName: 'getDemos',
8585
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(),
8686
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
8787
});
8888

8989
// Resolver for the Mutation "addDemo" that puts the item into the DynamoDb table.
90-
demoDS.createResolver({
90+
demoDS.createResolver('MutationAddDemoResolver', {
9191
typeName: 'Mutation',
9292
fieldName: 'addDemo',
9393
requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(
@@ -98,7 +98,7 @@ demoDS.createResolver({
9898
});
9999

100100
//To enable DynamoDB read consistency with the `MappingTemplate`:
101-
demoDS.createResolver({
101+
demoDS.createResolver('QueryGetDemosConsistentResolver', {
102102
typeName: 'Query',
103103
fieldName: 'getDemosConsistent',
104104
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(true),
@@ -137,7 +137,7 @@ declare const api: appsync.GraphqlApi;
137137
const rdsDS = api.addRdsDataSource('rds', cluster, secret, 'demos');
138138

139139
// Set up a resolver for an RDS query.
140-
rdsDS.createResolver({
140+
rdsDS.createResolver('QueryGetDemosRdsResolver', {
141141
typeName: 'Query',
142142
fieldName: 'getDemosRds',
143143
requestMappingTemplate: appsync.MappingTemplate.fromString(`
@@ -154,7 +154,7 @@ rdsDS.createResolver({
154154
});
155155

156156
// Set up a resolver for an RDS mutation.
157-
rdsDS.createResolver({
157+
rdsDS.createResolver('MutationAddDemoRdsResolver', {
158158
typeName: 'Mutation',
159159
fieldName: 'addDemoRds',
160160
requestMappingTemplate: appsync.MappingTemplate.fromString(`
@@ -244,7 +244,7 @@ const httpDs = api.addHttpDataSource(
244244
}
245245
);
246246

247-
httpDs.createResolver({
247+
httpDs.createResolver('MutationCallStepFunctionResolver', {
248248
typeName: 'Mutation',
249249
fieldName: 'callStepFunction',
250250
requestMappingTemplate: appsync.MappingTemplate.fromFile('request.vtl'),
@@ -275,7 +275,7 @@ const domain = new opensearch.Domain(this, 'Domain', {
275275
declare const api: appsync.GraphqlApi;
276276
const ds = api.addOpenSearchDataSource('ds', domain);
277277

278-
ds.createResolver({
278+
ds.createResolver('QueryGetTestsResolver', {
279279
typeName: 'Query',
280280
fieldName: 'getTests',
281281
requestMappingTemplate: appsync.MappingTemplate.fromString(JSON.stringify({

packages/@aws-cdk/aws-appsync/lib/data-source.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ export abstract class BaseDataSource extends Construct {
132132
/**
133133
* creates a new resolver for this datasource and API using the given properties
134134
*/
135-
public createResolver(props: BaseResolverProps): Resolver {
136-
return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, {
135+
public createResolver(id: string, props: BaseResolverProps): Resolver {
136+
return new Resolver(this.api, id, {
137137
api: this.api,
138138
dataSource: this,
139139
...props,
@@ -143,8 +143,8 @@ export abstract class BaseDataSource extends Construct {
143143
/**
144144
* creates a new appsync function for this datasource and API using the given properties
145145
*/
146-
public createFunction(props: BaseAppsyncFunctionProps): AppsyncFunction {
147-
return new AppsyncFunction(this, `${props.name}Function`, {
146+
public createFunction(id: string, props: BaseAppsyncFunctionProps): AppsyncFunction {
147+
return new AppsyncFunction(this.api, id, {
148148
api: this.api,
149149
dataSource: this,
150150
...props,

packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export interface IGraphqlApi extends IResource {
134134
/**
135135
* creates a new resolver for this datasource and API using the given properties
136136
*/
137-
createResolver(props: ExtendedResolverProps): Resolver;
137+
createResolver(id: string, props: ExtendedResolverProps): Resolver;
138138

139139
/**
140140
* Add schema dependency if not imported
@@ -285,8 +285,8 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi {
285285
/**
286286
* creates a new resolver for this datasource and API using the given properties
287287
*/
288-
public createResolver(props: ExtendedResolverProps): Resolver {
289-
return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, {
288+
public createResolver(id: string, props: ExtendedResolverProps): Resolver {
289+
return new Resolver(this, id, {
290290
api: this,
291291
...props,
292292
});

packages/@aws-cdk/aws-appsync/test/appsync-caching-config.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('Lambda caching config', () => {
3333
// WHEN
3434
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);
3535

36-
lambdaDS.createResolver({
36+
lambdaDS.createResolver('QueryAllPosts', {
3737
typeName: 'Query',
3838
fieldName: 'allPosts',
3939
});
@@ -50,7 +50,7 @@ describe('Lambda caching config', () => {
5050
// WHEN
5151
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);
5252

53-
lambdaDS.createResolver({
53+
lambdaDS.createResolver('QueryAllPosts', {
5454
typeName: 'Query',
5555
fieldName: 'allPosts',
5656
cachingConfig: {
@@ -77,7 +77,7 @@ describe('Lambda caching config', () => {
7777

7878
// THEN
7979
expect(() => {
80-
lambdaDS.createResolver({
80+
lambdaDS.createResolver('QueryAllPosts', {
8181
typeName: 'Query',
8282
fieldName: 'allPosts',
8383
cachingConfig: {
@@ -95,7 +95,7 @@ describe('Lambda caching config', () => {
9595

9696
// THEN
9797
expect(() => {
98-
lambdaDS.createResolver({
98+
lambdaDS.createResolver('QueryAllPosts', {
9999
typeName: 'Query',
100100
fieldName: 'allPosts',
101101
cachingConfig: {
@@ -113,7 +113,7 @@ describe('Lambda caching config', () => {
113113

114114
// THEN
115115
expect(() => {
116-
lambdaDS.createResolver({
116+
lambdaDS.createResolver('QueryAllPosts', {
117117
typeName: 'Query',
118118
fieldName: 'allPosts',
119119
cachingConfig: {

packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ describe('Lambda Data Source configuration', () => {
116116
description: 'custom description',
117117
});
118118

119-
ds.createResolver({
119+
ds.createResolver('TestField', {
120120
typeName: 'test',
121121
fieldName: 'field',
122122
});
@@ -164,4 +164,4 @@ describe('adding lambda data source from imported api', () => {
164164
ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] },
165165
});
166166
});
167-
});
167+
});

packages/@aws-cdk/aws-appsync/test/appsync-mapping-template.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('Lambda Mapping Templates', () => {
3434
// WHEN
3535
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);
3636

37-
lambdaDS.createResolver({
37+
lambdaDS.createResolver('QueryAllPosts', {
3838
typeName: 'Query',
3939
fieldName: 'allPosts',
4040
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(),
@@ -52,7 +52,7 @@ describe('Lambda Mapping Templates', () => {
5252
// WHEN
5353
const lambdaDS = api.addLambdaDataSource('LambdaDS', func);
5454

55-
lambdaDS.createResolver({
55+
lambdaDS.createResolver('PostRelatedPosts', {
5656
typeName: 'Post',
5757
fieldName: 'relatedPosts',
5858
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest('$util.toJson($ctx)', 'BatchInvoke'),
@@ -67,4 +67,4 @@ describe('Lambda Mapping Templates', () => {
6767
MaxBatchSize: 10,
6868
});
6969
});
70-
});
70+
});

packages/@aws-cdk/aws-appsync/test/appsync.test.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ beforeEach(() => {
2121
test('appsync should configure pipeline when pipelineConfig has contents', () => {
2222
// WHEN
2323
const ds = api.addNoneDataSource('none');
24-
const test1 = ds.createFunction({
24+
const test1 = ds.createFunction('Test1Function', {
2525
name: 'test1',
2626
});
27-
const test2 = ds.createFunction({
27+
const test2 = ds.createFunction('Test2Function', {
2828
name: 'test2',
2929
});
30-
api.createResolver({
30+
api.createResolver('TestTest2', {
3131
typeName: 'test',
3232
fieldName: 'test2',
3333
pipelineConfig: [test1, test2],
@@ -38,8 +38,8 @@ test('appsync should configure pipeline when pipelineConfig has contents', () =>
3838
Kind: 'PIPELINE',
3939
PipelineConfig: {
4040
Functions: [
41-
{ 'Fn::GetAtt': ['apinonetest1FunctionEF63046F', 'FunctionId'] },
42-
{ 'Fn::GetAtt': ['apinonetest2Function615111D0', 'FunctionId'] },
41+
{ 'Fn::GetAtt': ['apiTest1Function793605E9', 'FunctionId'] },
42+
{ 'Fn::GetAtt': ['apiTest2FunctionB704A7AD', 'FunctionId'] },
4343
],
4444
},
4545
});
@@ -48,16 +48,17 @@ test('appsync should configure pipeline when pipelineConfig has contents', () =>
4848
test('appsync should error when creating pipeline resolver with data source', () => {
4949
// WHEN
5050
const ds = api.addNoneDataSource('none');
51-
const test1 = ds.createFunction({
51+
const test1 = ds.createFunction('Test1Function', {
5252
name: 'test1',
5353
});
54-
const test2 = ds.createFunction({
54+
const test2 = ds.createFunction('Test2Function', {
5555
name: 'test2',
5656
});
5757

5858
// THEN
5959
expect(() => {
60-
ds.createResolver({
60+
api.createResolver('TestTest2', {
61+
dataSource: ds,
6162
typeName: 'test',
6263
fieldName: 'test2',
6364
pipelineConfig: [test1, test2],
@@ -83,7 +84,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty', (
8384

8485
test('appsync should configure resolver as unit when pipelineConfig is empty array', () => {
8586
// WHEN
86-
api.createResolver({
87+
api.createResolver('TestTest2', {
8788
typeName: 'test',
8889
fieldName: 'test2',
8990
pipelineConfig: [],
@@ -237,4 +238,4 @@ test('log retention should not appear when no retention time is specified', () =
237238

238239
// THEN
239240
Template.fromStack(stack).resourceCountIs('Custom::LogRetention', 0);
240-
});
241+
});

packages/@aws-cdk/aws-appsync/test/integ.api-import.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ const testTable = new db.Table(stack, 'TestTable', {
4343

4444
const testDS = api.addDynamoDbDataSource('ds', testTable);
4545

46-
testDS.createResolver({
46+
testDS.createResolver('QueryGetTests', {
4747
typeName: 'Query',
4848
fieldName: 'getTests',
4949
requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(),
5050
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
5151
});
5252

53-
testDS.createResolver({
53+
testDS.createResolver('MutationAddTest', {
5454
typeName: 'Mutation',
5555
fieldName: 'addTest',
5656
requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(appsync.PrimaryKey.partition('id').auto(), appsync.Values.projecting('test')),
@@ -64,7 +64,7 @@ const api2 = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'api2', {
6464

6565
const none = api2.addNoneDataSource('none');
6666

67-
const func = none.createFunction({
67+
const func = none.createFunction('PipelineFunction', {
6868
name: 'pipeline_function',
6969
requestMappingTemplate: appsync.MappingTemplate.fromString(JSON.stringify({
7070
version: '2017-02-28',
@@ -87,4 +87,4 @@ new appsync.Resolver(stack, 'pipeline_resolver', {
8787
})),
8888
});
8989

90-
app.synth();
90+
app.synth();

packages/@aws-cdk/aws-appsync/test/integ.appsync-lambda.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,40 +47,40 @@ const requestPayload = (field: string, { withArgs = false, withSource = false })
4747
};
4848
const responseMappingTemplate = appsync.MappingTemplate.lambdaResult();
4949

50-
lambdaDS.createResolver({
50+
lambdaDS.createResolver('QueryGetPost', {
5151
typeName: 'Query',
5252
fieldName: 'getPost',
5353
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(requestPayload('getPost', { withArgs: true })),
5454
responseMappingTemplate,
5555
});
5656

57-
lambdaDS.createResolver({
57+
lambdaDS.createResolver('QueryAllPosts', {
5858
typeName: 'Query',
5959
fieldName: 'allPosts',
6060
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(requestPayload('allPosts', {})),
6161
responseMappingTemplate,
6262
});
6363

64-
lambdaDS.createResolver({
64+
lambdaDS.createResolver('MutationAddPost', {
6565
typeName: 'Mutation',
6666
fieldName: 'addPost',
6767
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(requestPayload('addPost', { withArgs: true })),
6868
responseMappingTemplate,
6969
});
7070

71-
lambdaDS.createResolver({
71+
lambdaDS.createResolver('PostRelatedPosts', {
7272
typeName: 'Post',
7373
fieldName: 'relatedPosts',
7474
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(requestPayload('relatedPosts', { withSource: true }), 'BatchInvoke'),
7575
responseMappingTemplate,
7676
});
7777

78-
lambdaDS.createResolver({
78+
lambdaDS.createResolver('PostRelatedPostsMaxBatchSize', {
7979
typeName: 'Post',
8080
fieldName: 'relatedPostsMaxBatchSize',
8181
requestMappingTemplate: appsync.MappingTemplate.lambdaRequest(requestPayload('relatedPostsMaxBatchSize', { withSource: true }), 'BatchInvoke'),
8282
responseMappingTemplate,
8383
maxBatchSize: 2,
8484
});
8585

86-
app.synth();
86+
app.synth();

packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.assets.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "21.0.0",
2+
"version": "22.0.0",
33
"files": {
4-
"b0462850439179659920597f4327262b24073af4f4969622163b0a295fce1dda": {
4+
"de7d932209c6d07c0ba0e631387676246a8182018b2b7423dc18b52baec3e984": {
55
"source": {
66
"path": "aws-appsync-integ.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "b0462850439179659920597f4327262b24073af4f4969622163b0a295fce1dda.json",
12+
"objectKey": "de7d932209c6d07c0ba0e631387676246a8182018b2b7423dc18b52baec3e984.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/aws-appsync-integ.template.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
}
122122
}
123123
},
124-
"ApitestDataSourceQuerygetTestsResolverA3BBB672": {
124+
"ApiQueryGetTestsF8C40170": {
125125
"Type": "AWS::AppSync::Resolver",
126126
"Properties": {
127127
"ApiId": {
@@ -142,7 +142,7 @@
142142
"ApitestDataSource96AE54D5"
143143
]
144144
},
145-
"ApitestDataSourceMutationaddTestResolver36203D6B": {
145+
"ApiMutationAddTestBF148084": {
146146
"Type": "AWS::AppSync::Resolver",
147147
"Properties": {
148148
"ApiId": {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"21.0.0"}
1+
{"version":"22.0.0"}

packages/@aws-cdk/aws-appsync/test/integ.auth-apikey.js.snapshot/integ.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "21.0.0",
2+
"version": "22.0.0",
33
"testCases": {
44
"integ.auth-apikey": {
55
"stacks": [

0 commit comments

Comments
 (0)