☰
Serverless Framework 部署 Ruby Sinatra API 并接入 DynamoDB:AWS Lambda 上的无服务器 REST 服务完整指南
2026/10/14 2:14:13 网站建设 项目流程
  • 示例工程

【免费下载链接】examples

Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework on AWS Lambda, Microsoft Azure, Google Cloud Functions, and more.

项目地址:https://gitcode.com/gh_mirrors/exa/examples
点击查看免费下载

本文是一份基于开源仓库aws-ruby-sinatra-dynamodb-api模板的实战指南,讲解如何用 Serverless Framework(v4)将经典 Ruby Sinatra Web 应用以传统单函数模式部署到 AWS Lambda,并为其接入 DynamoDB 持久化存储。通过阅读本文,你将掌握:serverless-rack插件包裹 Rack 应用的机制、ANY /与ANY /{proxy+}双路由事件配置、DynamoDB 表的 CloudFormation 声明方式、bin/bundle-compat兼容垫片的作用,以及一套完整可复用的本地开发与远程部署流程。

模板概览:传统框架风格的单函数 API 架构

该模板源自同仓库中的 Python Flask 版本 aws-python-flask-api,将其从 WSGI 生态移植到了 Ruby 的 Rack 生态。整个服务的核心思路是:只配置一个 Lambda 函数api,让它接管所有进入 API Gateway 的 HTTP 请求,路由与请求处理完全交给 Sinatra 框架在函数内部完成。

从 serverless.yml 可以看到,这个函数通过两个http事件接住全部流量:

functions: api: handler: rack_adapter.handler events: - http: path: / method: ANY - http: path: /{proxy+} method: ANY
  • ANY /:匹配根路径(如https://xxxx.execute-api.us-east-1.amazonaws.com/dev/);
  • ANY /{proxy+}:匹配任意深度的子路径,{proxy+}是 API Gateway 的通配参数,让POST /users、GET /users/someUserId等请求全部落入同一个 Lambda;
  • handler: rack_adapter.handler:由serverless-rack插件提供的 Rack 适配器入口,它将 API Gateway 事件翻译成标准的 Rack 请求,再把 Sinatra 的响应翻译回 API Gateway 的响应格式。

请求进入函数后,路由分发的职责就完全交给了 Sinatra。相比为每个路由单独创建 Lambda,这种"一个函数承载整个 Web 应用"的传统框架风格,让代码组织、中间件复用和本地调试都更接近常规 Ruby Web 开发体验,代价则是函数冷启动与并发粒度相对粗一些——这是选用该模板前需要明确的取舍。

除了 API 本身,模板还声明了一个 DynamoDB 表UsersTable用于存储用户数据,Sinatra 应用暴露POST /users(创建用户)与GET /users/:user_id(按 ID 查询用户)两个端点,下文会逐一展开。

核心文件与配置逐段解读

仓库根目录下该模板共 6 个文件,各司其职:

文件职责
serverless.ymlServerless Framework 服务定义:函数、事件、权限、DynamoDB 资源
api.rbSinatra 应用主体:路由与 DynamoDB 读写逻辑
config.ruRack 标准入口,require './api'并run Sinatra::Application
GemfileRuby 依赖声明,区分运行依赖与 development 组
bin/bundle-compat兼容旧版bundle install调用的 bash 垫片
package.jsonNode 侧 devDependencies,仅声明serverless-rack插件

provider 与函数事件:服务级配置

serverless.yml 的 provider 部分定义了几个关键事实:

provider: name: aws runtime: ruby4.0 architecture: arm64 apiGateway: shouldStartNameWithService: true stage: dev iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: - Fn::GetAtt: [UsersTable, Arn] environment: USERS_TABLE: ${self:custom.tableName}

要点说明:

  • runtime: ruby4.0与architecture: arm64:这是本模板选定的 Lambda 运行时与处理器架构,后续"原生扩展 gem 打包"一节会说明这组配置与打包环境必须保持一致;
  • IAM 最小权限:仅授予函数对该服务自己创建的UsersTable表的六种 DynamoDB 操作权限,Resource通过Fn::GetAtt: [UsersTable, Arn]引用同栈内资源;
  • 环境变量:USERS_TABLE从custom.tableName注入,而custom.tableName定义为"users-table-${self:provider.stage}",即默认users-table-dev。应用代码通过ENV['USERS_TABLE']读取,见 api.rb 与 api.rb。

DynamoDB 资源声明

表定义位于 serverless.yml 的resources.Resources.UsersTable:

resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userId AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:custom.tableName}
  • 主键为字符串类型的userId,KeyType: HASH表示分区键;
  • 容量按按量计费场景可改为BillingMode: PAY_PER_REQUEST,模板默认使用ProvisionedThroughput(读写各 1 个容量单位),适合演示与低频场景。

