搬运向 | 浅析serverless架构与实践

2018-05-31 13:27:45 浏览数 (1)

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」

  • 跟以往一样,我认为建环境是最困难的部分
  • 首先要建一个IAMrole

IAM(Identity and Access Management) IAM 的功用就是让你能够管理使用者对于服务和资源所拥有的「权限」 可以针对不同的使用者,制定不同的角色, 举例来说,如果你今天的api 只想让user 从s3 的bucket 里面读一些静态资源 你就不会想要让他拥有access DynamoDB 的权限,懂? IAM是免费的

到aws 选取services,在拉下来一狗票的服务中,

选择IAM

建立一个新的User,名字就输入:serverless-admin

建立好之后,

把拿到的Access Key IdSecret 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

contextevent是我们在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执行GETPOST时,

虽然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底下POSTmethod的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 ,就能直接使用了。

首先当然是先测试原先的GETmethod,理论上来说应该要丢出error,

因为送进来的request(event),它的operationundefined

{"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

并且会依序传入errordata两个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/

0 人点赞