AWS Lambda NodeJS 导入 returns 空模块,但仅限于 AWS

AWS Lambda NodeJS import returns null module, but only in AWS

已更新

我在尝试调用我的 Lambda 函数时遇到以下错误

{
  "errorType": "TypeError",
  "errorMessage": "e is not a function",
  "trace": [
    "TypeError: e is not a function",
    "    at Runtime.handler (/var/task/serverless_sdk/index.js:9:88355)",
    "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
  ]
}

我已经追踪到对 DB 的引用(参见 schema.js DB 的最后几行应该在 schema.js

的顶部导入
const { DB } = require('./db.js')

确实,当我在本地计算机上尝试相同的代码时,没有问题。

这是否与 Lambda 函数 (LF) 被冻结以便在 AWS 中重复使用的一些微妙方式有关?我应该在哪里初始化 LF 中的数据库连接?

我尝试将 db.js 合并到 schema.js(没有导入),但我仍然遇到同样的错误。 我检查了无服务器加载的 zip 文件,它看起来不错(node_modules 和我的)。

这很难调试。所以在这方面的任何提示都会有所帮助。


server.js

const { ApolloServer } = require('apollo-server')
const { ApolloServer: ApolloServerLambda } = require('apollo-server-lambda')
const { typeDefs, resolvers, connect } = require('./schema.js')

// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.

async function setup(where) {
  if (where == 'local') {
    const server = new ApolloServer({ typeDefs, resolvers })
    let { url } = await server.listen()
    console.log(`Server ready at ${url}`)
  } else {
    const server = new ApolloServerLambda({ 
      typeDefs, 
      resolvers,
      playground: true,
      introspection: true,
      cors: {
        origin: '*',
        credentials: true,
      },
      context: ({ event, context }) => (
        {
          headers: event.headers,
          functionName: context.functionName,
          event,
          context
        })
    })
    exports.graphqlHandler = server.createHandler()
  }
}

let location = (process.env.USERNAME == 'ysg4206') ? 'local' : 'aws'
connect(location, setup)

schema.js

const { gql } = require('apollo-server')
const { GraphQLDateTime } = require('graphql-iso-date')
const { DB } = require('./db.js')

exports.typeDefs = gql`
  scalar DateTime

  type User {
    id: Int
    "English First Name"
    firstName: String
    lastName: String
    addressNumber: Int
    streetName: String
    city: String
    email: String
    createdAt: DateTime
    updatedAt: DateTime
  }

  type Query {
    users: [User]
    findUser(firstName: String): User
    hello(reply: String): String
  }

  type Mutation {
    addUser(user: UserType): User!
  }

  type Subscription {
    newUser: User!
  }
`

exports.resolvers = {
  Query: {
    users: () => DB.findAll(),
    findUser: async (_, { firstName }) => {
      let who = await DB.findFirst(firstName)
      return who
    },
    hello: (_, { reply }, context, info) => {
      console.log(`hello with reply ${reply}`)
      console.log(`context : ${JSON.stringify(context)}`)
      console.log(`info : ${JSON.stringify(info)}`)
      return reply
    }
  },
  Mutation: {
    addUser: async (_, args) => {
      let who = await DB.addUser(args.user)
      return who
    }
  }
}

exports.connect = async (where, setup) => {
  console.log(`DB: ${DB}')    // BUG DB is returning null
  await DB.dbSetup(where)             //BUG these lines cause Lambda to fail
  await DB.populate()                 //BUG these lines cause Lambda to fail
  let users = await DB.findAll()      //BUG these lines cause Lambda to fail
  console.log(users)                  //BUG these lines cause Lambda to fail
  await setup(where)
}

db.js

const { Sequelize } = require('sequelize')
const { userData } = require('./userData')

const localHost = {
    db: 'm3_db',
    host: 'localhost',
    pass: 'xxxx'
}
const awsHost = {
    db: 'mapollodb3_db',
    host: 'apollodb.cxeokcheapqj.us-east-2.rds.amazonaws.com',
    pass: 'xxxx'
}

class DB {

    async dbSetup(where) {
        let host = (where == "local") ? localHost : awsHost
        this.db = new Sequelize(host.db, 'postgres', host.pass, {
            host: host.host,
            dialect: 'postgres',
            logging: false,
            pool: {
                max: 5,
                min: 0,
                idle: 20000,
                handleDisconnects: true
            },
            dialectOptions: {
                requestTimeout: 100000
            },
            define: {
                freezeTableName: true
            }
        })
        this.User = this.db.define('users', {
            firstName: Sequelize.STRING,
            lastName: Sequelize.STRING,
            addressNumber: Sequelize.INTEGER,
            streetName: Sequelize.STRING,
            city: Sequelize.STRING,
            email: Sequelize.STRING,
        })
        try {
            await this.db.authenticate()
            console.log('Connected to DB')
        } catch (err) {
            console.error('Unable to connect to DB', err)
        }
    }

    async select(id) {
        let who = await this.User.findAll({ where: { id: id } })
        return who.get({ plain: true })
    }

    async findFirst(name) {
        let me = await this.User.findAll({ where: { firstName: name } })
        return me[0].get({ plain: true })
    }

    async addUser(user) {
        let me = await this.User.create(user)
        return me.get({ plain: true })
    }

    async  populate() {
        await this.db.sync({ force: true })
        try {
            await this.User.bulkCreate(userData, { validate: true })
            console.log('users created');
        } catch (err) {
            console.error('failed to create users')
            console.error(err)
        } finally {
        }
    }

    async findAll() {
        let users = await this.User.findAll({ raw: true })
        return users
    }

    async close() {
        this.db.close()
    }
}

exports.DB = new DB()

serverless.yml

service: apollo-lambda
provider:
  name: aws
  stage: dev
  region: us-east-2
  runtime: nodejs10.x
#  cfnRole: arn:aws:iam::237632220688:role/lambda-role
functions:
  graphql:
    # this is formatted as <FILENAME>.<HANDLER>
    handler: server.graphqlHandler
    vpc:
      securityGroupIds:
        - sg-a1e6f4c3
      subnetIds:
        - subnet-4a2a7830
        - subnet-1469d358
        - subnet-53b45038
    events:
    - http:
        path: graphql
        method: post
        cors: true
    - http:
        path: graphql
        method: get
        cors: true

zip 的文件夹结构

当 AWS Lambda 导入您的文件时,导出尚不可用。这就是为什么它抱怨你的处理程序不是一个函数(因为它实际上是 undefined 当时正在导入)。

这里有几个建议的解决方案:

1.仅使用 apollo-server-lambda 并使用 serverless-offline 进行本地开发。 这样您的处理程序代码与您在 Lambda 中的代码完全相同。

const { ApolloServer: ApolloServerLambda } = require("apollo-server-lambda");
const { typeDefs, resolvers, connect } = require("./schema.js");

const server = new ApolloServerLambda({
  typeDefs,
  resolvers,
  playground: true,
  introspection: true,
  cors: {
    origin: "*",
    credentials: true
  },
  context: ({ event, context }) => ({
    headers: event.headers,
    functionName: context.functionName,
    event,
    context
  })
});

exports.graphqlHandler = server.createHandler();

2。在您的 Lambda 中使用 apollo-server-lambda,但在另一个文件中使用 apollo-server(例如 local.js)。。然后,您只需使用 node local.js 进行本地开发。不需要 process.env.USERNAME 检查你最后做了什么。

找到问题了。这有点尴尬。但我 post 它以防其他人需要它。

作为 lambda 应用程序初始化的一部分,我试图连接到数据库。希望在冷启动或者热启动的时候,有DB的变量已经在保持连接了。

这是反模式。

使用 apollo,每个请求都必须重新连接到数据库。在 GraphQL 的解析器中,必须重新连接到数据库,然后关闭它,以便 AWS 可以看到没有打开的连接,然后关闭 Lambda 函数。

让我震惊的是,当 运行 作为 ApolloServer 并连接到本地数据库时,它工作正常。