Transcript
00:00:00嘿,非常感谢今天加入我们。
00:00:02我是来自 Vercel Workflow 团队的 Praneet。
00:00:05嗨,我是 Nate,也来自 Workflow 团队。
00:00:08Nate,你和我从最开始就在 Workflow 团队了,
00:00:12在过去六个月里我们发布的所有功能中,
00:00:15我觉得 hook 和 webhook 是我最喜欢的功能之一,
00:00:18而这正是你今天要聊的话题。
00:00:21hook 和 webhook 也是我最喜欢的功能。
00:00:23它们非常强大,我会通过几个演示来解释原因。
00:00:28第一个演示是我们可能都很熟悉的东西:魔术链接 (Magic Links)。
00:00:33魔术链接是一个登录表单。你输入邮箱,收件箱会收到一封邮件,
00:00:40当你点击那个链接时,你就登录了该服务。
00:00:44是的,如果我没记错的话,在 Vercel 甚至还没叫 Vercel、
00:00:48你需要引入数据库来跟踪状态,这很快就会变得一团糟。
00:00:52是的,我已经开始考虑如何构建它以及使用什么数据库了,
00:00:56因为这感觉像是我以前解决过的一个典型问题。
00:01:01所以,我很想看看它(工作流版)是什么样子的。
00:01:08好的,为了展示我所说的那些痛点,
00:01:12我先实现了一个“传统”的、不含工作流版本的魔术链接登录。
00:01:19这其中涉及三个端点。
00:01:24第一个是在提交登录表单时,
00:01:28它需要生成一个会话并将其存储在 Redis 之类的数据库中。
00:01:30你需要实现 TTL(生存时间),不能让数据永远留存,必须设置过期。
00:01:38然后发送邮件,这一步可能会失败,导致登录无法进行,体验非常糟糕。
00:01:43这涉及到三个端点。
00:01:47第一个是在提交登录表单时,
00:01:50它需要生成一个会话并将该会话存储在数据库中,例如 Redis。
00:01:57你需要实现 TTL(生存时间),不能让数据永远挂在那里,你需要让它过期。
00:02:06然后发送邮件,这一步可能会失败,接着登录就失效了,这体验非常让人沮丧。
00:02:14对,然后你还得安排一个定时任务或让实习生去清理数据库。
00:02:19我当时可能就是那个实习生。
00:02:22但接着还有第二个端点,这是用户点击邮件中的链接时触发的。
00:02:28这基本上需要查询数据库,恢复在第一个端点中构建的状态。
00:02:36我们已经开始看到代码变得像“意大利面”一样混乱了。
00:02:38当我试着想象这会是什么样子时,这些代码看起来非常熟悉,我以前也是这么构建的。
00:02:48我们可以看到,尽管这是一个非常简单的概念,但情况很快就变得复杂了。
00:02:54那么,让我们看看在 Workflow 中如何实现这个功能。
00:02:59使用 Workflow SDK 实现的魔术链接大概是这个样子。
00:03:05我们可以看到这里有函数,它带有 useWorkflow 指令,意味着这是我们的 Workflow 函数。
00:03:11我们要做的第一件事是调用 createWebhook 函数,它来自 Workflow 包。
00:03:18在这种情况下,我们还使用了 respondWithManual 选项,这意味着我们的 Workflow 函数将负责写入或发送对触发此 Webhook 的 HTTP 请求的响应。
00:03:36这是为了让他们登录后可以进行重定向之类的操作吗?
00:03:40是的,因为我们的 Workflow 函数中可能有一些信息,我们需要这些信息来确定发送哪种响应。
00:03:51与第一个端点类似,我们发送登录邮件。这是一个 useStep 函数。
00:03:57所以如果这类操作失败了,Workflow SDK 会自动重试。
00:04:03其持久性方面已经比传统方法提供了一些优势。
00:04:10所以 sendLoginEmails 是一个步骤,如果邮件发送失败,你会使用已经为 Webhook 创建的相同 URL 重试发送邮件。
00:04:21如果我们看这里,这是一个非常有趣的模式。
00:04:26我们使用了 promise.race 并设置了 5 分钟的睡眠 (sleep)。
00:04:30这之所以可行,是因为这个 Webhook 对象实现了一个 Promise。
00:04:35所以要等待此 Webhook 的请求,你只需 await Webhook 即可。
00:04:40或者像在这个例子中,你使用了 race。这很酷,因为我本以为这个 Webhook 功能会有超时选项作为另一个参数。
00:04:50但我更喜欢现在这样,它更简洁,要做超时处理,你只需将其建模为 Webhook 和 sleep 之间的 race。
00:04:58我觉得这能做更多事情。我也许可以让两个不同的 Webhook 互相竞争 (race)。
00:05:02当一个函数只有几个参数时,你能做的并不多。
00:05:06但由于它只是一个 Promise,我可以对 sleep 进行 promise.race,或者再加入一个步骤。
00:05:12我爱死这个模式了。我脑子里已经在飞速构思能用它构建的所有东西了。
00:05:16没错,这就是 Workflow SDK 提供的原语 (primitives) 的美妙之处。
00:05:21一切都以 Promise 的形式暴露出来。
00:05:23所以标准的 JavaScript 模式,如 await promise.race,可以直接运行。
00:05:28然后这里还有一点需要指出,这里没有 Redis,也没有数据库。
00:05:33在传统的例子中,我们使用 Redis TTL 来实现这个超时。
00:05:41而在这种情况下,我们使用的是 Workflow 的 sleep 原语。
00:05:44而且也不需要实习生在事后去清理混乱的数据库。
00:05:50那是最棒的部分。
00:05:51所以你可以看到 Workflow 通过重定向到登录成功页面来响应公开请求。
00:05:59然后检索有关用户的信息,并返回给发起登录页面的客户端。
00:06:07这就是我们的整个 Workflow。我们的魔术链接实现只需 50 行代码。
00:06:12看到这个真是太不可思议了。我们能看看它的实际运行情况吗?
00:06:17这是我们的魔术链接演示。我只需输入我的邮箱。
00:06:24我们的 Workflow 就在那里启动并发送了邮件。现在有一个 webhook 正在等待。
00:06:31事实上,我们的 Workflow 现在处于挂起状态。所以在等待人类点击邮件链接时,算力消耗为零。
00:06:41哦,这在 Vercel 上看起来是什么样的?我能看到一个挂起的运行记录吗?
00:06:47好的,我们确实收到了邮件。在我点击之前,让我们先看看可观测性 (observability)。
00:06:52我知道我有点跳跃,但我很喜欢看这个。
00:06:57好的,我们可以看到我们的运行记录在这里,40 秒前开始的。
00:07:02如果我们看一下,可以看到 Workflow 提供的标准可观测性功能。
00:07:08我们可以看到 Workflow 运行的输入。你可以看到我输入到登录表单的邮箱地址。
00:07:13有趣的是,我们可以看到这里的 hook 正在等待。
00:07:17你说现在没有算力在运行。虽然有可观测性界面,但实际上并没有任何东西在坐等我点击那个 hook。
00:07:25没错。hook 在等待,sleep 在休眠,这两者实际上都不涉及任何算力消耗。
00:07:39但我们可以看到我们的 hook,如果你还记得的话,这两个都在 promise.race 中竞争。
00:07:46所以其中一个必须先完成,Workflow 才能继续。
00:07:50如果我点击那个链接,好,我们可以看到我被重定向到了登录成功页面,这是我们 Workflow 逻辑中的一个步骤。
00:07:59如果我跳回登录表单...
00:08:01对,然后回到仪表板,那里应该也完成了。
00:08:05没错。我们的 Workflow 确实完成了。
00:08:08你可以看到一旦 hook 胜出,计时器也就停止了。
00:08:11是的,我们仅用大约 50 行代码就实现了魔术链接。
00:08:17这真的很简洁。看到这个太酷了,如果你只需在白板上画出魔术链接的工作原理并解释给别人听,
00:08:27你在代码中写的步骤与你画出的流程完全一致,现在的最终代码也是如此。
00:08:34中间没有额外的数据库,没有多个 API 路由。你给我看的代码读起来非常清晰。
00:08:41我认为这是 Workflow SDK 最强大的地方,它允许你构建应用程序逻辑,使其逻辑地流动,而不是为了适应基础设施而变得支离破碎。
00:08:59对。此外,我也很喜欢这里命名为 webhook 的想法,因为它给了我一种思考 webhook 的全新方式。
00:09:07它只是一个我可以创建并在此挂起的临时 URL。
00:09:10实际上,这可以很好地过渡到下一个话题,因为我在想,我们在 Vercel 构建了很多这类代理 (agents)。
00:09:16我们构建了一堆 Slack 代理和 GitHub 代理,我们经常订阅来自 GitHub 或 Slack 的 webhook,对吧?
00:09:23每次 PR 中有新评论时,我们都想启动一个 Vercel 代理,我们希望基于 GitHub 发送的这些 webhook 事件来执行操作,对吧?
00:09:31我们可以使用 Workflow 的 webhook 来订阅来自 GitHub 等平台的事件吗?
00:09:36好的,对于像 Slack 或 GitHub 发送的 webhook,通常你必须手动进入后台配置一个静态的回调 URL。
00:09:49对。你不能像处理这里的邮件一样,创建一个临时的、一次性的 URL 给它。
00:09:54没错。所以 create webhook 功能在某种意义上更高级一些,它提供了一个随机生成的唯一 webhook URL,
00:10:04它对应回一个特定的 Workflow 运行记录。
00:10:07而在 GitHub 或 Slack 的 webhook 路由情况下,那个路由可能对应任何数量的 Workflow 运行。
00:10:14对。你必须预先配置好某个端点,虽然你有多个拉取请求 (PR),但它们都指向同一个端点。
00:10:20所以为了使用 Workflow SDK 实现这一点,我们要深入一层,使用更底层的 hook 原语。
00:10:28我正好有一个演示可以展示给你。
00:10:31让我们来看看。
00:10:32好的。这是“故事时间机器人” (storytime bot)。
00:10:35这是我一年多前用 Workflow SDK 编写的第一个应用程序。
00:10:40它的工作原理是你只需输入 storytime/ 命令,我们就会看到这个线程被创建。
00:10:47每个线程由一个单独的 Workflow 运行实例代表。
00:10:52当我们展开线程时,我们看到一个大语言模型 (LLM) 为我们开始了这个故事,你、我或该频道中的任何人都可以继续写这个故事。
00:11:05LLM 会帮助我们引导故事走向最终结局。
00:11:09好的。Luna 有一颗神奇的种子,接下来会发生什么?她种下了种子。
00:11:13好的。我们看到这里有一些活动正在进行。
00:11:17接下来发生了什么?神奇的事情。
00:11:20我们的故事讲完了,我们得到了最终的故事,还会生成一张小图片。
00:11:26但我们会跳回那部分。
00:11:28我其实已经很好奇了,因为我注意到,我本以为会有一个 webhook 请求,但实际上至少有两个请求,因为你有两条消息。
00:11:35所以我很想看看代码里是怎么实现的。
00:11:38好的。这是我们故事时间机器人的 Workflow 函数。
00:11:44我们可以看到它接收频道 ID (channel ID),也就是故事时间机器人频道。
00:11:50它可以传递一些配置选项。
00:11:53但有趣的是,我们可以看到这个 messages 数组,如果你熟悉 AI SDK,这就是存储 AI 对话的数据格式。
00:12:04在像我们这里创建的典型的 Slack 机器人应用中,你通常会将此类内容存储在数据库中,并在每次迭代或每个 webhook 事件(即每条输入到线程的消息)中,恢复状态,从数据库中查找对话。
00:12:23但这里并不是这样。这只是你函数中的一个数组。
00:12:27是的。事实上这没问题,我刚才笑了,因为我看到开头你有这样一条注释,说:“看啊,没有队列,也没有知识库。”
00:12:34这里没有导入任何数据库。你只导入了 Workflow。
00:12:40回到刚才关于最后一条消息的话题,这一点几乎被忽略了,但你在这里确实有一个名为 final story 的变量,据推测随着时间的推移,我们会向这个数组推送消息,
00:12:55最终的故事将在这里显示为一个字符串,但它不需要存入任何数据库。所以,这就像是 let 变量成了你的数据库。
00:13:04是的,“let 就是你的数据库”是一个很好的术语,我们要创造这个词。
00:13:10我也许是从你那里偷来的词,但是。
00:13:14这里有趣的地方,也是我们要讨论的,是这个 hook 功能。我们可以看到我们在这里创建了这个 hook。与魔术链接中看到的 web hook 示例不同的是,在这种情况下,我们提供了一个令牌 (token),
00:13:28它是一个字符串,包含了该 Workflow 运行所特有的标识信息。
00:13:35TS 是线程 ID。所以这个字符串是唯一标识此 Workflow 运行的令牌。
00:13:44当我们查看 web hook 路由的代码时,我们会看到 Slack 发送的事件载荷包含了我们确定性地重新创建此标识符所需的全部信息。
00:13:58这就是 web hook 如何映射回单个 Workflow 运行的奥秘。
00:14:04是的,当我看到 web hook 时我也很好奇,因为在魔术链接的例子中你是在创建新的 URL,但因为我们之前构建过 Slack 机器人,你不能在每个线程中都给它一个新 URL。
00:14:17所以,理解你这里做法的方式是,你有一个已经连接到 Slack 的 API,但每次在那里收到消息时,你基本上在恢复端计算相同的令牌。
00:14:29因此,你的 Workflow 基本上可以等待这个令牌,你可以从消息载荷中构建相同的令牌来恢复此 Workflow 运行。
00:14:37完全正确。是的。Slack 机器人只需在 Slack 的后台手动点击配置一次,你需要静态地定义一个 webhook 回调 URL。
00:14:50这就是为什么在这种情况下底层的 hook 原语效果更好,因为我们可以动态地重新创建令牌。
00:14:59让我们快速看一下,这是 webhook 路由,实际上并没有太多内容。
00:15:07最主要的是我们如何从 Slack 传递的数据中重新创建令牌。
00:15:13然后我们调用 resume 函数,这会恢复该 Workflow 运行。
00:15:20所以我其实在想,这真的很酷。我猜测对于 webhooks,你做的其实也是同样的事情。
00:15:28webhook 基本上只是做一个随机令牌,然后你有一个 HTTP 端点来解析相同的随机令牌吗?
00:15:35是的,不过 webhook 功能的区别在于,你不需要在代码中定义那个 API 路由。
00:15:44Workflow SDK 实际上已经为你实现了 webhook 功能的默认路由。
00:15:50但除此之外,它就是一个针对特定 Workflow 运行生成的随机令牌。
00:15:55但在这种情况下,我们有带令牌的 hook,回到你刚才提到的一点,这个 hook 可以多次接收数据。
00:16:06但它们通常只适合做演示,我一直没能找到一个好的实际应用方式。
00:16:11在这种情况下,我们希望 hook 为某人在 Slack 线程中输入的每条唯一消息都触发一次。
00:16:17为此,你使用了 JavaScript 中的 for await 语法,这在异步迭代器中很常见。
00:16:25在这种情况下,我们正通过 hook 接收来自 Slack webhook 的多个事件载荷。
00:16:33这太酷了。我从来没发现过这么好的用例……我喜欢异步迭代器和生成器,很久以前我甚至还做过一个关于这个的演讲。
00:16:42但它们通常只适用于演示,我一直没能找到一个好的使用方式。
00:16:46在这里,对我来说就像是你在使用一个循环。
00:16:50但你不是在某个固定的项集上循环,也不是因为时间戳而循环,因为你使用了 for await 并作用于 hook,这里的循环非常契合。
00:17:01循环内的所有内容都对应一条用户消息。
00:17:05这是一种很好的思考方式,每条新的用户消息都会引发此循环的另一次迭代,它只需排队并继续运行。
00:17:12最美妙的地方在于,在此循环的每次迭代中,当我们等待用户输入下一条消息时,绝对没有任何算力消耗。
00:17:22Workflow 完全处于挂起状态,线程中的下一条消息可能在几分钟、几天后到达,甚至永远不到达,这都完全没问题。
00:17:33所以,在那个带有 Slack 机器人的频道里,可能还有一些 Slack 线程,我现在回去看,可能还有一个运行实例在那里坐等了几个星期,只要没人回复。
00:17:42这真的很酷。
00:17:43回到我们之前提到的 messages 数组,现在我们正在修改该数组。
00:17:48我们正在推送新的用户消息,这就是我们的数据库修改,因为我们的 messages 数组只是一个局部变量。
00:17:57太酷了。然后我看到你在使用更多的 promise.all 来并行化中间的更多步骤。
00:18:03对于循环中的每一个迭代,对于 Slack 上的每一条消息,这读起来都非常清晰。
00:18:08我喜欢这种模式,这正是我在黑客松之类的活动中构建此类应用时会采用的模型。
00:18:12就像是,我会写下每一条消息触发时会发生什么。
00:18:16是的,通过 promise.all 模型,这些只是常规的 use step 函数,目的就是并行运行它们。
00:18:23比如在 Slack 消息中添加回应 (reaction),只是为了给用户更及时的反馈,让他们知道有事情正在发生。
00:18:32但与此同时,我们也想启动 LLM 来帮助推进故事生成过程。
00:18:39我真的很想看看等到我们有时间的时候,它的可观测性看起来是什么样子的,因为我可以想象那些跨度 (spans) 同时开始,让一切都变得非常直观。
00:18:49我们确实有故事时间机器人的可观测性界面。
00:18:52它已经完成了,所以我们必须回去确认一下那张图片。
00:18:56我们可以看到我们的 hook。
00:18:58这里有趣的是,在这种情况下,我们有两个 hook 接收事件。
00:19:05这对应于我在 Slack 线程中输入的那两条消息。
00:19:10可观测性功能允许我们查看提供给 hook 的具体数据。
00:19:14噢,那真的很酷。
00:19:16所以那基本上就是 Slack 的载荷。你不需要额外记录日志。Slack 载荷直接作为事件显示出来,我可以在仪表板上回溯检查。
00:19:25对。然后我们可以看到,每次接收到 hook 载荷时,它都会继续我们的 Workflow 执行,我们的步骤也会继续。
00:19:34最后我们得到了生成分镜图的结果,看起来就在那里。
00:19:40这就是故事时间机器人。
00:19:42这真的很酷。
00:19:43我觉得看到由魔术链接生成的 webhook,以及现在看到你使用带有 hook 的更底层原语,并且还在循环中使用它们以便处理多个事件。
00:19:54这真的太酷了。
00:19:55我觉得这种模式真的让我明白了该如何处理涉及人类操作的 webhook。
00:20:02hook 还有什么其他用途吗?
00:20:05是的,当然有。
00:20:06我准备的最后一个演示与响应 Slack webhook 的模式非常相似。
00:20:17但在这种情况下,我们将使用 webhook 作为一种移交应用程序代码执行权的方式,然后等待其他地方的某些计算完成,而我们的 Workflow 则挂起并等待。
00:20:32然后使用那个 webhook URL 调用回我们的 webhook,接着我们就可以完成应用程序逻辑中需要做的任何剩余事情。
00:20:41对于这个例子,我们将使用 Vercel Sandbox,并执行一些长时间运行的计算操作,比如使用 FFmpeg 进行文件转换。
00:20:51这就是我们的 FFmpeg 转换 Workflow。
00:20:56首先发生的事情之一是我们创建了一个 Vercel Sandbox。
00:21:00有趣的是,Sandbox 的 NPM 包暴露出的函数在底层也使用了 use Step。
00:21:09所以,实际上这个操作就是一个 Step。
00:21:12所以你可以发布一个 NPM 包。
00:21:15Sandbox 基本上就是在发布的 NPM 包里的这个函数中,使用了 use Step 指令。
00:21:21这样当你将其导入并用于工作流中时,它会自动将 Sandbox 变成一个 Step,而无需你编写任何相关代码。
00:21:29但这不代表你不能在工作流之外使用 Sandbox 来创建实例。
00:21:32如果你在没有工作流的情况下调用它,会发生什么?
00:21:35如果你意识到该指令只是一个字符串,且你只是要在没有工作流编译器的情况下执行它,那该字符串不会起任何作用。
00:21:47所以它能正常工作。
00:21:49在你的 NPM 包中添加 use Step,即使没有工作流 SDK 也能运行良好。
00:21:55而一旦你在工作流 SDK 中使用该函数,你就能开箱即用地获得额外的好处,即持久性收益。
00:22:03好,Sandbox 只是执行一些典型的操作。
00:22:07它会安装 FFmpeg,因为默认情况下不可用。
00:22:11它会下载我们指定文件的 URL。
00:22:14目前这些运行也都只是 Step 吗?
00:22:17是的,这些会在 Sandbox 中运行单个命令,它们都是 Step。我们能在可观测性视图中看到它们。
00:22:29然后我们回到调用 create-webhook,你可能还记得魔法链接(Magic Link)的演示。
00:22:36但在这种情况下,我们只是将该 Webhook URL 传递给我们在 Sandbox 中运行的 Bash 脚本。
00:22:43这里发生的是,我们将运行 FFmpeg,并将文件转换为我们在 UI 中请求的格式。
00:22:53完成后,Bash 脚本将针对 Webhook 的回调 URL 运行 cURL。
00:22:59当该 cURL 请求发生时,我们的工作流逻辑就会恢复。
00:23:04明白,这很酷。我已经看到了,我刚才看快了一点,我注意到这次运行中有一个 AND。
00:23:11所以你实际上是在后台编写并运行这个脚本,因为像这样的 FFmpeg 步骤可能需要很长时间。
00:23:17你不希望有一个步骤只是在那儿坐着等。
00:23:20没错。这一行就在后台启动了我们的 FFmpeg 转换脚本。
00:23:28然后我们的工作流函数挂起,等待 Webhook 被恢复。
00:23:34我又看到了 promise race 配合一小时的休眠。这真是个很棒的模式。
00:23:40对,这次我们的 FFmpeg 转换过程可能需要很长时间。
00:23:46它可能是一个非常大的媒体文件。所以在这个案例中,我们指定了一小时的超时。
00:23:51这完全没问题。在工作流中,你基本上可以无限期地休眠。
00:23:56而且同样,在等待 Webhook 恢复期间,零计算资源在运行。
00:24:01我们能看看这个吗?能看看运行效果吗?有演示吗?
00:24:04有的。
00:24:05这是一个有点傻的例子。
00:24:07哈哈,我立刻就认出那个大兔子的例子了。那是来自 Blender 的。
00:24:12是的,我记得很久以前学习 Blender 时看过这些视频。
00:24:16喔,我好嫉妒。
00:24:19我们已经粘贴了媒体文件的 URL。在这种情况下,我们将只提取其中的音频层。
00:24:26点击按钮后,就会启动一个工作流,我们应该能转到可观测性页面查看。
00:24:33噢,就在那儿。是的,我们可以看到我们的 Sandbox 创建过程。
00:24:37它返回了我们的 Sandbox 实例。非常酷。
00:24:42这是因为在工作流中,所有的东西都必须是可序列化的。
00:24:46但正如你所说,Sandbox 实现了序列化,所以它们在工作流中也是可序列化的,并且会显示出来。
00:24:53没错。Vercel Sandbox 的 NPM 包里有一个 Sandbox 类,该类实现了工作流序列化函数。
00:25:03所以它在我们的可观测性视图中能正常工作。
00:25:06所以任何包都能这样做,对吧?不只是 Sandbox。Sandbox 并没有什么特别之处,任何类只要想在工作流中运行,都可以实现相同的符号并使用 use Step 指令。
00:25:17是的,没错。我们可以看到,这次我们的钩子(Hook)在 20 秒内就被回调了。
00:25:25转换速度快了一点,因为它是个小文件,但实际上可以是任何时长。
00:25:31我们可以看到,在 Sandbox 创建并初始化之后,我们的钩子也被创建了,并被传递到 Sandbox 中以启动 FFmpeg 命令。
00:25:43当它结束时,我们收到了来自 Sandbox 的负载。
00:25:48这就是刚才 Bash 脚本里发生的 cURL 请求。它是先写入命令,然后直接在 Sandbox 中使用 cURL 来完成 Webhook。
00:25:57没错。我们的 Sandbox 完成了它正在做的工作,所以它把控制权交还给了我们的工作流。
00:26:04我现在对这件事的理解是,通过工作流中的 Step,你运行一个步骤,它在后台运行代码,然后继续工作流。
00:26:13但感觉 Hook 和 Webhook 都属于更底层。我可以直接创建一个令牌或一个 URL,然后等待任何事情。
00:26:21那可以是人类点击魔法链接、一封邮件、一个 Sandbox,或者任何必须发生的计算任务。
00:26:27我可以让工作流带着所有状态暂停,直到该事件发生。感觉它几乎比 Step 本身还要底层。
00:26:34是的。我的想法是,Webhook 和 Hook 是将外部负载传递到工作流中的一种方式。
00:26:42我的理解是,Step 是工作流挂起并等待某种计算完成,然后我们再恢复的一种方式。
00:26:50但 Hook 和 Webhook 感觉确实更底层,因为你只是在生成一个令牌或 URL,你可以把它发送到任何地方,在这个案例中是 Sandbox。
00:27:01它也可以发送给人类、发送邮件,或者发送给另一个工作流。
00:27:05每当该操作完成,你的父工作流基本上就会被唤醒,并从中断的地方恢复。
00:27:12所以它在某种程度上甚至比 Step 还要底层。它是让你的工作流为任何外部操作而挂起的一种方式。
00:27:19是的。我喜欢的理解方式是,Hook 是挂起工作流并等待外部负载传回工作流的一种方式,这非常强大。
00:27:31这真的很酷。我知道我们今天没时间了,但通过这些演示,你再次向我证明了为什么 Hook 是我在工作流中最喜欢的功能,我很期待继续使用它进行开发。
00:27:42太棒了。是的,我很高兴你喜欢。
Community Posts
No posts yet. Be the first to write about this video!
Write about this video