Gemfile:依赖分组的意义

Gemfile 将依赖分为两组:

source 'https://rubygems.org' gem 'sinatra', '~> 4.0' gem 'aws-sdk-dynamodb' group :development do gem 'puma' gem 'rackup' end
  • 运行组:sinatra(约 4.0 版本)与aws-sdk-dynamodb(Ruby 官方 AWS SDK 的 DynamoDB 客户端);
  • development 组:puma与rackup,仅用于本地serverless rack serve时启动 Rack 服务器,不应被打包进 Lambda 部署包。这正是 serverless.yml 中custom.rack.bundlerArgs: '--without development'存在的原因。

bin/bundle-compat:新旧 Bundler 的兼容垫片

这是本模板最值得关注的工程细节。README 指出:serverless-rack在打包时仍会调用旧式命令bundle install --path ... --without ...,而ruby4.0Lambda 运行时自带的现代 Bundler 已移除--path与--without这两个命令行标志,改用bundle config set --local。若直接执行旧式命令会报错,因此模板提供了一个 bash 垫片 bin/bundle-compat,并通过custom.rack.bundlerBin: ./bin/bundle-compat挂接给插件使用。

垫片的核心逻辑(bin/bundle-compat)如下:

if [ "${1:-}" = "install" ]; then shift path="" without="" rest=() while [ $# -gt 0 ]; do case "$1" in --path) path="$2" shift 2 ;; --path=*) path="${1#--path=}" shift ;; --without) without="$2" shift 2 ;; --without=*) without="${1#--without=}" shift ;; *) rest+=("$1") shift ;; esac done if [ -n "$path" ]; then bundle config set --local path "$path" fi if [ -n "$without" ]; then bundle config set --local without "$without" fi if [ "${#rest[@]}" -gt 0 ]; then exec bundle install "${rest[@]}" else exec bundle install fi else exec bundle "$@" fi

工作方式:

  1. 拦截bundle install子命令,解析其中的--path <dir>与--without <group>参数(兼容--path=x与--without=x写法);
  2. 将它们转换为等价的bundle config set --local path .../bundle config set --local without ...;
  3. 其余参数原样透传给真实的bundle install;
  4. 对于非install调用(例如插件在本地开发时执行的bundle exec rackup),直接exec bundle "$@"原样转发。

api.rb:Sinatra 应用与 DynamoDB 客户端

api.rb 是全套业务逻辑所在,其结构可以拆成三部分。

JSON 输出辅助。由于sinatra-contrib尚不支持 Sinatra 4 / Rack 3,模板用一个小 helper 替代了原sinatra/json扩展(api.rb):

helpers do def json(payload) content_type :json payload.to_json end end

按运行环境选择 DynamoDB 客户端配置(api.rb):

client_options = if ENV['IS_OFFLINE'] { region: 'localhost', endpoint: 'http://localhost:8000', credentials: Aws::Credentials.new( 'DEFAULT_ACCESS_KEY', 'DEFAULT_SECRET' ) } else {} end dynamodb_client = Aws::DynamoDB::Client.new(client_options)
  • 当serverless-rack在本地运行时设置IS_OFFLINE环境变量时,客户端指向http://localhost:8000(DynamoDB Local 默认端口)并携带占位凭证;
  • 部署到 Lambda 时IS_OFFLINE不存在,client_options为空哈希,SDK 自动从 Lambda 执行环境获取临时凭证并访问真实 DynamoDB 服务。

查询用户(api.rb):

get '/users/:user_id' do result = dynamodb_client.get_item( key: { 'userId': params[:user_id] }, table_name: ENV['USERS_TABLE'] ) item = result.item if item json user_id: item['userId'], name: item['name'] if item else json error: "Could not find user with userId: #{params[:user_id]}" end end

创建用户(api.rb):

post '/users' do request_payload = JSON.parse(request.body.read) user_id = request_payload['user_id'] name = request_payload['name'] return json error: "Please provide both 'user_id' and 'name'" unless user_id && name dynamodb_client.put_item( item: { 'userId': user_id, 'name': name }, table_name: ENV['USERS_TABLE'] ) json user_id: user_id, name: name end

注意一处容易踩坑的细节:源码读取的请求字段是user_id(下划线命名),而入库时 DynamoDB 主键字段名为userId。因此调用POST /users时请求体应携带"user_id"字段;README 中的 curl 示例使用userId字段,与当前仓库源码存在不一致,实际发送user_id才会命中put_item成功分支。同样,成功响应中的键为user_id与name,例如{"user_id":"someUserId","name":"John"}。

部署到 AWS:前置条件与完整步骤

前置条件

  • Ruby 运行时版本4.0.*(与 Lambdaruby4.0运行时对应);
  • 已安装并登录 Serverless Framework(本模板基于 frameworkVersion 4);
  • 可访问 AWS 账户且本机具备相应凭证配置。

安装依赖并部署

在 aws-ruby-sinatra-dynamodb-api 目录下依次执行:

npm install

该命令会安装 package.json 中声明的serverless-rack插件。随后执行部署:

serverless deploy

部署过程会触发插件打包 Rack 应用与 gem 依赖,输出大致如下(摘自 README):

Packaging Ruby Rack handler... Backing up current bundle... Packaging gem dependencies... Bundle complete! 4 Gemfile dependencies, 15 gems now installed. Gems in the group 'development' were not installed. Restoring backed up bundle... Packaging service... Creating Stack... Checking Stack create progress... ........ Stack create finished... Uploading CloudFormation file to S3... Uploading artifacts... Uploading service aws-ruby-sinatra-dynamodb-api.zip file to S3 (4.86 MB)... Validating template... Updating Stack... Checking Stack update progress... .................................... Stack update finished... Service Information service: aws-ruby-sinatra-dynamodb-api stage: dev region: us-east-1 stack: aws-ruby-sinatra-dynamodb-api-dev resources: 13 api keys: None endpoints: ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} functions: api: aws-ruby-sinatra-dynamodb-api-dev-api layers: None

