Skip to content

feat(aws-appsync): add new handlerConfig properties #34387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions packages/aws-cdk-lib/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1421,3 +1421,75 @@ api.addChannelNamespace('lambda-direct-async-ns', {
},
});
```

### Advanced Handler Configuration

For more fine-grained control over channel namespace handlers, you can use the `handlerConfigs` property to directly configure the handler behavior and integration options. This provides a more flexible way to define how your handlers work with data sources.

```ts
declare const api: appsync.EventApi;
declare const lambdaDataSource: appsync.AppSyncLambdaDataSource;
declare const ddbDataSource: appsync.AppSyncDynamoDbDataSource;

// Using direct handlerConfigs property for advanced configuration
api.addChannelNamespace('advanced-handlers', {
handlerConfigs: {
onPublish: {
behavior: appsync.HandlerBehavior.DIRECT,
integration: {
dataSourceName: lambdaDataSource.name,
lambdaConfig: {
invokeType: appsync.LambdaInvokeType.EVENT,
},
},
},
onSubscribe: {
behavior: appsync.HandlerBehavior.CODE,
integration: {
dataSourceName: ddbDataSource.name,
},
},
},
code: appsync.Code.fromInline('/* Used for CODE behavior handler only */'),
});

// Using handlerConfigs with only publish handler
new appsync.ChannelNamespace(this, 'PublishOnlyHandler', {
api,
handlerConfigs: {
onPublish: {
behavior: appsync.HandlerBehavior.DIRECT,
integration: {
dataSourceName: lambdaDataSource.name,
},
},
},
});
```

The `handlerConfigs` property accepts a configuration object with optional `onPublish` and `onSubscribe` handlers. Each handler requires a `behavior` that can be either `CODE` or `DIRECT`, and an `integration` configuration specifying the data source to use.

When using `HandlerBehavior.DIRECT` with Lambda data sources, you can also specify the Lambda invocation type:

```ts
declare const api: appsync.EventApi;
declare const lambdaDataSource: appsync.AppSyncLambdaDataSource;

