Fun 发布 2.0 新版本啦

不久前,我们发布了 Fun 的 2.0 版本。欢迎大家使用!

Fun 是什么

Fun 是 have Fun with Serverless 的缩写,是一款 Serverless 应用开发的工具,可以帮助用户定义函数计算、API 网关、日志服务等资源。

为什么需要 Fun

Serverless 的出现,为我们的开发效率带来了巨大的提升。我们可以将 Serverless 看成是一种 "OpsLess" 的开发方式。利用 Serverless 产品开发出的应用,先天就是高度弹性可扩展的、按使用量付费的。

Serverless 已经极大的提升了开发效率以及应用发布后的运维效率。但在使用传统方式开发 Serverless 应用时,因为生态的相对不成熟,还是面临一些问题:

管理困难。FaaS 作为 Serverless 平台体系中的一部分,提供了一种更小粒度的开发模型。实现一个函数,就和我们过往开发中实现一个接口方法类似。因此,在一个 Serverless 应用中,往往包含多个函数,一部分函数可以用于功能的实现,一部分函数可以用于云服务间的粘合,这就导致了要管理非常多的的资源数量,如果再考虑到需要将函数部署在多个 region 中,那么要管理的资源数量还要乘上 region 数量。

交付困难。应用开发完成后,除了代码本身,还要附带详细的配置文档。

移植困难。在不同的 Region 下配置相同的应用,如果是手动操作,操作十分繁琐,也不利于维护。

Fun 就是致力于解决这些问题的一款工具。

全新的 Fun 2.0

2.0 我们依旧致力于为客户提供一款更好用的、提升开发效率的工具。2.0 的新功能参见

2.0 相对于之前的版本,主要两个变化:

1. 定义了全新的 Serverless Application Model

为了解决传统方式开发 Serverless 应用面临的问题。Fun 2.0 版本引入了全新设计的 Serverless Application Model(SAM) 规范。

SAM 作为一种基础设施即代码(Infrastructure as Code),允许用户描述函数计算及其相关云资源,可以使用同一份模板文件,进行跨 region 或者账户部署您的云应用。描述云资源的模板文件,也会成为项目代码的一部分,在不同开发者之间共享。这极大的降低了 Serverless 应用的交付难度、管理难度、移植难度。

SAM 在设计之初,就考虑到对 ROS 的兼容,在未来,我们会将 ROS 已有的能力纳入进来,与 ROS 相互赋能。

2. 对其描述能力进行了增强

除了 1.0 支持的函数、API 网关的配置,我们在 2.0 中:

  • 增强了对函数的描述能力:环境变量、日志服务、角色属性、VPC 属性等。
  • 支持配置新的应用资源,比如 Table Store、日志服务等。
  • 代码上传可以指定文件、目录、压缩包以及 OSS 路径。
  • 更多的 API 网关参数配置
  • 等等

Fun 使用示例

下面我们用 nodejs 编写一个结合函数计算、表格存储以及 API 网关的 demo,该 demo 可以用于统计网站的访问者数量。

首先,创建一个名为 index.js 的文件,内容为:

'use strict';

const TableStore = require('tablestore');
const Long = TableStore.Long;

async function getClient(context) {
    return new TableStore.Client({
        accessKeyId: context.credentials.accessKeyId,
        secretAccessKey: context.credentials.accessKeySecret,
        stsToken: context.credentials.securityToken,
        endpoint: process.env['Endpoint'],
        instancename: process.env['InstanceName']
    });
}

async function getCount(client) {
    var params = {
        tableName: process.env['TableName'],
        primaryKey: [{ 'count_name': 'views' }],
        maxVersions: 1
    };

    const tableName = process.env['TableName'];
    const response = await client.getRow(params); 
    const row = response.row;

    if (row && row.primaryKey) {
        return row.attributes[0].columnValue.toNumber();
    }
    return null;
}

