尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Hasura GraphQL Engine Event Triggers 服务端函数样板代码全解析:从 AWS Lambda 到 Zeit Now 的多平台异步业务实战

Hasura GraphQL Engine Event Triggers 服务端函数样板代码全解析:从 AWS Lambda 到 Zeit Now 的多平台异步业务实战 Hasura GraphQL Engine Event Triggers 服务端函数样板代码全解析从 AWS Lambda 到 Zeit Now 的多平台异步业务实战【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine导读本文以 graphql-engine 仓库中 community/boilerplates/event-triggers 目录为线索系统讲解如何借助 Hasura GraphQL Engine 的Event Triggers事件触发器功能在数据库发生 insert / update / delete 时自动调用 Serverless 云函数AWS Lambda、Google Cloud Functions、Microsoft Azure Functions、Zeit Now、Netlify Functions从而在数据库之外异步执行业务逻辑——例如回写关联数据、发送推送通知、ETL 数据转换等。读完本文你将掌握 Event Trigger 的 HTTP Payload 结构与事件分发原理、五大 Serverless 平台的具体部署步骤以及 echo回显与 mutation回写数据库两类样板函数的完整源码解读与实战改造思路。一、Event Triggers 与 Serverless 函数整体架构Event Triggers 是 Hasura GraphQL Engine 的核心能力之一当数据库表发生数据变更INSERT / UPDATE / DELETE时引擎会捕获该变更并以 HTTP POST 请求的形式将事件负载payload异步投递到你预先配置的 Webhook 端点。由于 Webhook 端点通常是一个 Serverless 云函数因此数据库事件 无服务器函数构成了一个天然的异步业务执行通道。仓库在 community/boilerplates/event-triggers/README.md 中提供了跨五种云函数平台的样板函数覆盖了事件触发场景下最常见的几类异步业务逻辑。其整体架构如下Event Triggers 架构左侧的 APIs、Background jobs、GraphQL mutations 等写入操作触发 Postgres 数据变更Hasura events 捕获变更后将事件异步分发给右侧的 Serverless function 与 Microservice 进行处理。从架构图可以看出事件流的完整链路触发源通过 REST API、后台任务Background jobs或 GraphQL mutation 对 Postgres 写入数据捕获Postgres 中的每次数据变更都会被 Hasura GraphQL Engine 的事件模块监听分发Hasura 以异步方式将事件负载投递到配置好的 HTTP 端点——可以是多个 Serverless Function也可以是常规 Microservice执行云端函数解析负载执行业务逻辑回写数据、发通知、同步索引等。这种架构的核心价值在于异步解耦数据库写入操作与后续业务处理彼此独立数据库操作不会被外部服务的延迟或故障阻塞。二、Event Trigger 的 HTTP 调用与负载结构所有样板函数都需要解析 Hasura 投递过来的事件负载因此理解负载结构是阅读和编写这些函数的前提。Event Triggers 通过 HTTPPOST请求调用 Webhook请求头为Content-Type: application/json请求体是一个 JSON 对象。结合 docs/docs/event-triggers/payload.mdx 中的官方定义完整的负载结构如下{ created_at: TIMESTAMP, delivery_info: { current_retry: RETRY_NUMBER, max_retries: MAX_RETRIES }, event: { data: { new: OBJECT_OF_COLUMNS_AND_VALUES, old: OBJECT_OF_COLUMNS_AND_VALUES|NULL }, op: INSERT|UPDATE|DELETE|MANUAL, session_variables: { x-hasura-role: ROLE_NAME }, trace_context: { span_id: SPAN_ID, trace_id: TRACE_ID } }, id: UUID_FOR_INVOCATION, table: { name: TABLE_NAME, schema: SCHEMA_NAME }, trigger: { name: TRIGGER_NAME } }各字段含义如下表字段类型说明created_atString触发器被调用时的时间戳delivery_infoObject消息投递的重试信息delivery_info.current_retryInteger当前重试次数delivery_info.max_retriesInteger最大重试次数eventObject事件本身及其相关数据event.dataObject与事件相关的数据event.data.newObject事件关联的新数据键值对为列名-值event.data.oldObject 或null事件关联的旧数据不适用时为nullevent.opString操作类型取值仅为INSERT/UPDATE/DELETE/MANUALevent.session_variablesObject会话变量键值对x-hasura-*无会话变量时为null仅 Postgres 支持event.session_variables.x-hasura-roleString触发事件用户的角色名event.trace_contextObject分布式追踪上下文event.trace_context.span_id/trace_idString追踪的 span ID / trace IDidString本次调用的 UUIDtableObject表信息table.name/table.schemaString表名 / Schema 名triggerObject触发器信息trigger.nameString触发器名称针对不同操作类型event.data中old/new的取值规则如下PostgresINSERTevent.data.old为nullevent.data.new包含插入的行UPDATEevent.data.old为更新前的值event.data.new为更新后的值DELETEevent.data.old包含被删除的行event.data.new为nullMANUALevent.data.old为nullevent.data.new包含当前行。需要注意的细节在UPDATE场景下仅当新旧数据不同时才投递事件Postgres 使用复合类型比较无法用比较的列则比较内部二进制表示表计算字段computed fields不会出现在事件负载数据中。这个负载结构正是下文所有样板函数解构的同一份数据协议无论函数运行在哪个云平台、使用哪种语言解析的核心字段都是table.name、event.op、event.data.new与event.data.old。三、样板函数目录结构与示例清单仓库将所有样板按云平台 → 语言 → 用例三级组织。顶层目录为各云平台每个平台目录内先按语言划分再按 use-case 划分。以下是 community/boilerplates/event-triggers/README.md 给出的代表性目录树. ├── aws-lambda | |── README.md | |── nodejs | | |── echo | | |── mutation | |── python | | ... | ├── azure-functions | |── README.md | |── nodejs | | |── echo | | |── mutation | |── python | | ...仓库当前支持以下云函数平台AWS Lambdacommunity/boilerplates/event-triggers/aws-lambdaGoogle Cloud Functionscommunity/boilerplates/event-triggers/google-cloud-functionsMicrosoft Azure Functionscommunity/boilerplates/event-triggers/azure-functionsZeit Nowcommunity/boilerplates/event-triggers/zeit-nowNetlify Functionscommunity/boilerplates/event-triggers/netlify-functions目录中已文档化的核心 use-case 有四个echo简单回显触发负载解析事件并原样/格式化返回mutation在数据变更事件发生时由函数发起一次 GraphQL mutation将相关数据回写进数据库如为每次修改记录一个 revision 历史push-notification异步发送 FCM / APNS 推送通知当前仓库内标注为未完成状态etl数据转换类用例——转换触发负载并更新 Algolia 索引当前仓库内标注为未完成状态。以 AWS Lambda 平台为例aws-lambda/README.md 中的完成度清单展示了各用例在各语言下的支持情况Folder nameUse-caseNode.js(6)PythonJavaGoC#Rubyechoecho the trigger payload✅✅❌✅❌✅mutationinsert related data on an insert event using graphql mutation✅✅❌✅❌✅push-notificationsend push notification on database event❌❌❌❌❌❌etltransform the trigger payload and update an algolia index❌❌❌❌❌❌说明push-notification 与 etl 两类用例在当前仓库中尚未实现表格以 ❌ 标注部分语言也存在 WIPwork in progress状态仓库欢迎贡献者补齐。四、echo 用例解析事件负载并回显echo 是理解 Event Trigger 负载协议的最佳入门示例函数读取table.name与event.op然后从event.data.new/event.data.old中取出对应数据并构造响应。4.1 Node.js 8AWS Lambda源码位于 aws-lambda/nodejs8/echo/index.jsexports.handler async (event) { let response {} try { let { table: { name }, event: { op, data } } event.body; response.statusCode 200; if (name notes op INSERT) { response.body New note ${data.new.id} inserted, with data: ${data.new.note}; } else if (name notes op UPDATE) { response.body Note ${data.new.id} updated, with data: ${data.new.note}; } else if (name notes op DELETE) { response.body Note ${data.old.id} deleted, with data: ${data.old.note}; } return response } catch (e) { response.statusCode 400; response.body cannot parse hasura event; return response } };代码要点通过解构语法一次性取出了table.name、event.op、event.data与上一节的负载结构一一对应对INSERT/UPDATE读取data.new对DELETE读取data.old符合各操作类型下old/new的取值规则函数用try/catch包裹解析逻辑解析失败时返回 400 状态码与cannot parse hasura event错误信息这是所有 echo 样板的通用防御模式。4.2 PythonAWS LambdaPython 版本位于 aws-lambda/python/echo/echo.py展示了同样的解析逻辑在 Python 运行时下的写法import json def lambda_handler(event, context): try: body json.loads(event[body]) except: return { statusCode: 400, body: json.dumps({message: Unable to parse hasura event}) } message Not able to process request data body[event][data] if body[table][name] notes and body[event][op] INSERT: message New note {} inserted, with data: {}.format(data[new][id], data[new][note]) elif body[table][name] notes and body[event][op] UPDATE: message Note {} updated, with data: {}.format(data[new][id], data[new][note]) elif body[table] notes and body[event][op] DELETE: message Note {} deleted, with data: {}.format(data[old][id], data[old][note]) return { statusCode: 200, body: json.dumps({message: message}) }注意一个细节Python 版本先用json.loads(event[body])手动反序列化请求体再逐层取body[table]、body[event][op]、body[event][data]。这与 Node.js 版本直接解构event.body等价——因为 API Gateway 传入 Lambda 的原始 HTTP body 是字符串两种语言都需先还原为对象。4.3 GoZeit NowGo 版本位于 zeit-now/go/echo/index.go它用结构体完整定义了 Hasura 事件负载的 JSON 协议是理解负载字段类型的最佳参考type HasuraEvent struct { ID string json:id Event json:event Table json:table Trigger json:trigger } type Event struct { Op string json:op Data json:data } type Data struct { Old map[string]interface{} json:old New map[string]interface{} json:new } type Table struct { Name string json:name Schema string json:schema } type Trigger struct { ID string json:id Name string json:name }其处理函数将请求体解码为HasuraEvent后构造如下的回显响应同时把旧数据与新数据一并返回response : TriggerResponse{ Message: fmt.Sprintf( got %s for %s operation on %s table in %s schema from %s trigger, event.ID, event.Event.Op, event.Table.Name, event.Table.Schema, event.Trigger.Name, ), OldData: event.Data.Old, NewData: event.Data.New, }可以看到 Go 版多利用了负载中的id、table.schema、trigger.name等字段输出信息比 Node/Python 版更丰富——这印证了同一份负载协议在不同语言下的通用解析模式。4.4 Google Cloud Functions 与 Azure Functions 的简洁变体google-cloud-functions/nodejs8/echo/index.js 展示了最精简的 echo 形态exports.function (req, res) { const { event: {op, data}, table: {name, schema} } req.body; const response {message: received event, data: {op, data, name, schema}}; console.log(response); res.json(response); };而 azure-functions/nodejs/echo/HTTPTrigger/index.js 则直接回传原始请求体module.exports function (context, req) { context.log(JavaScript HTTP trigger function processed a request.); if (req.body req.body) { context.res { // status: 200, /* Defaults to 200 */ body: req.body }; } else { context.res { status: 400, body: Please pass a request body }; } context.done(); };这两版展示了不同平台函数签名的差异GCF 用(req, res)Azure 用(context, req)但解析的数据协议完全一致。五、mutation 用例在事件中回写数据库mutation 用例演示的是更贴近生产的模式函数收到事件后主动调用 Hasura 的 GraphQL 端点执行一次 mutation把关联数据写回数据库。以为笔记的每次变更记录一个 revision为例——当notes表发生 UPDATE/DELETE 时函数把旧值插入到note_revision历史表中实现审计留痕。5.1 Node.js 8AWS Lambda源码位于 aws-lambda/nodejs8/mutation/index.js// Lambda which gets triggered on insert, and in turns performs a mutation const fetch require(node-fetch); const accessKey process.env.ACCESS_KEY; const hgeEndpoint process.env.HGE_ENDPOINT; const query mutation updateNoteRevision ($noteId: Int!, $data: String!) { insert_note_revision (objects: [ { note_id: $noteId, note: $data } ]) { affected_rows } } ; exports.handler async (event) { try { const qv { noteId: event.body.event.data.old.id, data: event.body.event.data.old.note }; const result await fetch(hgeEndpoint /v1/graphql, { method: POST, body: JSON.stringify({ query: query, variables: qv }), headers: { Content-Type: application/json, x-hasura-admin-secret: accessKey }, }); const { errors, data } await result.json(); if (errors) { throw new Error(errors); } else { return { statusCode: 200, body: success }; } } catch (e) { return { statusCode: 400, body: cannot parse hasura event }; } };实现要点环境变量注入HGE_ENDPOINTHasura GraphQL Engine 地址与ACCESS_KEYadmin secret都从进程环境变量读取避免硬编码敏感信息GraphQL mutation定义带$noteId、$data两个变量的insert_note_revisionmutation写入note_id与note两列变量来源noteId与data取自event.body.event.data.old——即被修改/删除之前的旧值用于留存历史快照调用方式向HGE_ENDPOINT /v1/graphql发起 POST请求头携带Content-Type: application/json与x-hasura-admin-secret请求体为{ query, variables }错误处理GraphQL 响应中的errors字段非空时抛出异常并返回 400成功则返回 200 success。这里演示了一个关键安全实践函数通过 admin secret 以管理员身份调用 GraphQL API这在 webhook 回调场景中是常见做法webhook 由引擎可信地调用函数再以管理员权限回写。5.2 PythonAWS LambdaPython 版本位于 aws-lambda/python/mutation/mutation.py逻辑与 Node.js 版完全对应import os import json from botocore.vendored import requests ADMIN_SECRET os.environ[ADMIN_SECRET] HGE_ENDPOINT os.environ[HGE_ENDPOINT] HGE_URL HGE_ENDPOINT /v1/graphql HEADERS { Content-Type: application/json, X-Hasura-Admin-Secret: ADMIN_SECRET, } query mutation updateNoteRevision ($noteId: Int!, $data: String!) { insert_note_revision (objects: [ { note_id: $noteId, note: $data } ]) { affected_rows } } def lambda_handler(event, context): try: body json.loads(event[body]) except: return { statusCode: 400, body: json.dumps({message: Unable to parse request body}) } data body[event][data] qv {noteId: data[old][id], data: data[old][note]} jsonBody {query: query, variables: qv} resp requests.post(HGE_URL, datajson.dumps(jsonBody), headersHEADERS) my_json resp.json() print(my_json) return { statusCode: 200, body: json.dumps({message: success}) }两个版本的差异点值得注意Python 用requests.post发送请求Node.js 用fetchPython 版把HGE_URL预拼好并复用了包含 admin secret 的HEADERS字典Node.js 版检查 GraphQL 响应中的errors并显式抛错Python 版仅打印响应 JSON——这说明生产环境建议参照 Node.js 版加入错误检查避免静默失败两者都从data[old]取id与note作为 mutation 变量印证了用旧值留存历史的设计意图。5.3 mutation 用例的可扩展方向mutation 样板是函数内发起 GraphQL 请求的最小可运行示例基于同一模式可以扩展出大量真实业务更新关联表统计字段如帖子被点赞后更新计数将外部服务结果写回数据库如 OCR、审核结果落库在事件中组合session_variables以触发者身份而非 admin 身份执行受限 mutation需在请求头注入x-hasura-role等会话变量。六、五大 Serverless 平台部署指南6.1 通用前置条件所有样板代码都假设你已经有一个正在运行的 Hasura GraphQL EngineHGE实例。若尚未搭建仓库文档建议参照 Hasura 官方文档完成 Postgres HGE 的部署。各平台还要求云厂商账号开启计费、安装对应 CLI 工具如aws、gcloud、azure-cli、netlify-cli、now。6.2 AWS Lambdaaws-lambda/README.md前置条件已启用计费的 AWS 账号、可用的 HGE 实例。以 Node.js echo 样板aws-lambda/nodejs8/echo/README.md为例完整流程分为三步第一步建表notes: id: int note: text第二步创建 Lambda 函数作为 Webhook在 AWS 中创建函数Create a function运行时选择 Node.js 8.10选择 start from scratch从零开始添加 API Gateway 作为触发器Add API gateway as a trigger在 API Gateway 中新建一个 API将 index.js 的代码粘贴到函数中函数 handler 指向index.handler。第三步在 Hasura 中添加触发器在 Events 页签点击添加触发器Add a trigger为触发器选择 insert、update、delete 全部操作将 AWS Lambda 的 API 端点粘贴为 Webhook URL。完成后对notes表的任何增删改都会触发 Lambda并收到类似New note 1 inserted, with data: hello的回显响应。提示若使用 mutation 样板还需为 Lambda 配置HGE_ENDPOINT与ACCESS_KEY两个环境变量参考 5.1 节的代码读取逻辑并保证note_revision表已按 mutation 中的字段note_id、note建好。6.3 Google Cloud Functionsgoogle-cloud-functions/README.md前置条件已启用计费的 Google Cloud 账号、gcloudCLI、HGE 实例。需先安装gcloud beta组件gcloud components update gcloud components install beta支持矩阵见 google-cloud-functions/README.mdFolder nameUse-caseNode.js(8)Node.js(6)Pythonechoecho the trigger payload✅✅✅mutationinsert related data on an insert event using graphql mutation✅✅❌push-notificationsend push notification on database event❌❌❌etltransform the trigger payload and update an algolia index❌❌❌即 echo 用例支持 Node.js 8 / 6 / Python 三种运行时mutation 用例仅支持 Node.js。各用例代码位于 google-cloud-functions 目录下的nodejs8/echo、nodejs8/mutation、nodejs6/echo、nodejs6/mutation、python/echo等子目录按平台官方 CLI 文档部署即可。6.4 Microsoft Azure Functionsazure-functions/README.md前置条件运行中的 HGE、已启用计费的 Azure 账号、安装 azure-cli 与 azure-functions-core-tools。支持矩阵Folder nameUse-caseJavascriptJavaC#F#echoecho the trigger payload✅❌❌❌mutationinsert related data on an insert event using graphql mutation✅❌❌❌push-notificationsend push notification on database event❌❌❌❌etltransform the trigger payload and update an algolia index❌❌❌❌当前仅 JavaScript 运行时可用。每个用例位于 azure-functions/nodejs 下包含HTTPTrigger/function.json函数绑定配置、HTTPTrigger/index.js处理函数、host.json与sample.dat示例请求数据。echo 样板直接以context.res.body req.body回传负载。6.5 Zeit Nowzeit-now/README.mdZeit NowVercel 前身是部署最简单的一类平台。部署步骤创建 Zeit 账号安装 now CLInpm install -g now登录now login。Zeit Now 目录下提供了四套可直接 checkout 的示例各含now.json配置与index.js/index.go源码NodeJS EchoNodeJS MutationGo EchoGo Mutation其中 Go 版 echo 的完整结构体定义见 zeit-now/go/echo/index.go已在 4.3 节详解可直接作为其他平台 Go 实现的类型参考。6.6 Netlify Functionsnetlify-functions/README.mdNetlify 平台没有单独提供样板源码而是通过netlify-cli的模板功能创建准备阶段可点击 Heroku 部署按钮一键在 Heroku 上部署带免费 Postgres 插件的 GraphQL Engine其他部署方式参见官方部署文档。配置netlify-clinpm install netlify-cli -g # 安装 CLI ntl login # 登录 Netlify ntl init # 初始化实例已有项目可用 ntl link 关联创建并部署触发器函数ntl functions:create # 选择 hasura 模板创建函数 ntl deploy --prod # 部署到生产环境函数代码位于 netlify-functions/nodejs/echo/functions/index.js配合 netlify.toml 使用如需内置认证可结合 netlify-identity-widget 及其替代方案。七、贡献与扩展如何新增平台与用例仓库为社区贡献预留了明确的路径见 community/boilerplates/event-triggers/README.md标记了good-first-issue或help-wanted的 issue 是新手友好的起点部分示例对应仓库中带great-first-issue标签的 issue各云平台 README 中有相应 checklist 可对照查看提交新样板欢迎为已有用例补充新的语言实现或为全新平台如 Apache OpenWhisk 等提交 PR请求新样板可以通过创建 issue 的方式请求新的 use-case 样板。在新增平台时建议沿袭现有的目录规范——平台名/语言/用例三级结构并参考 4.3 节的 Go 结构体定义确保负载解析字段完整、与 第二节 的官方负载协议保持一致。八、从样板到生产实践要点小结综合以上各平台的样板代码与官方负载文档落地 Event Triggers Serverless 方案时可遵循以下要点统一解析协议所有函数的入口都是对table.name、event.op、event.data.new/old的解析可先按 第二节 的字段表设计好类型定义参考 Go 样板的结构体再翻译到目标语言区分old/newINSERT 只读newDELETE 只读oldUPDATE 两者都读——mutation用例正是利用这一规则读取旧值做历史快照回写数据库用环境变量注入端点与密钥mutation 样板的HGE_ENDPOINT/ACCESS_KEY或ADMIN_SECRET均来自环境变量生产环境切勿硬编码务必检查 GraphQL 响应中的errorsNode.js 版 mutation 样板示范了有错即抛的健壮写法直接复用 Python 版的只打印不检查存在静默失败风险函数要保持幂等与快速返回Event Triggers 的投递带有delivery_info重试语义current_retry/max_retries云函数应能容忍重复调用并尽快返回 2xx 响应从 echo 起步、按需扩展先用 echo 样板验证端到端链路建表 → 建函数 → 配触发器 → 观察回显再按 push-notification推送通知、etlAlgolia 索引同步等方向扩展异步业务。通过以上步骤你可以基于仓库中的样板代码在任意主流 Serverless 平台上快速搭建出数据库事件驱动的异步业务处理闭环——这也是 Hasura Event Triggers 在真实项目中最常见、最高价值的落地形态。【免费下载链接】graphql-engineBlazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.项目地址: https://gitcode.com/gh_mirrors/gr/graphql-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表