by Paul Matthew Jaworski
保罗·马修·贾沃斯基(Paul Matthew Jaworski)
Unfortunately, since I wrote this article, v1.0 of the Serverless Framework has been released, along with some breaking changes. I believe that you can migrate to the new version simply by adding:
不幸的是,自从我撰写本文以来,已经发布了无服务器框架 v1.0以及一些重大更改。 我相信您只需添加以下内容即可迁移到新版本:
integration: lambda
to each of your resources. For example:
您的每一个资源。 例如:
createPet: handler: handler.create events: - http: path: pets method: POST cors: true integration: lambda
However, I have decided to move on from Serverless for now, mainly due to issues with authentication, authorization, and frustrations with DynamoDB, so I won’t be updating this post. I will explore these issues and my decision to switch back to a “traditional” REST API in a later story.
但是,我决定暂时从Serverless迁移,主要是由于身份验证,授权和DynamoDB的困扰,因此,我将不更新此帖子。 我将探讨这些问题,并在稍后的故事中决定切换回“传统” REST API。
For now, I would recommend referencing the official Serverless docs on API Gateway to get started, and possibly using the rest of this post as a reference, bearing in mind that any information in the Serverless docs takes precedence over anything written here.
就目前而言,我建议您先参考API Gateway上的官方无服务器文档开始使用,并且可能会以本文的其余部分为参考,但要记住,无服务器文档中的任何信息都优先于此处编写的任何内容。
In this article, I’ll share my experience going “serverless” and building a CRUD API “microservice” using AWS Lambda, API Gateway, and DynamoDB. This will function as a guide for you to make your own microservices with these tools.
在本文中,我将分享使用“无服务器”和使用AWS Lambda,API Gateway和DynamoDB构建CRUD API“微服务”的经验。 这将作为指导您使用这些工具制作自己的微服务。
I’m going to assume you have an AWS account and NodeJS installed. If not, handle that now.
我将假设您已经安装了一个AWS账户和NodeJS。 如果没有,请立即处理。
Next you’ll need to install the Serverless npm package, which provides a way to easily create, edit, and deploy microservices as AWS Lambda functions:
接下来,您需要安装Serverless npm软件包,该软件包提供了一种轻松创建,编辑和部署微服务作为AWS Lambda函数的方法:
npm install -g serverless
Then follow Amazon’s instructions on creating an IAM user and configuring Serverless to use those credentials.
然后按照Amazon 关于创建IAM用户并将无服务器配置为使用这些凭据的说明进行操作。
Navigate to the directory where you want to store your new project and run:
导航到要存储新项目的目录并运行:
serverless create --template aws-nodejs --path pets-service
Now’s a good time to set up linting in your project. Since this is not an intro to ESLint, I won’t go into full detail, but I’d recommend installing that now and setting up your .eslintrc like this:
现在是在项目中设置棉绒的好时机。 由于这不是ESLint的简介,所以我不会详细介绍,但是我建议立即安装并按如下所示设置.eslintrc :
{ “plugins”: [“node”], “extends”: [“eslint:recommended”, “plugin:node/recommended”], “env”: { “node”: true, “mocha”: true }, “rules”: { “no-console”: 0, “node/no-unsupported-features”: [2, {“version”: 4}] }}
The important thing to note here is the “no-unsupported-features” rule from the node plugin. AWS Lambda uses Node v4.3, and knowing which ES6 features are available can be a pain in the ass. This makes it easy.
这里要注意的重要事情是节点插件中的“无不支持的功能”规则。 AWS Lambda使用Node v4.3,并且知道哪些ES6功能可用可能很难。 这很容易。
Install the aws-sdk and lodash with npm:
使用npm安装aws-sdk和lodash:
npm i -S aws-sdk lodash
Now head over to handler.js and add those dependencies to the top of your file:
现在转到handler.js并将那些依赖项添加到文件顶部:
const aws = require(‘aws-sdk’);const _ = require(‘lodash/fp’);
Note that we’re using the “functional programming” variant of lodash because its merge function won’t mutate the original object.
请注意,我们使用lodash的“功能编程”变体,因为其合并功能不会使原始对象变异。
Below that, set up your document client for communicating with DynamoDB:
在此之下,设置您的文档客户端以与DynamoDB通信:
const dynamo = new aws.DynamoDB.DocumentClient();
Now let’s make our create() function to make a new Pet in the database:
现在,让我们的create()函数在数据库中创建一个新的Pet:
exports.create = function(event, context) { const payload = { TableName: 'Pets', Item: event.body };
const cb = (err, data) => { if (err) { console.log(err); context.fail(‘Error creating pet’); } else { console.log(data); context.succeed(data); } }
dynamo.put(payload, cb);};
It’s pretty easy to see what’s going on here for the most part:
很容易看到这里发生的大部分情况:
We get an event object passed in with a key body that contains the data we want to store. The DocumentClient requires at minimum an object with the keys TableName and Item to be passed into put().
我们通过一个包含要存储的数据的键主体传递了一个事件对象。 DocumentClient至少需要将具有键TableName和Item的对象传递到put()中。
We also provide a callback that does two important things:
我们还提供了执行以下两项重要操作的回调:
If there is an error, we run context.fail(), which is basically just an onError callback provided by AWS.
如果有错误,我们将运行context.fail() ,它基本上只是AWS提供的onError回调。
If the item creation is successful, we run context.succeed(), passing in the data to be returned as the result of our Lambda function.
如果项目创建成功,我们将运行context.succeed() ,传入要作为Lambda函数的结果返回的数据。
An important caveat with DynamoDB is that we must provide the primary key ourselves on creation. In this case, we have to include petId as a key in our event.body object.
DynamoDB的一个重要警告是,我们必须在创建时自行提供主键。 在这种情况下,我们必须在我们的event.body对象中包含petId作为键。
Why is such a basic feature missing from DynamoDB? Your guess is as good as mine.
为什么DynamoDB缺少这样的基本功能? 你的猜测和我的一样。
I’m fortunate enough in my application to have a unique ID generated for me by Auth0, which I’m using for my auth/user management. You’ll have to solve this problem some other way if you’re not.
我很幸运地在我的应用程序中获得了Auth0为我生成的唯一ID,该ID用于我的auth /用户管理。 否则,您将不得不以其他方式解决此问题。
We’ll follow this same basic pattern for the rest of our CRUD operations:
对于其余的CRUD操作,我们将遵循相同的基本模式:
exports.show = function(event, context) { const payload = { TableName: 'Pets', Key: { petId: event.params.path.petId } }
const cb = (err, data) => { if (err) { console.log(err); context.fail('Error retrieving pet'); } else { console.log(data); context.done(null, data); } }
dynamo.get(payload, cb);};
exports.list = function(event, context) { const payload = { TableName: 'Pets' }
const cb = (err, data) => { if (err) { console.log(err); context.fail('Error getting pets'); } else { console.log(data); context.done(null, data); } }
dynamo.scan(payload, cb);}
exports.update = function(event, context) { const payload = { TableName: 'Pets', Key: { petId: event.params.path.petId } };
dynamo.get(payload, (err, data) => { if (err) { console.log(err); context.fail('No pet with that id exists.'); } else { const item = _.merge(data.Item, event.body); payload.Item = item;
dynamo.put(payload, (putErr, putData) => { if (putErr) { console.log('Error updating pet.'); console.log(putErr); context.fail('Error updating pet.'); } else { console.log('Success!'); console.log(putData); context.done(null, item); } }); } });}
exports.delete = function(event, context) { const payload = { TableName: 'Pets', Key: { petId: event.params.path.petId } };
const cb = (err, data) => { if (err) { console.log(err); context.fail('Error retrieving pet'); } else { console.log(data); context.done(null, data); } }
dynamo.delete(payload, cb);}
There are just a couple things to note here:
这里只需要注意几件事:
We want to be able to do partial updates, meaning you don’t need to send the entire Pet object with its changes, you can just send the changes. To accomplish this, we’re calling a get first in the update() function, then merging our changes into the result of that operation.
我们希望能够进行部分更新,这意味着您无需发送包含其更改的整个Pet对象,只需发送更改即可。 为此,我们先在update()函数中调用get ,然后将更改合并到该操作的结果中。
Our petId is passed in as a parameter to API Gateway and then provided to us in Lambda via event.params.path.petId. You could also use query strings if you prefer.
我们的petId作为参数传递给API网关,然后通过event.params.path.petId在Lambda中提供给我们。 如果愿意,也可以使用查询字符串。
We’re almost done here, so now let’s get our Serverless config files set up. Open up serverless.yml and edit it to look like this:
我们几乎在这里完成了,所以现在让我们设置无服务器配置文件。 打开serverless.yml并对其进行编辑,如下所示:
service: pets-service
provider: name: aws runtime: nodejs4.3
defaults: stage: dev region: us-west-2
functions: createPet: handler: handler.create events: - http: path: pets method: POST showPet: handler: handler.show events: - http: path: pets/{petId} method: GET listPets: handler: handler.list events: - http: path: pets method: GET updatePet: handler: handler.update events: - http: path: pets/{petId} method: PUT deletePet: handler: handler.delete events: - http: path: pets/{petId} method: DELETE
This is pretty easy to understand, I think. We’re just specifying the names of our Lambda functions, then mapping them to our handler.js functions and the HTTP methods and paths we want them to respond to.
我认为这很容易理解。 我们只是指定Lambda函数的名称,然后将它们映射到我们的handler.js函数以及我们希望它们响应的HTTP方法和路径。
I’ve changed defaults to use ‘us-west-2’ as my region, and kept ‘dev’ as my stage. Setting up different stages with Serverless is not something I have fully explored.
我将默认值更改为使用“ us-west-2”作为我的区域,并保留了“ dev”作为我的舞台。 我没有充分探讨过用Serverless设置不同的阶段。
The documentation is very lacking right now, but this configuration will result in an API Gateway named “dev-pets-service” being created, even though that’s not really what we want.
目前非常缺少文档,但是此配置将导致创建名为“ dev-pets-service”的API网关,即使这并不是我们真正想要的。
API Gateways shouldn’t have the environment referenced in their name at all, since they can hold multiple environments or “stages.”
API网关完全不应该在名称中引用环境,因为它们可以容纳多个环境或“阶段”。
Hopefully I’ll find a fix for this and publish it in a future edit ;)
希望我会为此找到一个修复程序,并将其发布在以后的编辑中;)
Now we’re ready to deploy! All it takes is running:
现在我们准备部署! 它只需要运行:
serverless deploy
In a minute or so, your Lambda functions should be deployed, and your API Gateway created.
大约一分钟后,应该部署Lambda函数,并创建API网关。
Create a DynamoDB table named ‘Pets’ (or whatever you’re calling your resource). Then head to API Gateway. Find your ‘dev-pets-service’, and navigate to the POST method.
创建一个名为“ Pets”(或您正在调用的资源)的DynamoDB表。 然后转到API网关。 找到您的“ dev-pets-service”,然后导航至POST方法。
Test your API by clicking on the “TEST” button with the lightning bolt and using the following data:
通过用闪电单击“测试”按钮并使用以下数据来测试您的API:
{ petId: "029340", name: "Fido", type: "dog" }
You should have successfully created a new item in your database!
您应该已经在数据库中成功创建了一个新项目!
Your next steps might be enabling CORS for your resources, using a custom domain name for your API, and setting up your front end app to talk to these endpoints.
您的下一步可能是为您的资源启用CORS,使用API的自定义域名以及设置前端应用程序以与这些端点进行通信。
This is all beyond the scope of the article, and should be pretty simple, but let me know in the comments if you have questions.
这超出了本文的范围,应该非常简单,但是如果您有任何疑问,请在评论中告诉我。
EDIT
编辑
User jcready on Reddit has suggested an improvement to our update method:
jcready的Reddit用户建议对我们的更新方法进行改进:
exports.update = function(event, context) { const payload = _.reduce(event.body, (memo, value, key) => { memo.ExpressionAttributeNames[`#${key}`] = key memo.ExpressionAttributeValues[`:${key}`] = value memo.UpdateExpression.push(`#${key} = :${key}`) return memo }, { TableName: 'Pets', Key: { petId: event.params.path.petId }, UpdateExpression: [], ExpressionAttributeNames: {}, ExpressionAttributeValues: {} }) payload.UpdateExpression = 'SET ' + payload.UpdateExpression.join(', ') dynamo.update(payload, context.done)}
The issue with our current implementation is that a user could overwrite another’s changes if two update requests are sent at once.
当前实现的问题在于,如果一次发送两个更新请求,则用户可能会覆盖另一个人的更改。
DocumentClient provides us with an update method that allows us to specify the fields we want to update, but the syntax is a little odd and requires generating an “UpdateExpression” to achieve these changes.
DocumentClient为我们提供了一个更新方法,该方法允许我们指定要更新的字段,但是语法有点奇怪,需要生成“ UpdateExpression”来实现这些更改。
This code builds that expression based on the keys passed in and solves the problem of overwriting updates in an application where resources are shared between users.
该代码基于传入的键来构建该表达式,并解决了覆盖用户之间共享资源的应用程序中覆盖更新的问题。
翻译自: https://www.freecodecamp.org/news/building-a-nodejs-microservice-on-aws-lambda-6adb6da53cbb/