🐩
CDKでJSリゾルバーとLambdaリゾルバーのデプロイ方法を揃える
AppSyncの構築をCDKでやっている際に、Typescriptで書いたJavaScriptリゾルバーのソースをデプロイする手段について悩んでいたのですが公式ドキュメントを確認したところ、esbuildを使う方法が紹介されていたのでそれに倣いCDKのカスタムコンストラクトを用意してTypescriptのリゾルバーをビルドからデプロイまで行いたいと思います。
今回は以下の3つの種類のリゾルバーのコンストラクトを用意したいと思います。
- ユニットリゾルバー
- パイプラインリゾルバー
- Lambdaリゾルバー
コンパイル用のutilityを用意
まずはコンパイル用のutilityを用意します。
compileFileと言う関数をutilityに用意してカスタムコンストラクトからこれを呼び出すようにしています。
utilities.ts
import type { OutputFile } from 'esbuild'
import { buildSync } from 'esbuild'
export const compileFile = (entryPoint: string): OutputFile => {
const result = buildSync({
entryPoints: [entryPoint],
bundle: true,
write: false,
external: ['@aws-appsync/utils'],
format: 'esm',
target: 'esnext',
platform: 'node',
sourcemap: 'inline',
sourcesContent: false,
})
if (result.errors.length > 0) {
throw new Error(
`Failed to build ${entryPoint}: ${result.errors.join('\n')}`,
)
}
if (!result.outputFiles[0]) {
throw new Error(`Failed to build ${entryPoint}: no output files`)
}
return result.outputFiles[0]
}
ユニットリゾルバーのコンストラクト
次にユニットリゾルバー用のカスタムコンストラクトです。
propsでdataSourceとfieldName, typeName, resolverCodeのファイルパスを渡してcompileFileを実行するような構成になってます。
jsUnitResolverConstruct.ts
import * as appsync from 'aws-cdk-lib/aws-appsync'
import { Construct } from 'constructs'
import { compileFile } from './utilities'
interface JsUnitResolverProps {
api: appsync.GraphqlApi
dataSource: appsync.BaseDataSource
typeName: 'Query' | 'Mutation' | string
fieldName: string
source: string
resolverCode?: string
}
/**
* JSのユニットリゾルバーをデプロイするカスタムコンストラクト
*/
export class JsUnitResolver extends Construct {
public readonly resolver: appsync.Resolver
constructor(scope: Construct, id: string, props: JsUnitResolverProps) {
super(scope, id)
const runtime = appsync.FunctionRuntime.JS_1_0_0
this.resolver = props.dataSource.createResolver(
`${props.fieldName}${props.typeName}Resolver`,
{
runtime,
typeName: props.typeName,
fieldName: props.fieldName,
code: appsync.Code.fromInline(compileFile(props.source).text),
},
)
}
}
パイプラインリゾルバーのコンストラクト
次にパイプラインリゾルバーです。
こちらもユニットリゾルバーと要領は同じでAppsyncFunctionの定義を追加しています。
jsPipelineResolverConstruct.ts
import * as appsync from 'aws-cdk-lib/aws-appsync'
import { Construct } from 'constructs'
import { compileFile } from './utilities'
interface JsPipelineResolverProps {
api: appsync.GraphqlApi
dataSource: appsync.BaseDataSource
typeName: 'Query' | 'Mutation' | string
fieldName: string
functions: {
name?: string
dataSource?: appsync.BaseDataSource
filePath: string
}[]
resolverCode: string
}
/**
* 複数の関数で構成されるJSパイプラインリゾルバーをデプロイするカスタムコンストラクト
*/
export class JsPipelineResolver extends Construct {
public readonly resolver: appsync.Resolver
constructor(scope: Construct, id: string, props: JsPipelineResolverProps) {
super(scope, id)
const resolverCode = compileFile(props.resolverCode).text
const runtime = appsync.FunctionRuntime.JS_1_0_0
const funcs = props.functions.map((func, idx) => {
const funcName =
func.name ?? `${props.fieldName + props.typeName}Func${idx + 1}`
return new appsync.AppsyncFunction(this, funcName, {
api: props.api,
dataSource: func.dataSource ?? props.dataSource,
name: funcName,
code: appsync.Code.fromInline(compileFile(func.filePath).text),
runtime,
})
})
this.resolver = props.dataSource.createResolver(`${props.fieldName}${props.typeName}Resolver`, {
runtime: appsync.FunctionRuntime.JS_1_0_0,
typeName: props.typeName,
fieldName: props.fieldName,
pipelineConfig: funcs,
code: appsync.Code.fromInline(resolverCode),
})
}
}
Lambdaリゾルバーのコンストラクト
Lambdaリゾルバーの場合はデータソースもLambda毎に変わることになるのでコンストラクト内でdataSourceを作成しています。
関数によってアクセス先は制御したいのでexecutionRoleも各リゾルバーで設定するようにしています。
lambdaResolverConstruct.ts
import * as appsync from 'aws-cdk-lib/aws-appsync'
import { IRole } from 'aws-cdk-lib/aws-iam'
import { Runtime } from 'aws-cdk-lib/aws-lambda'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import { Construct } from 'constructs'
interface lambdaResolverProps {
api: appsync.GraphqlApi
typeName: 'Query' | 'Mutation' | string
fieldName: string
entry: string
executionRole?: IRole
environment?: { [key: string]: string }
}
/**
* Lambdaリゾルバーをデータソース, Lambda関数含めてビルド・デプロイするカスタムコンストラクト
*/
export class lambdaResolver extends Construct {
public readonly resolver: appsync.Resolver
constructor(scope: Construct, id: string, props: lambdaResolverProps) {
super(scope, id)
const dataSource = props.api.addLambdaDataSource(
'LambdaDataSource',
new NodejsFunction(this, 'Lambda', {
functionName: props.fieldName,
entry: props.entry,
handler: 'handler',
runtime: Runtime.NODEJS_18_X,
role: props.executionRole,
environment: props.environment,
}),
)
this.resolver = dataSource.createResolver('LambdaResolver', {
typeName: props.typeName,
fieldName: props.fieldName,
})
}
}
参考
Discussion