exports.handler = function(event, context, callback) {

    (async () => {
        let views = null;

        const client = await getClient(context);
        let success = false;

        do {
            views = await getCount(client);

            if (views) { 
                try {
                    await client.updateRow({
                        tableName: process.env['TableName'],
                        condition: new TableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, new TableStore.SingleColumnCondition('count', Long.fromNumber(views), TableStore.ComparatorType.EQUAL)),
                        primaryKey: [{ 'count_name': 'views' }],
                        updateOfAttributeColumns: [
                            { 'PUT': [{'count': Long.fromNumber(views + 1)}]}
                        ],
                        returnContent: { returnType: TableStore.ReturnType.Primarykey }
                    });
                    success = true;
                } catch (ex) {
                    if (ex.code !== 403) {
                        callback(ex, null);
                    }
                }
            } else {
                try {
                    views = 1;
                    const res = await client.updateRow({
                        tableName: process.env['TableName'],
                        condition: new TableStore.Condition(TableStore.RowExistenceExpectation.EXPECT_NOT_EXIST, null),
                        primaryKey: [{ 'count_name': 'views' }],
                        updateOfAttributeColumns: [
                            { 'PUT': [{'count': Long.fromNumber(views)}]}
                        ]
                    });
                    success = true;
                } catch (ex) {
                    console.log(ex);
                }
            }
        } while(!success);

        var response = {
            isBase64Encoded: false,
            statusCode: 200,
            body: `You are the ${views}th visitor!`
        };

        callback(null, response);
    })();
};

这里我们需要处理表格存在、表格不存在的两种情况,分别保证他们的操作的原子性。

可以看到,这里我们对于表格存储的配置都是直接从环境变量中读取的。即使后续的开发过程中,修改了表格的名字,只需要修改配置即可,不用修改代码。

接下来,我们直接在 template 中定义函数、表格存储以及 API 网关:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  views-count-demo: # service name
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'Module as a service'
      Policies: 
        - AliyunOTSFullAccess
    views-count: # function name
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        CodeUri: './'
        Description: 'views counts'
        Runtime: nodejs8
        EnvironmentVariables:
          InstanceName: views-count-inst
          TableName: viewsTablesName
          Endpoint: https://views-count-inst.cn-shanghai.ots.aliyuncs.com

  views-count-inst: 
    Type: 'Aliyun::Serverless::TableStore'
    Properties:
      ClusterType: HYBRID
      Description: used for views_count_demo
    viewsTablesName: # table name
      Type: 'Aliyun::Serverless::TableStore::Table'
      Properties:
          PrimaryKeyList:
            - Name: count_name
              Type: STRING

  views_count_apis:
    Type: 'Aliyun::Serverless::Api'
    Properties:
      StageName: RELEASE
      DefinitionBody:
        '/':
          get:
            x-aliyun-apigateway-api-name: views
            x-aliyun-apigateway-request-config:
              requestMode: "PASSTHROUGH"
              requestProtocol: "http"
            x-aliyun-apigateway-fc:
              arn: acs:fc:::services/${views-count-demo.Arn}/functions/${views-count.Arn}/

执行 fun deploy,即可创建、配置并部署好相关服务:

$ fun deploy

Waiting for service views-count-demo to be deployed...
    Waiting for function views-count to be deployed...
    function views-count deploy success
service views-count-demo deploy success

Waiting for table store views-count-inst to be deployed...
    Waiting for table store viewsTablesName to be created...
    create table store viewsTablesName successfully
table store views-count-inst deploy success

Waiting for api gateway views_count_apis to be deployed...
    URL: GET http://81f67bf5fce5478fa5dc18864a1fda18-cn-shanghai.alicloudapi.com/
      stage: RELEASE, deployed, version: 20180702222529788
      stage: PRE, undeployed
      stage: TEST, undeployed
api gateway views_count_apis deploy success

用浏览器打开提示的链接 http://81f67bf5fce5478fa5dc18864a1fda18-cn-shanghai.alicloudapi.com/,即可预览效果。

demo 完整代码

未来展望

Fun 2.0 对我们来说,只是前进了一小步。Fun 工具还有很多地方需要改进,接下来我们会在以下三个方向上作出努力:

  • 功能更完善:提供对更多资源的支持,比如 ots trigger、oss 及其触发器等。提供对 ROS 的支持,将 ROS 的能力整合进来。
  • 开发更连贯:提供提供初始化工程的功能,并且支持线上配置导出为模板文件,即使手边没有模板文件,也能将线上配置一键导出为模板文件,助您持续地开发,高效地迭代。
  • 调试更容易:提供本地运行调试、依赖安装打包等功能的支持,本地运行结果即为线上预期结果。

大家有什么新的需求或者使用过程中有任何问题,请随时联系:@倚贤 @小默 @朴灵

Fun 相关文档

使用入门:中文英文

Serverless Application Model 规范文档:中文英文

github demos

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值