Skip to content
This repository has been archived by the owner on Sep 30, 2019. It is now read-only.

Implement lambda BatchInvoke #99

Merged
merged 3 commits into from Mar 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/appsync-emulator-serverless/__test__/example/handler.js
Expand Up @@ -3,6 +3,18 @@ module.exports.graphqlJSON = (event, context, callback) =>
test: 'yup',
});

module.exports.graphqlArrayJSON = (event, context, callback) =>
callback(null, [{ test: 'yup.0' }, { test: 'yup.1' }]);

module.exports.graphqlBatchJSON = (events, context, callback) =>
callback(
null,
events.map(e => [
{ test: `${e.source.test}.0` },
{ test: `${e.source.test}.1` },
]),
);

module.exports.graphql = (event, context, callback) => {
const field = Object.keys(event.arguments)[0];
return callback(null, event.arguments[field]);
Expand Down
@@ -0,0 +1,8 @@
{
"version": "2017-02-28",
"operation": "BatchInvoke",
"payload": {
"arguments": $utils.toJson($context.arguments),
"source": $utils.toJson($context.source)
}
}
Expand Up @@ -11,6 +11,7 @@ type Query {
QuoteResponse(query: [AttributeFilter]): [QuoteResponse]!
cognitoInfo: CognitoInfo!
lambda: LambdaInfo!
lambdaBatch: [LambdaInfo]
lambdaPython: LambdaInfo!
lambdaRuby: LambdaInfo!
lambdaGo: LambdaInfo!
Expand Down Expand Up @@ -77,6 +78,7 @@ type CognitoInfo {

type LambdaInfo {
test: String!
lambdaChild: [LambdaInfo]
}

type QuoteRequest {
Expand Down
Expand Up @@ -122,6 +122,16 @@ custom:
type: Query
request: lambda-request.vtl
response: result-response.txt
- dataSource: TestLambdasJSON
type: Query
field: lambdaBatch
request: lambda-request.vtl
response: result-response.txt
- dataSource: TestBatchLambdaJSON
type: LambdaInfo
field: lambdaChild
request: lambda-batch-request.vtl
response: result-response.txt
- dataSource: TestLambdaJSON
type: Query
field: jsonTest
Expand Down Expand Up @@ -205,6 +215,20 @@ custom:
lambdaFunctionArn: { Fn::GetAtt: [GraphqlLambdaFunction, Arn] }
serviceRoleArn: { Fn::GetAtt: [AppSyncLambdaServiceRole, Arn] }
functionName: graphqlJSON
- type: AWS_LAMBDA
name: TestLambdasJSON
description: 'Lambda JSON array DataSource'
config:
lambdaFunctionArn: { Fn::GetAtt: [GraphqlLambdasFunction, Arn] }
serviceRoleArn: { Fn::GetAtt: [AppSyncLambdaServiceRole, Arn] }
functionName: graphqlArrayJSON
- type: AWS_LAMBDA
name: TestBatchLambdaJSON
description: 'Lambda JSON DataSource'
config:
lambdaFunctionArn: { Fn::GetAtt: [GraphqlLambdaFunction, Arn] }
serviceRoleArn: { Fn::GetAtt: [AppSyncLambdaServiceRole, Arn] }
functionName: graphqlBatchJSON
- type: HTTP
name: TestHTTP
description: 'Test HTTP endpoint'
Expand Down Expand Up @@ -234,6 +258,10 @@ custom:
functions:
graphqlJSON:
handler: handler.graphqlJSON
graphqlArrayJSON:
handler: handler.graphqlArrayJSON
graphqlBatchJSON:
handler: handler.graphqlBatchJSON
graphql:
handler: handler.graphql
emptyJSON:
Expand Down
53 changes: 53 additions & 0 deletions packages/appsync-emulator-serverless/__test__/schemaTest.test.js
Expand Up @@ -223,6 +223,59 @@ describe('creates executable schema', () => {
expect(result).toMatchObject({ data: { lambda: { test: 'yup' } } });
});

it('should allow querying lambda in batch', async () => {
const result = await graphql({
schema,
contextValue,
source: `
query test {
lambdaBatch {
test
lambdaChild {
test
lambdaChild {
test
}
}
}
}
`,
});

expect(result).toMatchObject({
data: {
lambdaBatch: [
{
test: 'yup.0',
lambdaChild: [
{
test: 'yup.0.0',
lambdaChild: [{ test: 'yup.0.0.0' }, { test: 'yup.0.0.1' }],
},
{
test: 'yup.0.1',
lambdaChild: [{ test: 'yup.0.1.0' }, { test: 'yup.0.1.1' }],
},
],
},
{
test: 'yup.1',
lambdaChild: [
{
test: 'yup.1.0',
lambdaChild: [{ test: 'yup.1.0.0' }, { test: 'yup.1.0.1' }],
},
{
test: 'yup.1.1',
lambdaChild: [{ test: 'yup.1.1.0' }, { test: 'yup.1.1.1' }],
},
],
},
],
},
});
});

it('should allow querying lambda python', async () => {
const result = await graphql({
schema,
Expand Down
1 change: 1 addition & 0 deletions packages/appsync-emulator-serverless/package.json
Expand Up @@ -14,6 +14,7 @@
"aws-sdk": "^2.254.1",
"consola": "^1.4.1",
"cors": "^2.8.4",
"dataloader": "^1.4.0",
"dateformat": "^3.0.3",
"event-to-promise": "^0.8.0",
"express": "^4.16.3",
Expand Down
63 changes: 46 additions & 17 deletions packages/appsync-emulator-serverless/schema.js
Expand Up @@ -16,6 +16,7 @@ const log = require('logdown')('appsync-emulator:schema');
const consola = require('./log');
const { inspect } = require('util');
const { scalars } = require('./schemaWrapper');
const DataLoader = require('dataloader');

const vtlMacros = {
console: (...args) => {
Expand Down Expand Up @@ -187,31 +188,58 @@ const dispatchRequestToSource = async (
}
};

const generateDataLoaderResolver = (source, configs) => {
const batchLoaders = {};
return fieldPath => {
if (batchLoaders[fieldPath] === undefined) {
batchLoaders[fieldPath] = new DataLoader(
requests => {
const batchRequest = requests[0];
batchRequest.payload = requests.map(r => r.payload);
consola.info(
'Rendered Batch Request:\n',
inspect(batchRequest, { depth: null, colors: true }),
);
log.info('resolver batch request', batchRequest);
return dispatchRequestToSource(source, configs, batchRequest);
},
{
shouldCache: false,
},
);
}

return batchLoaders[fieldPath];
};
};

const generateTypeResolver = (
source,
configs,
{ requestPath, responsePath },
{ requestPath, responsePath, dataLoaderResolver },
) => async (root, vars, context, info) => {
try {
consola.start(
`Resolve: ${info.parentType}.${info.fieldName} [${gqlPathAsArray(
info.path,
)}]`,
);
log.info('resolving', gqlPathAsArray(info.path));
const fieldPath = `${info.parentType}.${info.fieldName}`;
const pathInfo = gqlPathAsArray(info.path);
consola.start(`Resolve: ${fieldPath} [${pathInfo}]`);
log.info('resolving', pathInfo);

assert(context && context.jwt, 'must have context.jwt');
const resolverArgs = { root, vars, context, info };
const request = runRequestVTL(requestPath, resolverArgs);
consola.info(
'Rendered Request:\n',
inspect(request, { depth: null, colors: true }),
);
log.info('resolver request', request);
const requestResult = await dispatchRequestToSource(
source,
configs,
request,
);

let requestResult;
if (request.operation === 'BatchInvoke') {
const loader = dataLoaderResolver(fieldPath);
requestResult = await loader.load(request);
} else {
consola.info(
'Rendered Request:\n',
inspect(request, { depth: null, colors: true }),
);
log.info('resolver request', request);
requestResult = await dispatchRequestToSource(source, configs, request);
}

const response = runResponseVTL(responsePath, resolverArgs, requestResult);
consola.info(
Expand Down Expand Up @@ -308,6 +336,7 @@ const generateResolvers = (cwd, config, configs) => {
const source = dataSourceByName[dataSource];
const pathing = {
requestPath: path.join(mappingTemplates, request),
dataLoaderResolver: generateDataLoaderResolver(source, configs),
responsePath: path.join(mappingTemplates, response),
};
const resolver =
Expand Down
6 changes: 6 additions & 0 deletions yarn.lock
Expand Up @@ -1578,6 +1578,11 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"

dataloader@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==

dateformat@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
Expand Down Expand Up @@ -4337,6 +4342,7 @@ lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4,
lodash@^4.17.11:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==

logdown@^3.2.3:
version "3.2.3"
Expand Down