Skip to content

Commit

Permalink
feat(appsync): support appsync functions for pipelineConfig (aws#10111)
Browse files Browse the repository at this point in the history
Support AppSync Function by exposing Function Configuration. 

**BREAKING CHANGES**: `Resolver.pipelineConfig` no longer supports `string[]`
- **AppSync**: `Resolver.pipelineConfig` no longer supports `string[]`, instead use `AppsyncFunction`
- **AppSync**: Resolvers are scoped from `GraphqlApi` instead of its `Data Source`, this means that the **logical id** of resolvers will change!

Fixes aws#9092

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
BryanPan342 authored and skiyani committed Dec 7, 2020
1 parent db9b95e commit 68a4e98
Show file tree
Hide file tree
Showing 21 changed files with 632 additions and 267 deletions.
38 changes: 35 additions & 3 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,38 @@ api.grantMutation(role, 'updateExample');
api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL');
```

### Pipeline Resolvers and AppSync Functions

AppSync Functions are local functions that perform certain operations onto a
backend data source. Developers can compose operations (Functions) and execute
them in sequence with Pipeline Resolvers.

```ts
const appsyncFunction = new appsync.AppsyncFunction(stack, 'function', {
name: 'appsync_function',
api: api,
dataSource: apiDataSource,
requestMappingTemplate: appsync.MappingTemplate.fromFile('request.vtl'),
responseMappingTemplate: appsync.MappingTemplate.fromFile('response.vtl'),
});
```

AppSync Functions are used in tandem with pipeline resolvers to compose multiple
operations.

```ts
const pipelineResolver = new appsync.Resolver(stack, 'pipeline', {
name: 'pipeline_resolver',
api: api,
dataSource: apiDataSource,
requestMappingTemplate: appsync.MappingTemplate.fromFile('beforeRequest.vtl'),
pipelineConfig: [appsyncFunction],
responseMappingTemplate: appsync.MappingTemplate.fromFile('afterResponse.vtl'),
});
```

Learn more about Pipeline Resolvers and AppSync Functions [here](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html).

### Code-First Schema

CDK offers the ability to generate your schema in a code-first approach.
Expand Down Expand Up @@ -543,7 +575,7 @@ To learn more about authorization and directives, read these docs [here](https:/
While `GraphqlType` is a base implementation for GraphQL fields, we have abstractions
on top of `GraphqlType` that provide finer grain support.

##### Field
#### Field

`Field` extends `GraphqlType` and will allow you to define arguments. [**Interface Types**](#Interface-Types) are not resolvable and this class will allow you to define arguments,
but not its resolvers.
Expand All @@ -570,7 +602,7 @@ const type = new appsync.InterfaceType('Node', {
});
```

##### Resolvable Fields
#### Resolvable Fields

`ResolvableField` extends `Field` and will allow you to define arguments and its resolvers.
[**Object Types**](#Object-Types) can have fields that resolve and perform operations on
Expand Down Expand Up @@ -646,7 +678,7 @@ Intermediate Types include:
- [**Input Types**](#Input-Types)
- [**Union Types**](#Union-Types)

##### Interface Types
#### Interface Types

**Interface Types** are abstract types that define the implementation of other
intermediate types. They are useful for eliminating duplication and can be used
Expand Down
148 changes: 148 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/appsync-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Resource, IResource, Lazy, Fn } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnFunctionConfiguration } from './appsync.generated';
import { BaseDataSource } from './data-source';
import { IGraphqlApi } from './graphqlapi-base';
import { MappingTemplate } from './mapping-template';

/**
* the base properties for AppSync Functions
*/
export interface BaseAppsyncFunctionProps {
/**
* the name of the AppSync Function
*/
readonly name: string;
/**
* the description for this AppSync Function
*
* @default - no description
*/
readonly description?: string;
/**
* the request mapping template for the AppSync Function
*
* @default - no request mapping template
*/
readonly requestMappingTemplate?: MappingTemplate;
/**
* the response mapping template for the AppSync Function
*
* @default - no response mapping template
*/
readonly responseMappingTemplate?: MappingTemplate;
}

/**
* the CDK properties for AppSync Functions
*/
export interface AppsyncFunctionProps extends BaseAppsyncFunctionProps {
/**
* the GraphQL Api linked to this AppSync Function
*/
readonly api: IGraphqlApi;
/**
* the data source linked to this AppSync Function
*/
readonly dataSource: BaseDataSource;
}

/**
* The attributes for imported AppSync Functions
*/
export interface AppsyncFunctionAttributes {
/**
* the ARN of the AppSync function
*/
readonly functionArn: string;
}

/**
* Interface for AppSync Functions
*/
export interface IAppsyncFunction extends IResource {
/**
* the name of this AppSync Function
*
* @attribute
*/
readonly functionId: string;
/**
* the ARN of the AppSync function
*
* @attribute
*/
readonly functionArn: string;
}

/**
* AppSync Functions are local functions that perform certain operations
* onto a backend data source. Developers can compose operations (Functions)
* and execute them in sequence with Pipeline Resolvers.
*
* @resource AWS::AppSync::FunctionConfiguration
*/
export class AppsyncFunction extends Resource implements IAppsyncFunction {
/**
* Import Appsync Function from arn
*/
public static fromAppsyncFunctionAttributes(scope: Construct, id: string, attrs: AppsyncFunctionAttributes): IAppsyncFunction {
class Import extends Resource {
public readonly functionId = Lazy.stringValue({
produce: () => Fn.select(3, Fn.split('/', attrs.functionArn)),
});
public readonly functionArn = attrs.functionArn;
constructor (s: Construct, i: string) {
super(s, i);
}
}
return new Import(scope, id);
}

/**
* the name of this AppSync Function
*
* @attribute Name
*/
public readonly functionName: string;
/**
* the ARN of the AppSync function
*
* @attribute
*/
public readonly functionArn: string;
/**
* the ID of the AppSync function
*
* @attribute
*/
public readonly functionId: string;
/**
* the data source of this AppSync Function
*
* @attribute DataSourceName
*/
public readonly dataSource: BaseDataSource;

private readonly function: CfnFunctionConfiguration;

public constructor(scope: Construct, id: string, props: AppsyncFunctionProps) {
super(scope, id);
this.function = new CfnFunctionConfiguration(this, 'Resource', {
name: props.name,
description: props.description,
apiId: props.api.apiId,
dataSourceName: props.dataSource.name,
functionVersion: '2018-05-29',
requestMappingTemplate: props.requestMappingTemplate?.renderTemplate(),
responseMappingTemplate: props.responseMappingTemplate?.renderTemplate(),
});
this.functionName = this.function.attrName;
this.functionArn = this.function.attrFunctionArn;
this.functionId = this.function.attrFunctionId;
this.dataSource = props.dataSource;

this.function.addDependsOn(this.dataSource.ds);
props.api.addSchemaDependency(this.function);
}
}
11 changes: 9 additions & 2 deletions packages/@aws-cdk/aws-appsync/lib/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IDatabaseCluster } from '@aws-cdk/aws-rds';
import { ISecret } from '@aws-cdk/aws-secretsmanager';
import { IResolvable, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { BaseAppsyncFunctionProps, AppsyncFunction } from './appsync-function';
import { CfnDataSource } from './appsync.generated';
import { IGraphqlApi } from './graphqlapi-base';
import { BaseResolverProps, Resolver } from './resolver';
Expand Down Expand Up @@ -125,7 +126,14 @@ export abstract class BaseDataSource extends CoreConstruct {
* creates a new resolver for this datasource and API using the given properties
*/
public createResolver(props: BaseResolverProps): Resolver {
return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, {
return this.api.createResolver({ dataSource: this, ...props });
}

/**
* creates a new appsync function for this datasource and API using the given properties
*/
public createFunction(props: BaseAppsyncFunctionProps): AppsyncFunction {
return new AppsyncFunction(this, `${props.name}Function`, {
api: this.api,
dataSource: this,
...props,
Expand Down Expand Up @@ -172,7 +180,6 @@ export class NoneDataSource extends BaseDataSource {
export interface DynamoDbDataSourceProps extends BackedDataSourceProps {
/**
* The DynamoDB table backing this data source
* [disable-awslint:ref-via-interface]
*/
readonly table: ITable;
/**
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IDatabaseCluster } from '@aws-cdk/aws-rds';
import { ISecret } from '@aws-cdk/aws-secretsmanager';
import { CfnResource, IResource, Resource } from '@aws-cdk/core';
import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource, RdsDataSource, AwsIamConfig } from './data-source';
import { Resolver, ExtendedResolverProps } from './resolver';

/**
* Optional configuration for data sources
Expand Down Expand Up @@ -107,6 +108,11 @@ export interface IGraphqlApi extends IResource {
options?: DataSourceOptions
): RdsDataSource;

/**
* creates a new resolver for this datasource and API using the given properties
*/
createResolver(props: ExtendedResolverProps): Resolver;

/**
* Add schema dependency if not imported
*
Expand Down Expand Up @@ -217,6 +223,16 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi {
});
}

/**
* creates a new resolver for this datasource and API using the given properties
*/
public createResolver(props: ExtendedResolverProps): Resolver {
return new Resolver(this, `${props.typeName}${props.fieldName}Resolver`, {
api: this,
...props,
});
}

/**
* Add schema dependency if not imported
*
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appsync/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// AWS::AppSync CloudFormation Resources:
export * from './appsync-function';
export * from './appsync.generated';
export * from './key';
export * from './data-source';
Expand Down
29 changes: 21 additions & 8 deletions packages/@aws-cdk/aws-appsync/lib/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Construct } from 'constructs';
import { IAppsyncFunction } from './appsync-function';
import { CfnResolver } from './appsync.generated';
import { BaseDataSource } from './data-source';
import { IGraphqlApi } from './graphqlapi-base';
Expand Down Expand Up @@ -26,7 +27,7 @@ export interface BaseResolverProps {
* @default - no pipeline resolver configuration
* An empty array | undefined sets resolver to be of kind, unit
*/
readonly pipelineConfig?: string[];
readonly pipelineConfig?: IAppsyncFunction[];
/**
* The request mapping template for this resolver
*
Expand All @@ -42,13 +43,9 @@ export interface BaseResolverProps {
}

/**
* Additional properties for an AppSync resolver like GraphQL API reference and datasource
* Additional property for an AppSync resolver for data source reference
*/
export interface ResolverProps extends BaseResolverProps {
/**
* The API this resolver is attached to
*/
readonly api: IGraphqlApi;
export interface ExtendedResolverProps extends BaseResolverProps {
/**
* The data source this resolver is using
*
Expand All @@ -57,6 +54,16 @@ export interface ResolverProps extends BaseResolverProps {
readonly dataSource?: BaseDataSource;
}

/**
* Additional property for an AppSync resolver for GraphQL API reference
*/
export interface ResolverProps extends ExtendedResolverProps {
/**
* The API this resolver is attached to
*/
readonly api: IGraphqlApi;
}

/**
* An AppSync resolver
*/
Expand All @@ -71,7 +78,13 @@ export class Resolver extends CoreConstruct {
constructor(scope: Construct, id: string, props: ResolverProps) {
super(scope, id);

const pipelineConfig = props.pipelineConfig && props.pipelineConfig.length ? { functions: props.pipelineConfig } : undefined;
const pipelineConfig = props.pipelineConfig && props.pipelineConfig.length ?
{ functions: props.pipelineConfig.map((func) => func.functionId) }
: undefined;

if (pipelineConfig && props.dataSource) {
throw new Error(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`);
}

this.resolver = new CfnResolver(this, 'Resource', {
apiId: props.api.apiId,
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-appsync/lib/schema-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export interface IIntermediateType {
/**
* Method called when the stringifying Intermediate Types for schema generation
*
* @param api The binding GraphQL Api [disable-awslint:ref-via-interface]
* @param api The binding GraphQL Api
*
* @internal
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-appsync/lib/schema-field.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IAppsyncFunction } from './appsync-function';
import { BaseDataSource } from './data-source';
import { AuthorizationType } from './graphqlapi';
import { MappingTemplate } from './mapping-template';
Expand Down Expand Up @@ -423,7 +424,7 @@ export interface ResolvableFieldOptions extends FieldOptions {
* @default - no pipeline resolver configuration
* An empty array or undefined prop will set resolver to be of type unit
*/
readonly pipelineConfig?: string[];
readonly pipelineConfig?: IAppsyncFunction[];
/**
* The request mapping template for this resolver
*
Expand Down
Loading

0 comments on commit 68a4e98

Please sign in to comment.