输出中的Bundle complete! 4 Gemfile dependencies, 15 gems now installed.验证了 development 组(puma、rackup)未被安装;endpoints一节展示的ANY /与ANY /{proxy+}正是前文分析的双路由事件。

生产环境提醒(README):按当前形态部署后,API 对公网完全开放,任何人都可以调用。若用于生产,务必为 API Gateway 配置 authorizer(如 Cognito、自定义 Lambda 授权器等),这部分不在本模板范围内,需参考 Serverless Framework 的 API Gateway 事件文档配置。

调用 API:创建与查询用户

部署成功后即可用 curl 验证两个端点。

创建用户

curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}'

按当前仓库 api.rb 的实际实现,请求体字段应为user_id,可改用:

curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "user_id": "someUserId"}'

成功时返回创建的用户数据(JSON 键为user_id与name)。若请求体缺少user_id或name,则返回错误提示{"error":"Please provide both 'user_id' and 'name'"}。

按 ID 查询用户

curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/someUserId

命中记录时返回类似{"user_id":"someUserId","name":"John"}的 JSON;查询不存在的用户时,源码会返回错误响应(api.rb):

{"error":"Could not find user with userId: someUserId"}

(README 示例中的错误文案为{"error":"Could not find user with provided \"userId\""},与当前源码略有出入,以上方源码行为为准。)

关键坑位:原生扩展 gem 的打包平台匹配

这是本模板 README 特意警示的部署陷阱(README),务必重视:

aws-sdk-dynamodb会间接引入bigdecimal,而bigdecimal自带编译型 C 扩展。serverless-rack是在执行serverless package/serverless deploy的那台机器上运行bundle install的,因此这个 C 扩展是针对打包机平台编译的。如果打包环境不是 Linux/arm64——例如在 Mac 上打包,或在常见的 x86_64 CI runner 上打包——而本模板又以provider.architecture: arm64部署,最终进入部署包的 gem 就无法在 Lambda(arm64 运行时)上加载,函数会在初始化阶段直接报Bundler::GemNotFound。

规避方式:

  1. 始终在 Linux/arm64 环境打包,使打包机与 Lambda 的运行时和架构完全一致;
  2. 推荐做法:基于public.ecr.aws/lambda/ruby:4.0镜像启动容器,在容器内执行部署命令(容器内需安装gcc/make供原生扩展编译),这与本模板的 Lambda 运行时、架构精确匹配。

本地开发:零成本模拟 API 与 DynamoDB

得益于serverless-rack与 DynamoDB Local,整套服务可以在本地完整跑起来。

准备本地 bundle

bundle config set --local path vendor/bundle bundle install

bundle config set --local path vendor/bundle正是bin/bundle-compat在打包阶段替插件做的事,本地手动执行一次即可让依赖落入项目内目录。

启动 Rack 应用

serverless rack serve

接入 DynamoDB Local

需要安装serverless-dynamodb插件(原serverless-dynamodb-local已归档,这是其维护分支):

serverless plugin install -n serverless-dynamodb serverless dynamodb install

第一条命令会把插件写入 package.json 的devDependencies以及serverless.yml的plugins段;第二条命令负责下载安装本地 DynamoDB。

随后在serverless.yml的custom段补充(README):

custom: (...) dynamodb: start: migrate: true stages: - dev
  • start.migrate: true:启动本地 DynamoDB 时自动按 CloudFormation 资源定义创建表结构;
  • stages:限定哪些 stage 使用本地 DynamoDB,模板场景仅需dev。

前文 api.rb 中IS_OFFLINE分支的本地客户端配置,正是为这种模式准备的:serverless-rack在本地运行时注入IS_OFFLINE环境变量,应用便自动将 DynamoDB 客户端指向http://localhost:8000。

依次执行:

serverless dynamodb start

再开一个终端:

serverless rack serve

即可在本地以与线上一致的路径规则调试POST /users与GET /users/:user_id。serverless-rack与serverless-dynamodb插件的更多本地开发能力(如热重载、断点调试配合)可查阅各自官方仓库文档。

小结

本模板用约 60 行的 Sinatra 代码加一份声明式serverless.yml,完整覆盖了"Web 框架上 Lambda + 托管数据库"这一经典无服务器架构形态。值得沉淀到日常工作的几点经验:

  • 单函数全路由:ANY /+ANY /{proxy+}让传统框架平滑迁入 Lambda,路由逻辑零改动;
  • Rack 适配:serverless-rack负责 API Gateway 事件与 Rack 请求的双向翻译,config.ru保持标准 Rack 入口;
  • 平台一致性:涉及原生扩展的 gem 时,打包机平台必须与runtime/architecture匹配,否则部署包在 Lambda 初始化时必然失败;
  • 新旧工具链兼容:bin/bundle-compat展示了如何在插件调用旧式 Bundler 参数时以极小成本平滑桥接;
  • 本地优先:IS_OFFLINE分支 +serverless-dynamodb让本地联调体验与线上保持一致。

如需在真实项目中复用,可直接克隆本模板目录,按本文"部署"一节操作,再根据业务需要扩展路由与表结构即可。

  • 示例工程

【免费下载链接】examples

Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework on AWS Lambda, Microsoft Azure, Google Cloud Functions, and more.

项目地址:https://gitcode.com/gh_mirrors/exa/examples
点击查看免费下载
上一篇:Light-R1社区贡献指南:如何参与这个开源数学推理项目
下一篇:FOSSASIA-artwork项目深度解析:从2010到2026的设计演变史

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询