// Configure Lambda invocation type for direct handler
new appsync.ChannelNamespace(this, 'AsyncLambdaHandler', {
api,
handlerConfigs: {
onPublish: {
behavior: appsync.HandlerBehavior.DIRECT,
integration: {
dataSourceName: lambdaDataSource.name,
lambdaConfig: {
invokeType: appsync.LambdaInvokeType.EVENT, // Asynchronous invocation
},
},
},
},
});
```

Note that when both publish and subscribe handlers use `HandlerBehavior.DIRECT`, you should not provide a `code` property, as code handlers are not used with direct data source behavior.
140 changes: 114 additions & 26 deletions packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,69 @@ export enum HandlerBehavior {
DIRECT = 'DIRECT',
}

/**
* Lambda invocation type configuration for handler integration
*/
export interface LambdaConfigOptions {
/**
* The Lambda invoke type for the integration
*
* @default - LambdaInvokeType.REQUEST_RESPONSE
*/
readonly invokeType?: LambdaInvokeType;
}

/**
* Integration configuration for handlers
*/
export interface IntegrationOptions {
/**
* Data source to invoke for this integration
*/
readonly dataSourceName: string;

/**
* Configuration for Lambda integration
*
* @default - No Lambda specific configuration
*/
readonly lambdaConfig?: LambdaConfigOptions;
}

/**
* Configuration for individual event handlers
*/
export interface HandlerConfigOptions {
/**
* The behavior of the handler
*/
readonly behavior: HandlerBehavior;

/**
* Integration configuration for the handler
*/
readonly integration: IntegrationOptions;
}

/**
* Configuration for all handlers in the channel namespace
*/
export interface HandlerConfigsOptions {
/**
* Handler configuration for publish events
*
* @default - No publish handler configured
*/
readonly onPublish?: HandlerConfigOptions;

/**
* Handler configuration for subscribe events
*
* @default - No subscribe handler configured
*/
readonly onSubscribe?: HandlerConfigOptions;
}

/**
* Handler configuration construct for onPublish and onSubscribe
*/
Expand Down Expand Up @@ -114,6 +177,13 @@ export interface BaseChannelNamespaceProps {
*/
readonly subscribeHandlerConfig?: HandlerConfig;

/**
* Direct configuration for handler configs
*
* @default - Handler configs derived from publishHandlerConfig and subscribeHandlerConfig
*/
readonly handlerConfigs?: HandlerConfigsOptions;

/**
* Authorization config for channel namespace
*
Expand Down Expand Up @@ -213,38 +283,45 @@ export class ChannelNamespace extends Resource implements IChannelNamespace {

let handlerConfig: { [key: string]: any } = {};

if (props.publishHandlerConfig) {
handlerConfig = {
onPublish: {
behavior: props.publishHandlerConfig?.direct ? HandlerBehavior.DIRECT : HandlerBehavior.CODE,
integration: {
dataSourceName: props.publishHandlerConfig?.dataSource?.name || '',
// If direct handlerConfigs is provided, use it
if (props.handlerConfigs) {
handlerConfig = props.handlerConfigs;
}
// Otherwise, build from publishHandlerConfig and subscribeHandlerConfig for backward compatibility
else {
if (props.publishHandlerConfig) {
handlerConfig = {
onPublish: {
behavior: props.publishHandlerConfig?.direct ? HandlerBehavior.DIRECT : HandlerBehavior.CODE,
integration: {
dataSourceName: props.publishHandlerConfig?.dataSource?.name || '',
},
},
},
};

if (handlerConfig.onPublish.behavior === HandlerBehavior.DIRECT) {
handlerConfig.onPublish.integration.lambdaConfig = {
invokeType: props.publishHandlerConfig?.lambdaInvokeType || LambdaInvokeType.REQUEST_RESPONSE,
};

if (handlerConfig.onPublish.behavior === HandlerBehavior.DIRECT) {
handlerConfig.onPublish.integration.lambdaConfig = {
invokeType: props.publishHandlerConfig?.lambdaInvokeType || LambdaInvokeType.REQUEST_RESPONSE,
};
}
}
}

if (props.subscribeHandlerConfig) {
handlerConfig = {
...handlerConfig,
onSubscribe: {
behavior: props.subscribeHandlerConfig?.direct ? HandlerBehavior.DIRECT : HandlerBehavior.CODE,
integration: {
dataSourceName: props.subscribeHandlerConfig?.dataSource?.name || '',
if (props.subscribeHandlerConfig) {
handlerConfig = {
...handlerConfig,
onSubscribe: {
behavior: props.subscribeHandlerConfig?.direct ? HandlerBehavior.DIRECT : HandlerBehavior.CODE,
integration: {
dataSourceName: props.subscribeHandlerConfig?.dataSource?.name || '',
},
},
},
};

if (handlerConfig.onSubscribe.behavior === HandlerBehavior.DIRECT) {
handlerConfig.onSubscribe.integration.lambdaConfig = {
invokeType: props.subscribeHandlerConfig?.lambdaInvokeType || LambdaInvokeType.REQUEST_RESPONSE,
};

if (handlerConfig.onSubscribe.behavior === HandlerBehavior.DIRECT) {
handlerConfig.onSubscribe.integration.lambdaConfig = {
invokeType: props.subscribeHandlerConfig?.lambdaInvokeType || LambdaInvokeType.REQUEST_RESPONSE,
};
}
}
}

Expand Down Expand Up @@ -317,6 +394,17 @@ export class ChannelNamespace extends Resource implements IChannelNamespace {
}

private validateHandlerConfig(props?: ChannelNamespaceProps) {
// Handle the case when direct handlerConfigs is provided
if (props?.handlerConfigs) {
// If code is provided when both handlers are direct, it's an error
const onPublishDirect = props.handlerConfigs.onPublish?.behavior === HandlerBehavior.DIRECT;
const onSubscribeDirect = props.handlerConfigs.onSubscribe?.behavior === HandlerBehavior.DIRECT;
if (onPublishDirect && onSubscribeDirect && props.code) {
throw new ValidationError('Code handlers are not supported when both publish and subscribe use the Direct data source behavior', this);
}
return;
}

// Handle the case when no handler configs are defined for publish or subscribe
if (!props?.publishHandlerConfig && !props?.subscribeHandlerConfig) return undefined;

Expand Down
Loading
Loading