Serverless ,不是没有server,而是不用去担心维护server 这件事,
不管是在部署还是开发,都是以一个个function 为单位,
这带来了程式码上的高度decoupling,但同时也因为过大的弹性,
常常搞的我们无所适从,就像这张图一样:
serverless 更考验着我们对系统设计的思维,
这是一篇非常粗浅的文章,
目的在带领对serverless 有兴趣的人无痛的入门,
不管是在概念上,还是在实务的使用上。
假如你是懒得看文章的人,可以直接到我的github repo上面看 有哪里写错的话可以提个issue,觉得赞赞赞的话也可以给星星以兹鼓励。
试想当你是一个单枪匹马的开发者时,你绝对会希望能真正专心在开发,
而不是一天到晚担心机器有没有死掉,或者配置环境就花了大半时间。
我只是一个前端工程师,对于后端的知识甚是浅薄,
serverless 对我而言是个很合理的选择,
但这不代表我不在乎任何后端的专业性,
更不代表着后端工程师使用serverless 架构就是代表实力不够。
相反的,我认为后端工程师如果能从管理机器中解放,
设计出更好的serverless 架构以及更专注在程式本身的逻辑上,
那从serverless 上能获得的增益一定也是相当惊人的。
看着我们虚拟化的趋势=> VM => Container => Docker 的兴起 尽管做法略有不同,但方向是一致的, 都是想让程式开发者更能专注在程式本身,而不是管理机器上 话说回来,前端后端的分界点一直都是个有争议的问题, 不过就不在这里去讨论了
这篇会需要用到数个aws 的服务,不过为了让事情更单纯,
我只会用到IAM, DynamoDB, API Gateway, CloudWatch 以及Lambda,
都不熟悉这些也没有关系,因为我在写完这一段之前,
也只是大略的把文件扫过去,也不用担心缩写令人看不懂,
因为我最讨厌的就是这种缩来缩去的东西,
所以接下来都会在提到的地方解释我们正在处理的是什么。
以往都是直接用EC2 开一台机器, 要用什么直接当自己家的在上面装就是了。 (当然可以学一些东西自动化这流程: chef,不过这不是这篇的重点)
Introduction
这篇会着重在比较抽象化的概念上,
而不是去针对特定的功能作serverless 的实现,
但不要误会了,后面还是有一个简易restful api 的实作
我认为能掌握以下几个点,才是针对特定功能实现的基础:
- Project 的架构
- 对于设计一套serverless architecture 的抽象概念
- 各个功能与api 间对应的关系
- 资料的处理
- 要能永久被储存
- CRUD 操作
- Schedule:定时或是routine 的去做一些事情(这一篇文章里面不会提到)
- 部署
- 有新功能时我们要能够部署上去
- Log
- 不然你debug 是要通灵吗
至于使用的语言会是nodejs。
优点
- 不需要自己管机器,以及近乎无限能力的scale-out(你的财力够的话)
- 相对便宜。因为我们是有执行function 才收费
- 如果只是自己要使用或是小型专案,基本上都会落在free tier 区间
- 高度的解耦及灵活的配置
- 不管你是想要制作nano service 还是micro service 你都能灵活地去组合
有人说过,当你手上只有锤子时,那你看到的所有东西都会是钉子。
不过对于function
这么general purpose的东西来说,
它的确能拿来解决一切计算相关的问题,端看你组合的方式对不对而已。
限制与风险
讲了这么多好处,现在当然要来讲它的限制。
- 有限的记忆体
- timeout
- 目前最多只能运算300 秒,就会被强制结束掉
- 高度的解耦
- 这看起来是好处,但必须要用跟以前不一样的想法来设计程式,因为我们每次function 运行完之后,就会把所有资源释放出去
- Latency
- 因为我们是需要计算时,才会去要资源来运算,每次都算是一个cold start,所以对latency 完全无法容忍的服务,可能不适合。
- 实际上透过schedule 可以一定程度的解决这问题
- 风险
- 我说一个字大家就懂了:Parse
- 当事情走到这一步的时候,基本上就没啥救了,这就是我们冒着最大的风险
- 但就如同前面所言,我认为serverless 是未来大势所趋,也许不会所有的project 都如此,不过大多数的中小型专案都会转向朝这一架构迈进。
- 因为我们以function 为单位的高解耦,所以更换API,不是一个让人全面崩溃的风险
- 坦白说,如果是考虑到有没有办法scale-out,那我想大部分情形,aws 都是没问题的
- Scale-out
- API 更换
- 服务被停用
Why serverless framework
- 过度的自由,失控的decoupling
- 框架给了我们更好结构化project 的方式
- Config 的设置以及部署function 简化
- 文件和plugins
- 社群或公司支持
- Serverless 的官网上有说到,现在是由一群工程师全职在维护这个framework
- gitter 上问问题也几乎马上就能得到回答
- Apex?
- TJ 的产品,目前还在观望中,但serverless 看起来相对较稳定、成熟
- 不过光是TJ 这个名字,就很值得一试
- 就像我前面说的,因为高度解耦的关系,其实要迁移过来「理论上」不是太难的事
Setup 开发环境的建置
我不认为一个环境的建置,是在把东西装一装之后就结束了,
因为东西装一装之后,通常后续只会有更多的问题,
而且一个project 本来就需要在一开始就做好deploy 的准备了。
不部署的话干嘛要用aws 啊?囧
完整一点的setup 应该要包含了从建置基本设定=> 部署
才算是真的结束,
所以这一小节会从配置到部署都走过一次。
AWS 的介面可能会因为时间的关系,与下方略有不同, 但估计变动不会太大,知道要使用什么功能比较重要, 故我不会把操作介面的图片放上来。
为你的api 建立一个「role」
- 跟以往一样,我认为建环境是最困难的部分
- 首先要建一个
IAM
role
IAM(Identity and Access Management)
IAM
的功用就是让你能够管理使用者对于服务和资源所拥有的「权限」 可以针对不同的使用者,制定不同的角色, 举例来说,如果你今天的api 只想让user 从s3 的bucket 里面读一些静态资源 你就不会想要让他拥有access DynamoDB 的权限,懂? IAM是免费的。
到aws 选取services,在拉下来一狗票的服务中,
选择IAM
。
建立一个新的User,名字就输入:serverless-admin
。
建立好之后,
把拿到的Access Key Id
跟Secret Access Key
给记下来,
待会会用到。
接着选择刚刚建立的那个user:serverless-admin
,
在permissions 的地方加上新的policy,
这里aws 相当贴心的提供我们超大一坨的policies 可供选择,
为了方便,我们直接选择AdministratorAccess
。
当在production 环境时,这样处理permissions 不会是一个好主意XD 坦白说我觉得permissions 会是一个令人头痛的点
Create Project
我们选择了serverless-framework
这一套serverless framework。
npm i -g serverlessserverless project create |
---|
会要你输入名字以及刚刚的access key id 跟secret access key。
接着还要选择你想要你的project 运行服务在的地区。
再来稍后三分钟之后, project 就会建好了。
会生成一大堆东西,下面列出简易版的解释, 看不懂也没关系,之后在实作中就会碰到很多次了:
├── _meta // (.gitignored) 就是个存meta data 的地方(config 之类的├── admin.env // (.gitignored)刚刚create function 时的AWS Profiles├── functions│ └── function1│ ├── event.json│ ├── handler.js│ └── s-function.json├── package.json // 就是npm 的那个├── s-project.json // serverless 的套件管理└── s-resources-cf.json // 就是上述讲到CloudFormation 的描述档 |
---|
Create First function
先让我们focus在function
上,这些config真的都可以先放着没关系。
这不代表他们不重要,只是晚点再回来看他们是在做什么 如果你真的现在就等不及,也可以到serverless 的官方文件看 Project structure
serverless function create functions/posts |
---|
选择nodejs => Create Endpoint
接着就可以看到多了一个functions
资料夹,
并且里面跟着一个posts
以及一些东西了。
一样我们只要知道自己现在建立了一些基础建设,稍后再来回头看这是什么。
Deployment
serverless dash deploy |
---|
function - postsendpoint - posts - GET |
---|
这两个都记得要选才会把东西部署上去aws-lambda。
选择deploy 之后稍待几秒钟,就可以看到回传一个网址给你。
这就是能够执行我们刚刚部属上去的posts
的地方。
如果你没做任何更改,点进去后应该能看到
{"message": "Go Serverless! Your Lambda function executed successfully!"} |
---|
到这里为止,我们才能不心虚的说:环境建完,可以继续了。
Abstraction
Overview
前面一直说到serverless 架构是以function 为单位去部署和开发,
现在来对「lambda function」有个具体的抽象概念。(欸?
先来个大略的概观,你可以跟刚刚create 的project 对照着看:
- 每个function 可以有许多个endpoint(进入点)
- 每个endpoint 可以有许多个method( GET, POST…)
- Handler则是aws lambda执行的进入点(就是
handler.js
)
来看一下handler.js
module .exports.handler = function ( event, context, cb ) {// empty} |
---|
实际上我们运行的function 就是长下面这个样子,
在开始讨论其他配置,和aws 要怎么运行到这里之前,
先搞清楚到底在谈论什么东西:
function ( event, context ) |
---|
可以有第三个参数cabllback, 不过其实只要这两项就可以运作的很好了, 而且callback 实在不是一个好事
Source event
source event,可以是push 或pull model。
假设S3 上面资料新增,lambda function 会接收到event 去做事情,
那这就是一个push model。
假设今天是lamda function 去扫了一遍DynamoDB ,
发现有事情要根据上面的资料去做,
这就是一个pull model。
而source event 也可以很单纯的来自http request。
Context
context
是一个object,
里面包含了当前lambda 运行环境的讯息,
以及一些method。
有三个methods 是一定要知道的:
这里的参数是可选的,我们可以只让function做事, 没有一定要强制回传结果。
context.succeed(Object result)
- 可以在执行成功时回传东西:
context.succeed(someObject)
- 注意这里的
result
必须要能够被JSON.stringifyu转成字串
- 可以在执行成功时回传东西:
context.fail(Error error)
- 在失败时回传东西
context.done(Error error, Object result)
- 这个就有点奇葩了,有了成功和失败为什么还要存在个done 呢?
- 如果error 不为null,这次的lamda function 就会被认定为执行失败
再来是可以看到目前执行剩余时间:
context.getRemainingTimeInMillis()
这里所谓的看到当然是指在function 执行时我们能利用啦!
不过要注意的是如果归零,
AWS lambda 就会强制终止我们的lambda function 了。
handler.js
前面有提到过这里就是aws 运行的进入点,
要在s-function.json
里面设定,
这里看到我们只在handler
那个属性打上: handler.handler
,
这有两件事情值得注意:
- 对应执行的就是
handler.js
这个module底下的handler
// in handler.jsmodule .exports.handler = function ( event, context ) {// This be implemented} |
---|
第二件事就是这个hanlder 属性还隐含着我们目前能作用的scope,
假如我们是:function1/handler.handler
,
就把上层的parent folder 给包含进去,
所以他就吃得到我们在根目录安装的npm 套件。
比如说你安装了react,那你就可以:
require('react')
理解到这样的程度,就已经足够进行下去了,
直接来实作吧!
Implementation: Simple RESTful api
直接看文件时,总会有种雾里看花的感觉,
不过等到实际开始做之后,你会发现其实概念只要mapping 过去,
并没有想像中的困难。
这个是完成后的github repo, 如果你中途发现有什么错误的话,可以在上面查看是否有哪里不一样。
Why
底下会包含基本的CRUD 以及list,
大多数的应用程式都不脱这五种操作,
就算需要更特殊的操作,
也总是要熟悉这些基础后才能继续前进,
包含着如何储存资料以及debug 的概念。
至于资料夹的结构或是workflow 的顺序,
你都可以依照个人的喜好去调整,不一定要照我写的走。
Log
- 没错,我们先来看看要怎么找出错误,从犯错中学习,是新手成长最快的方式
- 来修改一下
functions/posts/hanlder.js
context
和event
是我们在lambda中要好好处理的东西没错,
不过这里先专注在出bug 时要怎么解决:
'use strict'console .log( 'Loading function' )function display ( object ) {return JSON .stringify(object, null , 2 )}module .exports.handler = (event, context) => {console .log( 'Event: ' , display(event))console .log( 'Context: ' , display(context))context.succedd({message: 'ok, it works'})} |
---|
这里的程式码有个明显的错误,待会我们会除错并且学习如何看log
稍做一些更改之后我们就可以再次部署了:
serverless dash deploy |
---|
再到刚刚的网址,会发现出现错误了!
幸好这里加上了许多console.log
,
假如你曾经写过JavaScript 对这样的除错技巧一定不陌生,
但,这里的log 不会在console 印出来,会到哪里呢?
这里就要使用aws 上的另个服务:CloudWatch 了。
到services 点CloudWatch,选取logs,
就会看到这里有个log groups 就是我们刚刚建立的functions。
选进去后会很神奇地发现我们之前call 的纪录都在这里。
在log 中我们可以看到:
...(一些日期和系统资讯) TypeError: context.succedd is not a function at module.exports.handler (/const/task/handler.js:12:11) |
---|
我们出了一个typo 的错误,改正过来以后就成功啦!
context.succeed({message: 'ok, it works'}) |
---|
Create an item
要存资料库前,必须先在DynamoDB
建一张Table。
DynamoDB 是一个no sql 的资料库 为了scale-out ,它在使用上有一些限制, 但在这个简单的示例中,并不会需要考量到这些, 假如有兴趣深入的话,可以看补充资料的地方 解析DynamoDB
- 到aws上选择
DynamoDB
。 - Create table
- table name 输入
posts
- primary key 名称设定为
id
- 下面的default setting 取消勾选,然后将Read capacity units 以及Write capacity units 都调成 1
- 我们就有一个很阳春的table 了
接着是在handler
里面的更动,
首先要安装两个package
npm i -S dynamodb-doc node-uuid |
---|
前面有说过lambda function 其实就是根据source event,
去执行对应的动作:
const DOC = require ( 'dynamodb-doc' )const dynamo = new DOC.DynamoDB()module .exports.handler = (event, context) => {console .log( 'Event: ' , display(event))console .log( 'Context: ' , display(context))const operation = event.operationif (event.tableName) {event.payload.TableName = event.tableName}switch (operation) {case 'create' :const uuid = require ( 'node-uuid' )event.payload.Item.id = uuid.v1()dynamo.putItem(event.payload, () => {context.succeed({"id" : event.payload.Item.id})})breakdefault :context.fail( new Error ( 'Unrecognized operation "' operation '"' ))}} |
---|
其实蛮像我们平常在
redux
中处理对应的action type的reducer
这里建立了一个DynamoDB
的client,简单的来说,我们会把event.payload
这个object,
新增成Table里的一个新item,并且给它一个唯一的id
,
毕竟是Primary key 嘛!
如果你不熟悉Database 的基础理论,Primary key。 Primary key 就是我们拿来识别这个item 在这个表中是唯一的「身分证」, 在这里我们是用
id
来作为我们的Primary key。
那这个event
又是怎么来的呢?
首先我们要了解的是Create这个动作对应到的http method是POST
,
所以当我们在对同一个url执行GET
跟POST
时,
虽然call 的是同个function(或者更精确地说,是同一个Endpoint)。
在posts
资料夹底下,可以看到一个s-function.json
,
这个档案中放着的是关于我们在进入handler.js
时相关的config。
当然也包括了前面说到的event
。
先直接看到endpoints
这个attribute,里面有许多个物件,
预设的是这个:
{"path" : "posts" ,"method" : "GET" ,"type" : "AWS" ,"authorizationType" : "none" ,"authorizerFunction" : false ,"apiKeyRequired" : false ,"requestParameters" : {},"requestTemplates" : {"application/json" : ""},"responses" : {"400" : {"statusCode" : "400"},"default" : {"statusCode" : "200" ,"responseParameters" : {},"responseModels" : {"application/jsoncharset=UTF-8" : "Empty"},"responseTemplates" : {"application/jsoncharset=UTF-8" : ""}}}} |
---|
这里有好多东西,
假如我们要在里面定义我们对每个endpoint 的长相,谁不发疯呢?
眼尖的你应该看到了有template
这个字眼,
而刚刚送进来的event
正是一个http request,
所以我们要做的事情已经呼之欲出了,就是在requestTemplates
加上我们指定的template名称,
就能根据这个template 生出我们想要的event 。
在endpoints
中加上了这个新的object:
{"path" : "posts" ,"method" : "POST" ,"type" : "AWS" ,"authorizationType" : "none" ,"authorizerFunction" : false ,"apiKeyRequired" : false ,"requestParameters" : {},"requestTemplates" : "$${requestCreatePostTemplate}" ,"responses" : {"400" : {"statusCode" : "400"},"default" : {"statusCode" : "200" ,"responseParameters" : {},"responseModels" : {"application/jsoncharset=UTF-8" : "Empty"},"responseTemplates" : {"application/jsoncharset=UTF-8" : ""}}}} |
---|
当进入这个api 时(path 没有改变),使用POST method时,
我们的request会照着requestCreatePostTemplate
这个template走
$${requestCreatePostTemplate} 是特殊的语法, 让serverless 知道这是个template 名字,而不是一般的string。
所以我说,那个tempalte 呢?
这里要在posts
底下新增s-templates.json
,
所有的关于lambda function 的template 都会放在这里。
接下来我们就可以设计我们的request(event)的长相了:
{"requestCreatePostTemplate" : {"application/json" : {"operation" : "create" ,"tableName" : "posts" ,"payload" : {"Item" : {"content" : "$input.json('$')"}}}}} |
---|
这里比较让人疑惑的是$input.json('$')
是什么,
这其实是跟API Gateway 比较有关系的template 语法,
而不是serverless 这个框架底下的。
This function evaluates a JSONPath expression and returns the results as a JSON string. For example, $input.json('$.pets') will return a JSON string representing the pets structure.
简单的说,他会将input 转成一个json-like string,
更棒的地方是他可以像我们平常access 底下的attribut 那样去找底下的东西:
(就是所谓的json path)
像是$.pets
就是将我们吃到的input object底下pets
对应到的东西,
转成string。
Amazon API Gateway: Mapping template reference 想了解更多关于Template 的话可以参考serverless framework 的文件: Template & Variable
接着回到一开始的handler.js
,
就可以把跟event
有关的东西与我们前面template里面所做的config连接起来了:
module .exports.handler = (event, context) => {console .log( 'Event: ' , display(event))console .log( 'Context: ' , display(context))const operation = event.operationif (event.tableName) {event.payload.TableName = event.tableName}switch (operation) {case 'create' :const uuid = require ( 'node-uuid' )event.payload.Item.id = uuid.v1()console .log( 'Payload: ' , display(event.payload))dynamo.putItem(event.payload, () => {context.succeed(event.payload.Item)})breakdefault :context.fail( new Error ( 'Unrecognized operation "' operation '"' ))}} |
---|
这时候可以部署了!
部署完成之后我们需要试试有没有成功,必须要打开API Gateway,
一进去就可以看到对应project 名称的api,
点进去能看到我们现在有哪几个api 可以用(url)。
可以把API Gateway想像成我们平常使用的router
,
Gateway 会把要执行的endpoint 接到对应的url 上。
点击/posts
底下POST
method的integration request ,
在Body Mapping Templates 可以看到对应的template:
{ "operation" : "create" , "tableName" : "posts" , "payload" :{ "Item" :{ "content" :$input.json( '$' )}}} |
---|
那,要怎么测试呢?
我习惯用postman,算是一个测api 相当好用的工具,
找到serverless-demo
这project底下对应的stages
,
选择当前对应的stage(预设应该是dev),
然后选择Export as Swagger Postman Extensions
这个选项,
会下载一个json ,里面把你所有建立的request 都包好好的。
接着就能在postman 中import ,就能直接使用了。
首先当然是先测试原先的GET
method,理论上来说应该要丢出error,
因为送进来的request(event),它的operation
是undefined
:
{"errorMessage" : "Unrecognized operation "undefined"" ,"errorType" : "Error" ,"stackTrace" : ["module.exports.handler (/const/task/handler.js:28:26)"]} |
---|
非常的好。
接着是POST
:
{"errorMessage" : "Process exited before completing request"} |
---|
居然喷错了,所以我们要再度到CloudWatch 去看一下log,
看起来event
的样子是对的,但往下一看就找到了这个错误:
Cannot find module 'node-uuid' |
---|
我们在根目录虽然有package.json
,
但是目前对于底下的handler.js
而言,
它对根目录是完全一无所知的,那该怎么做呢?
在s-function.json
中的handler
改成functions/posts/handler.handler
,
我们能在这里决定function 要对整个project 的权限到哪里,
像这里就会一直延伸到根目录,所以我们在根目录所安装的package,
自然到了posts
底下也吃得到了。
假如仍然没有办法动到dynamodb 的话,
就要到s-resources-cf.json
更改设定
在IamPolicyLambda.Properties.PolicyDocument.Statement
底下加上:
{"Effect" : "Allow" ,"Action" : [ "*" ],"Resource" : "arn:aws:dynamodb:${region}:*:table/*"} |
---|
再去Postman 执行一次,
DynamoDB 的Table 里面就会出现新一笔的资料了(一个新的Item)。
Read an item
- 我们刚刚已经可以在DynamoDB 里面新增资料,自然要有办法拿出来才是。
第一步一样是从handler.js
里面直接去做更改:
为什么每次都从
handler.js
开始是因为这边是最符合逻辑的地方, 其他都比较特定的config 问题
switch (operation) {case 'create' :const uuid = require ( 'node-uuid' )event.payload.Item.id = uuid.v1()console .log( 'Payload: ' , display(event.payload))dynamo.putItem(event.payload, () => {context.succeed(event.payload.Item)})breakcase 'read' :dynamo.getItem(event.payload, context.done)breakdefault :context.fail( new Error ( 'Unrecognized operation "' operation '"' ))} |
---|
接着要到s-function.json
里面去加上对于parameter的设定,
以及加上template:
在GET method 的底下
"requestParameters" : {"integration.request.querystring.id" : "method.request.querystring.id"},"requestTemplates" : "$${requestReadPostTemplate}" |
---|
最后则是template:
"requestReadPostTemplate" : {"application/json" : {"operation" : "read" ,"tableName" : "posts" ,"payload" : {"Key" : {"id" : "$input.params('id')"}}}} |
---|
假如你好奇为什么要用
Key
的话, 可以参考DynamoDB js sdk的github 与mongodb 的query 非常相似
因为我们在handler中用了context.done
,
这里其实是个callback function,等到getItem
结束后,
才会执行context.done
,
并且会依序传入error
、data
两个object,
所以回传的response 会是像这样的一整个item:
{"Item" : {"id" : "3caaeb80-1ebf-11e6-81a9-21cf9c171332" ,"content" : {"message" : "Hello world again!"}}} |
---|
有时候我们并不想让使用者知道这么多,
所以可以使用response template,
这里就能看到前面说的json path 的用处:
// s-function.json"responseTemplates" : "$${responseReadPostTemplate}" |
---|
// s-templates.json"responseReadPostTemplate" : {"application/json" : {"post" : {"id" : "$input.path('$').Item.id" ,"content" : {"message" : "$input.path('$').Item.content.message"}}}} |
---|
Update an item
Update 跟Read 的做法其实已经大同小异,
一样是把查询用的Key放在params
中,
这里我们一样把整包payload 都丢进来。
dynamo.putItem(event.payload, (err, data)=> {context.succeed(event.payload)}) |
---|
看起来只是改成使用putItem
而已,
但其实这边的template 有点小小的改变。
"requestUpdatePostTemplate" : {"application/json" : {"operation" : "update" ,"tableName" : "posts" ,"payload" : {"Item" : {"id" : "$input.params('id')" ,"content" : "$input.json('$')"}}}} |
---|
这样子的好处就是在更新时,只要在params输入指定的id
,
其余要更新的部分就是放在body
里面。
这里的
PUT
并不是partial的更新, 而是整个会替换掉,符合它原本HTTP method 对应的行为
至于s-function.json
里面要怎么改,这有点太trivial ,
就不放上来了。
Delete an item
删除一个item,要做的事情比update 单纯多了,
基本上只要指定好Key,一切就已经结束了:
dynamo.deleteItem(event.payload, context.done) |
---|
"requestDestroyPostTemplate" : {"application/json" : {"operation" : "destroy" ,"tableName" : "posts" ,"payload" : {"Key" : {"id" : "$input.params('id')"}}}} |
---|
List items
除了以上的CRUD 之外,
列出一定数量的items 也是一个相当常见的需求。
dynamo.scan(event.payload, context.done) |
---|
"requestListPostTemplate" : {"application/json" : {"operation" : "list" ,"tableName" : "posts" ,"payload" : {}}} |
---|
最后的Response template会用到foreach
语法,
坦白说这里我压根不想去理解这里的意义是什么,
我宁愿在需要的时候再去查文件就好,
因为我相信这种夭寿的语法迟早会被改掉的:
"responseListPostTemplate": "{"posts" : [#foreach($post in $input.path('$').Items){"id" : "$post.id","content " : { "message":"$post.content.message" }}#if($foreach.hasNext),#end #end ] }" |
---|
Conclusion
现在大概知道,
为什么当初开始学的时候网路上没什么好的教学文了,
因为config 的设置真的是挺复杂的,
不过我想这一篇这样记录下来,应该能让许多人省下走冤枉路的时间。
对于一个程式开发者来说,学习东西的时间就是最大的成本,
我想serverless 不管对于前后端来说,
都是一项很超值的投资。
因为大部分时候,我们都不需要开一整台机器来完成你想做的事情。
在完成这篇之后,可以做什么练习呢?
你可以试着把你原本在EC2 上host 的服务,
转移成serverless 架构。
光想就觉得超难的
或者是把一些routine 的工作,用serverless 的方式去做,
当你越过前面那些xxxconfig 后,
你会发现开发和部署上带来的效率令你吃惊。
作者:Tsung-Chen Ku
原文链接:http://denny.qollie.com/2016/05/22/serverless-simple-crud/