Ohhnews

分类导航

$ cd ..
InfoQ Java原文

Clean Architecture for Serverless:可随处携带的业务逻辑

#clean architecture#serverless#spring cloud function#多云部署#kotlin

InfoQ 首页 演讲

无服务器的整洁架构:可随身携带的业务逻辑

观看演讲 时长:49:28

[LOADING...]

摘要

Elena van Engelen 探讨了如何在不牺牲原生云能力的前提下消除无服务器供应商锁定。她解释了如何利用整洁架构、Spring Cloud Function 和 Gradle 模块来结构化 FaaS 应用程序,从而实现业务逻辑的隔离。最后,她通过一个实时演示展示了如何使用 Terraform CDK 进行多云基础设施即代码(IaC),在 AWS 和 Azure 上部署可移植的 Kotlin 服务。

个人简介

Elena van Engelen 是一位拥有二十多年经验的专业软件工程师,对技术充满热情。她专精于 Kotlin 和云原生解决方案,专注于构建关键任务、可扩展且可维护的系统。她还乐于通过演讲、博客、Kotlin 培训以及她的著作《Kotlin Crash Course》分享知识。

关于本次会议

InfoQ Dev Summit 慕尼黑软件开发者大会聚焦高级开发团队当前面临的关键软件挑战。获取来自 20 多位资深软件开发人员的宝贵实际技术见解,与演讲者和同行交流,并享受社交活动。

INFOQ 活动

  • [LOADING...]2026年8月6日,美国东部时间下午1点

为高风险事件响应构建 AI Agent 评估机制

主讲人:Brianne Bujnowski – Datadog AI 高级产品营销经理,Benjamin Barton – Datadog 高级软件工程师

  • [LOADING...]2026年8月27日,美国东部时间下午1点

框架之下:为什么 Agent 上下文是一个基础设施问题

主讲人:Boyd Stowe – Tacnode 创始解决方案架构师

演讲稿

Elena van Engelen: 无服务器是否在暗中锁定我们?许多开发者和思想领袖认为确实如此。这是一个在当今洲际局势紧张等背景下非常好的问题,因此非常切合实际。如果你能利用架构来帮助你的业务逻辑保持云无关呢?这就是我们今天要探讨的内容。我们将研究一些构建块,这些构建块可以帮助我们保持业务逻辑的云无关性,同时我们仍然在不同云提供商上使用无服务器构建块。我们将看看 Spring Cloud Function。这里有人用过 Spring 吗?整洁架构。我想很多人都听说过这个?我们将使用一些 Gradle 模块。这可能更具体一些。我们将使用这些构建块来引导我们得出关键结论:一个云无关业务逻辑的框架。我是 Elena van Engelen,一位专精于 Kotlin 和云原生解决方案的高级软件工程师。我目前在 AZL(NN 集团的人寿和养老金部门)担任首席工程师。我也是 AWS 无服务器类别的社区贡献者,是《Kotlin Crash Course》一书的作者,以及 Kotlin 和云主题的博主。

无服务器与函数即服务(FaaS)

让我们继续深入无服务器。什么是无服务器?你用过无服务器吗?谁用过无服务器?并不是说我们没有服务器。我们确实有服务器。只是我们不必管理基础设施。我们不必扫描容器的漏洞。我们不必修补操作系统。我们不必担心硬件。这让我们的生活稍微轻松了一些。它是按使用量付费的资源。我们自动扩展,并且由事件触发。我们稍微对比一下基于容器的应用。在基于容器的应用中,你可能会有微服务或单体应用等。一个东西里有很多业务逻辑。当你开始扩展时,因为请求增多而需要扩展,你需要扩展所有内容。你会扩展所有内容。而当你使用函数即服务时,它们非常简单。一个函数有输入和输出,所以它的职责少得多。它实际上应该只有一个职责。你的微服务、单体应用或产品将由多个函数构建而成。当事件到来时,只需要扩展其中一些或一个函数,而不是全部。如果你有一些后台任务,或者处理消息等事件,或者处理 HTTP 事件,如果其中某个函数收到大量请求,只有它们需要扩展。使用无服务器,你的资源利用效率要高得多。让我们看看你可能会使用无服务器的一些用例。这些是非常常见的用例。当我说 REST API 时,你可能在想冷启动。像 AWS 和 Azure 这样的大型云提供商已经缓解了冷启动问题。你可以缓解它,并且仍然可以使用 REST API。你只需要确保将冷启动考虑在内。例如,AWS 有 SnapStart。如果你在 JVM 上结合预热使用它,你实际上可以消除冷启动,即使在 p99 上也几乎感觉不到。在 Azure 上,你可以使用 Elastic Premium。它不是免费的,但会缓解冷启动。物联网事件处理,这是一个非常好的用例。我实际上曾在 PostNL 工作,这是荷兰最大的包裹递送公司。他们在所有方面都使用无服务器 AWS。他们每天处理 8 亿个事件,并且使用无服务器来处理。这对物联网来说非常有效。数据转换、点击流、定时任务。如果你有一个定时任务,你不想让容器等一整天来每天运行一次或两次。这些也是很好的用例,前提是你的定时任务不是长时间运行的。基本上,无服务器可以覆盖所有这些用例。

Spring Cloud Function

我们说函数即服务让我们的生活更轻松。我们还问,可移植性怎么办?让我们看看 Spring Cloud Function、整洁架构和 Gradle 模块如何帮助我们的业务逻辑保持云无关。Spring Cloud Function 简介。正如我看到的,只有一半的人使用 Spring,所以基本上,Spring 是一个允许你使用控制反转的框架。它是一个非常流行的 JVM 语言框架,也可以在 .NET 中使用。我认为其他语言也有类似的依赖注入框架。Spring Cloud Function 基本上允许我们在函数即服务中运行 Spring 应用。这就是它能让你做到的事情。因此,你可以在你的函数即服务中使用你的 Spring 特性。你最喜欢的 Spring 特性:依赖注入和自动配置,任何你喜欢的东西。它拥有一个非常大的生态系统。它能适应环境。你可以在本地运行,也可以在 AWS Lambda、Azure Functions 上运行。它们还支持其他云。你甚至可以为你自己的云编写适配器。基本上,要在函数即服务上运行你的 Spring Boot 应用,你只需要 Spring Cloud 适配器。让我们立刻跳到代码中,看看它看起来什么样。因为 Spring 宣称我们是平台无关的。实际上,当你开始编写代码并尝试将其部署到云提供商时,你会发现很多情况下,你仍然需要编写云特定的代码。首先,我们将使用云适配器。这实际上是云特定的依赖项,我们不想在不同云上部署它,我们只想在 Azure 上部署它。这是 Azure 的情况。我们将为 Azure 编写一个函数,包含 Spring Cloud 适配器。然后我们需要创建一个函数入口点。这就像一个 Hello World 示例。这将是一个 HTTP 触发器。它有 FunctionName。然后我们定义 httpTrigger,它将在一个端点的 docs flow 上接受 POST 请求。我们将添加一些基本的安全措施。这是一个演示,所以我们不会过于担心安全。让我们只添加基本的安全措施。这是一个 API 密钥,我们将用于安全。这就是 Azure 上函数的样子。为了运行这个 Hello World 示例,我们当然需要部署它。我们将使用 CDK。我使用 Terraform CDK,因为我要部署到多个云。谁用过 Terraform?它是基础设施即代码。使用 CDK 的人少得多,它基本上是使用编程语言来生成你的 Terraform 文件,然后你可以部署。然后你可以使用你自己的语言来做到这一点。对于 Azure Functions,我们只需要指定一个 function app,我们的函数将在其中运行。在那里我们必须指定 MAIN_CLASS。这是我们 Spring 应用的 MAIN_CLASS。这是我们需要做的唯一一件事,它就会正常工作。Hello World 已经在云中运行了。我会展示给你看,因为我们现在不想做 Hello World。任何人都可以做 Hello World。AWS 的情况如何?首先,我们需要包含依赖项。这是 AWS 特定的 Spring Cloud Function 适配器。同样,这是 AWS 特定的。我们需要将其包含在我们的应用中。然后我们有一些代码在触发器触发时执行,即 HTTP 触发器。我们的 HTTP 触发器在哪里?它不在代码中。它不同。对于 AWS,我们实现我们的触发器,它将使用来自 SDK 的 AWS 特定请求和响应。触发器在基础设施即代码中。这与 Azure 不同。让我们快速看一下函数的名称:uploadDocument。稍后我们会用到它。当我们转到 AWS 的基础设施即代码时,首先,我们需要定义我们的 Lambda。这是我们的代码将运行的地方。为了配置我们的 Spring Cloud Function,我们需要指定函数的名称:Spring_Cloud_Function_Definition。那就是我们刚才看到的 upload document 函数名。然后使用 MAIN_CLASS,再次,我们需要指定我们 Spring 应用的 MAIN_CLASS。我们还需要指定处理程序。AWS Lambda 需要知道在收到事件时调用什么。这实际上是云适配器中的一个类,你需要指定它。完成之后,你需要指定触发器。我们还没有触发器。触发器是我们的 DocsFlow 端点。那将是我们的 API 网关规范。DocsFlow,我们想要到达 DocsFlow 端点。我们想要接受两个请求。我们也希望有一个 API 密钥用于一些基本的安全。这将与 Azure 相同。你可以看到,定义触发器与定义入口点是不同的。这些事情应该与你的业务逻辑分离。正如我所说,我们不会做 Hello World。那很无聊。我将快速展示一下。我们有两个运行中的 Hello World。这是 AWS 的那个。我展示我正在指向 AWS DocsFlow 端点。这是一个运行中的 Hello World,什么也不做,因为稍后我将把业务逻辑插入到这些端点中。而不是使用 Hello World,让我们转到 Azure。同样,那里什么也没有。这是 Azure Function。我们只是做一个没有任何内容的 POST 请求。我们实际上没有在其中发布任何信息,因为它什么也不做。我们还没有插入任何东西。那基本上就是 Hello World。我们要构建什么?我将回到我写书的经历。每次我写完一章,一旦完成,它就需要被审阅。我必须通过电子邮件发送它,并且为章节指定一个特殊的名称,这样他们就知道它处于哪个审阅阶段。然后我将带着章节通过电子邮件发送给审阅者。首先,是编辑审阅。然后他们会给我评论,我修改。然后发送给技术审阅。他们会做技术审阅。然后,章节名称会再次改变,因为他们想知道它处于哪个阶段。然后它将进入最终编辑审阅。如果后来你想到,实际上我可以将这一点添加到已经定稿的章节中。那么你就会进入这个异常场景,它必须再次被审阅。这非常手动。让我们稍微现代化一下,添加一些业务逻辑,这样我们就可以自动完成它。我们要做的是使用 Azure 和 AWS。我们将把我们的业务逻辑部署到两个云上。它们也会上传文档。我没有做图形用户界面,所以我将使用 Postman 上传。我们将上传一个文档到 API 端点。在 Azure 上,它将是 Azure Functions。这将使用该发布者的通用业务逻辑验证我的章节是否符合基本要求。如果我的章节符合要求,可能包括图片质量、字数或其他,它将保存到存储中。在 Azure 上,它将存储到 Blob 存储。在 AWS 上,它将存储到 S3。一旦我的文档通过了验证并被保存,我实际上想要进行自动审阅。假设它可能是该发布者特定的某个 AI 模型来进行审阅,或者其他一些业务逻辑。这些东西将是云无关的。它们需要部署在函数即服务上,这些服务将响应文档被放入存储的事件。这将实际生成一封带有安全链接的电子邮件,并发送给可以进行进一步审阅的人工审阅者。这就是我们要做的。感觉像一个真正的应用。你可以看到我使用了真正的云服务。即使我想要可移植性,我也没有避免使用任何云服务。我使用的是 Azure Functions、Blob 存储、Azure Communication Services 用于电子邮件。在 AWS 的情况下,我使用的是 API Gateway、Lambda、S3 和 SES(简单电子邮件服务)。

无服务器的整洁架构

我们要怎么做?我们将使用无服务器的整洁架构。这实际上是整洁架构的简化版本。我们不需要这么多层。因为这不是你典型的、职责众多的基于容器的应用。它就像一个只有一个职责的函数。你不需要过于复杂的分层。你可以使用简化的分层。你有一个领域层。它将包含你的领域逻辑、领域对象、基本的领域对象验证等。然后你有一个应用层,它将包含你的用例、业务逻辑、接口。然后你将有一个基础设施层。这是你放置云特定代码的地方。我相信这种样板代码最终可能会由 AI 生成。比如保存到 Blob 存储、保存到 S3 的代码。那些将你和你的业务逻辑连接到云的代码,将它们分开,并放在一个单独的层中。让我们看一些真实世界的例子。我们看看我工作的养老金行业。它并不无聊,听起来很无聊,养老金,其实不然。你有一个养老金基金的参与者。参与者、养老金基金,这是两个领域对象。你还有生活事件。为什么我们需要生活事件?许多国家实际上有关于生活事件的规则,这些规则会影响你的养老金。以荷兰为例。如果你结婚了,我们需要知道这件事。这个事件必须进来,我们必须登记它。因为如果你最终离婚了,你的伴侣可能会拿走你的一部分养老金,并且他们会成为新的参与者。我们必须自动处理这件事。我们需要知道这件事。另一个事件,当你去世时,我们需要知道,因为你的养老金有一些受益者。主要是你的孩子和你的伴侣。他们对你的养老金有权利。他们将成为参与者。还有,你20年前的前任,他们可以要求你在20年前与他们在一起的那段时间对应的养老金。这真的很复杂。这些业务规则很复杂。如果我知道这些,我可能会重新考虑在荷兰结婚。你不想把这些业务规则绑定到你的云上。这真的很复杂。你想把它们分开,这样你就能更灵活,并延长你的应用生命周期。如果我们看我们现在要构建的应用,当然要简单得多。在领域层,我们将只放置文档元数据,我们上传的文档。我们想了解它的一些信息。在应用层,我们有两个服务。一个将验证文档,另一个将审阅文档,实际上还会发送电子邮件。这是两个职责。是的,所以它将审阅文档并将审阅结果作为电子邮件发送给审阅者。

整洁架构 —— 使用 Gradle 模块

这就是在 Gradle 模块中看起来的样子。你有领域和应用模块。它们没有任何云知识。它们没有对云的依赖。它们对云一无所知。使用 Gradle,我们可以强制执行这一点,并确保如果你试图访问任何基础设施代码或任何云特定代码,代码将无法编译。然后你为每个云有一个模块。你有 AWS 或 Azure。当我们打包以部署到云时,我们只打包我们需要的基础设施代码。在 AWS 包中不会有任何 Azure 代码,在 Azure 包中也不会有任何 AWS 代码。然后我还有一个 CDK。CDK 实际上不会被部署,因为这只是一个描述。这只是你的基础设施的配置。它将在流水线中用于实际部署你的基础设施。它没有打包在你的部署包中,只是用于指定你的基础设施。再次,对于 Azure 也是如此。这就是在 Gradle 中的样子,因为不是每个人都用它。例如,在 settings.gradle 中,你可以定义你的模块。然后,在特定模块中,你可以指定你的依赖项。例如,在应用模块中,我有对领域的依赖。应用知道领域,但它对基础设施一无所知。这就像一个向内依赖。领域什么都不知道。应用知道领域。基础设施知道应用和领域。如果我们反过来做,它不会编译。这就是我们要做的,放置相同的业务逻辑。然后我们将使用依赖注入在运行时无缝地将我们的业务逻辑连接到云,以实际调用实际的特定云组件。

演示

让我进入代码。我们将做一些编码。很快展示给你看,这里我们有应用层。那是业务逻辑。这是我右手边的领域逻辑。这是基础设施。基本上,我现在要做的是将我的业务逻辑连接到基础设施。基础设施有我的函数的入口点。在我进入之前,我将快速返回到解决方案设计。我可以提醒你我们正在做什么。我们现在要做的是实现注入到函数中的业务逻辑,该函数将接收文档,以及将审阅文档的函数。AWS Lambda 也是一样。同样的业务逻辑将用于接收文档、验证和实际审阅文档。让我们从 Azure 开始。首先,我将打开类,然后我将关闭那个窗口,这样你就能读到代码了。我认为它是可读的。这是我的两个函数的容器。一个函数是 UploadDocument,我们将从这个开始。当你上传文档时,我们想要验证它,并将其保存到存储。然后,在它保存到存储之后,我们想要审阅它。我要做的是注入那些服务。谢谢 AI。一旦我注入了服务,我实际上想要使用它们,并用实际的调用替换我的 Hello World 逻辑。这将实际接收文档并验证它。如果它有效,保存到存储。然后我想转到监听 Blob 存储的部分。如果有文档被放置,我想审阅它。我将替换这个 Hello World 逻辑来实际调用应用层的业务逻辑。这将审阅文档并实际发送电子邮件。它实际上做两件事:审阅文档和发送电子邮件。我已经完成了 Azure Function。让我们做 AWS。我们将完全一样。只需将我们的 AWS Lambda 入口点注入业务逻辑。非常相似。让我们注入。那不是我想做的。AI 并不总是做我想做的事。一旦我注入了业务逻辑,我就可以从我的入口点调用它。UploadDocument 将被调用。对于 HTTP 请求,我将提交文档。我们将替换 Hello World,第二个函数 processDocument。当我有一个 S3 事件,当文档被保存时,我想把我的 Hello World 替换为 reviewAndNotifyDocument。我们已经将我们的业务逻辑注入到云入口点。现在我们要做的是用接口注入我们的业务逻辑,这些接口将把它连接到云代码,而它不知道或不依赖于这些模块中的任何东西。这是最后一部分,我们要做的。然后我们将部署。我们回到我们的应用。这是我们刚刚注入的业务逻辑。这里我们有几个接口。这其实不是一个新概念。我们有几个接口。一个将允许我们发送电子邮件,并将我们连接到实际的实现。我可以看到,这里我在每个基础设施模块中有两个实现。AWS 将有 SES 电子邮件发送器,Azure 将有 ACS 电子邮件发送器。业务逻辑当然不知道这一点。对象存储也是如此。一个与 S3 通信,另一个与 Blob 存储通信。我们要进入我们的业务逻辑。首先,审阅。让我们进入,这样你就能看到代码了。这里我们有这个 DocsFlow 请求处理程序。一旦我提交文档,它将验证并保存到存储。让我们用对象存储注入它。这里我们有一些验证逻辑,它是可移植的。它只是返回 true。我这里实际上没有做任何业务逻辑,因为这只是演示。它将验证文档,返回 true。它总是会验证,然后保存。我们想在这里做的是实际保存到对象存储。我们保存到对象存储。我们正在保存文档。让我们删除 TODO 行。第一个服务就完成了。它只会验证和保存。然后,让我们进入第二个业务逻辑服务。让我打开它。这个将做两件事。它将基本上审阅文档。可能是 AI 或任何其他业务逻辑。然后它将为你的文档生成一个安全 URL,并将电子邮件发送给实际的审阅者。我将删除那个名称。我们有了两个服务,我们实际上是在注入我们的业务逻辑。对于安全链接,我们可以调用我们的通用对象存储。我们不知道我们在调用哪个云,所以是对象存储。我们只想为我们的 blobId 生成一个安全 URI。这将为审阅者提供 URI,这样他们就可以点击文档。这基本上是我复杂的审阅业务逻辑。目前它只是一个随机字符串。我现在没有插入任何 AI。你可以在里面放各种业务逻辑。一旦审阅完成,我们可以使用通知 API 发送电子邮件。发送带有审阅结果的电子邮件,审阅结果包含安全 URL。基本上是一个非常简单的例子。我们已经完成了所有这些,但在部署之前,让我们先构建一下,因为同时部署到两个云能出什么错呢?让我们进入控制台。我将构建它。确保在我做任何提交之前它是绿色的。我现在要做的是提交到我的 Git 仓库,那里有两个流水线。一个将部署到 Azure,一个将部署到 AWS。我们没问题。这将进行提交。让我们删除那个。我们实际修改了四个类。两个函数类(特定于 AWS 和 Azure)用于插入业务逻辑,然后我们修改了业务逻辑以实际使用云的特定接口。既然我们已经推送了,接下来应该启动一些流水线。让我们看看进展如何。我们有两条流水线,一条通向 AWS,另一条通向 Azure。

部署 - Terraform CDK

在等待的同时,我们先回头看看部署方式。Terraform CDK——我知道并非所有人都喜欢 Terraform。但 Terraform 有它的优势,其中之一就是你可以部署到多个云平台,并且使用相同的语言。不过方言不同,因为部署到 AWS 时使用 AWS 资源,部署到 Azure 时则使用 Azure 资源。代码并不完全相同,但概念是相通的。由于使用了 CDK,你实际上是在用自己熟悉的编程语言,这有助于将运维融入开发,也就是 DevOps。开发者来操作,比学习 tf 格式的 Terraform 文件要容易得多。多云兼容性来自 Terraform。可重用性:你可以重用 Terraform 模块。如果我写了一个 tf 文件的 Terraform 模块,可以在 CDK 中重用;或者我用 .NET 写的,也可以重用,因为它最终会生成 Terraform 文件。可预测的变更:如果我修改了基础设施,可以看到云上会发生什么变化。具体如何操作?如果我创建了新的基础设施即代码,需要运行 cdktf get 来获取所有依赖。然后运行 synth,它会生成 Terraform 文件。这个过程很慢,所以只在修改基础设施即代码时才执行。在流水线中,只有当基础设施即代码发生变化时才运行它,否则不需要运行。因为如果你只更新软件而不更新基础设施,只需执行 Terraform plan 和 apply,将新软件上传到函数即可,这样快得多。

演示

让我们看看流水线。正常情况下此刻应该有一条完成了。我的 AWS 已经完成了,所以我们可以先看看 AWS 的部分。不过在我进入之前,先看看 S3 存储桶里有什么——目前是空的,我还没有提交任何文档。同样地,Azure 的 Blob 存储也是空的。现在我要打开 Postman。我想如果明年我用 AI 的话,我会用 AI 创建一个前端。我不擅长前端,但 AI 不一样。这里我们有一个 Hello World 端点,现在我们要连接到 AWS,因为它是第一个完成部署的。我点击了 AWS 的端点,它指向我的 AWS 环境。之前我们看到的是 Hello World,现在我们要上传一个文件进行审核。我将上传第 13 章,这一章讲的是 AWS 上的事件驱动无服务器。我们要对它进行审核。提交这个文档,看看会发生什么。正常情况下,它会审核文档,然后给我发送一封包含审核结果和安全链接的邮件。点击链接,我应该能下载该章节并看到审核评论。我看到它已经完成了,并且保存了一些登录信息。很好。我们先查看浏览器,应该能在 S3 中看到文档已经到达。就是这个文档。我们还应该看到一封邮件。请忽略我其他的邮件。这就是那封邮件,它给了我称赞,说文章读起来很流畅。然后我点击文档,应该能下载第 13 章。打开后,可以看到第 13 章的内容,所以功能正常。

接下来我们要看 Azure,因为它做的事情完全相同。让我们确认 Azure 是否已经部署完成,因为 Azure 部署需要更长时间。稍微等一等 Azure。在展示 Azure 部分之前,我需要等它部署完我的应用。同时我可以展示一下流水线。让我们打开 IntelliJ。我使用的是 GitHub Actions 来部署,并不复杂。看看 AWS 的部署流程,我主要做的是:构建包,将包上传到 S3 存储桶,然后为了部署,我会执行部署操作。这里我会把它放到 S3 存储桶中,然后用 Terraform 进行部署。Azure 的流程非常类似:上传到 Blob 存储,然后用 terraform apply 来部署任何变更,这正是我们刚刚做的。快速展示一下 CDK 在哪里。这就是包含基础设施即代码的 CDK。例如,AWS 部分非常类似。我不知道有谁用过原生的 AWS CDK。它和那个类似,但更冗长。你需要指定权限,比如当你的 AWS Lambda 需要连接 S3 时,需要指定 IAM 权限,还有 API 网关。非常相似,但更详细。如果我只在一个云上,我更喜欢使用原生 CDK,即 AWS CDK。但如果你要部署到多个云,使用同一种语言就非常方便。例如,这个用于 API 网关的 Lambda 权限,它看起来像这样:一个用实际编程语言编写的构建器。让我们回去看 Azure。Azure 已经完成了。现在我们可以演示 Azure 的部分。转到 Postman 中的 Azure 集合。之前是 Hello World,现在指向 Azure。我们要上传文档。选一个不同的章节,比如第 8 章,关于函数式编程。让它审核文档。由于业务逻辑相同,而且我们使用了随机字符串,通常它会生成不同的审核结果。现在已经完成了。如果滚动一下,可以看到它正在保存到 Blob 存储。我们可以进入之前为空的 Blob 存储,刷新后就能看到我们的文档。邮件也应该到达了。我们收到了邮件,其中 80% 是关于 AWS 的,这一封是关于 Azure 的。审核结果不同,它说连 AI 都在中途停止了阅读,需要喝杯咖啡休息一下,说明它不喜欢这篇文档。我们需要改进文档。点击文档,可以下载。确实是第 8 章,所以功能正常。

关键要点

我们完成了演示。现在进入最后一部分。让我切换到浏览器。关键要点是:如果你将业务逻辑分离出来,就可以实现有意义的可移植性。你可以将业务逻辑移植到不同的云上。这真的很简单。所有这些概念并不新鲜,但这里我们是实际应用到了可移植性上。当公司说不想使用无服务器是因为会被云锁定时,这并不一定正确。如果我在容器中调用 S3,我仍然与云相连,还是需要分离。容器也有类似的问题。Spring Cloud Function 允许我们在运行时进行依赖注入。还有其他框架也支持依赖注入,有些是在编译时进行的。在你使用的其他语言中,很可能也有类似的框架。Gradle 模块让我们能够强制各层之间的分离。这确实有助于防止开发人员滥用架构,不遵循指导原则。因为时间压力,你需要快速开发。如果不强制实施,我敢肯定随着时间的推移会出现问题。Kotlin 并不是必须的,但它确实有帮助,因为当我在云上运行时,我并不依赖 JVM,因为 Kotlin 可以针对 JVM 8 及以上版本编译。这意味着当 AWS 升级到 JVM 或 Java 21 时,Azure 可能落后了。如果你已经在使用 21,迁移时就会遇到麻烦。而使用 Kotlin,我只需使用最新版本,不用关心它运行在哪个 JVM 上。这很有帮助。Terraform CDK 也有帮助,因为你使用同一种语言,尽管用词不同,但它有助于可移植性。

资源

我有一篇 Medium 博客讨论了同样的话题。我还有一个不同的示例。如果你想看不同示例的 GitHub 仓库以及 Medium 博客(NN Tech 博客),请扫描二维码。这是我的网站,上面有一些社交媒体的链接:elenavanengelenmaslova.github.io。

问答

参与者 1: 你选择了一个技术用例而不是领域用例——上传到对象存储。上传到对象存储不也应该与云无关吗?

Elena van Engelen: 基本上,它就是上传到存储。不需要说对象存储,就是上传到存储。你不知道是哪种存储,可能是数据库。不一定是 S3。

参与者 1: 是的,但抽象应该体现在上传这个操作上。在你的业务代码中,你不知道它是 Azure Blob 存储还是 Amazon S3。因为 S3 本身也是一个 API。我认为它也适用于 Azure Blob 存储。

Elena van Engelen: 你是指函数的命名吗?

参与者 1: 不是,我是说什么时候进行抽象?技术抽象与领域逻辑的分离。

Elena van Engelen: 你可以直接说 save。我记得我那里用的是 save。可能接口的名字不同。基本上就是 save。你得到一个对象,比如一个文档,你想要把它存到某处。你可以说 store、storage。或者你可以说 save、persist(持久化)。

参与者 1: 没错,而不需要知道它在哪个云上。

Elena van Engelen: 不需要知道是哪个云或哪个数据库。我也可以把它保存到 Cosmos 数据库或其他地方。Blob 存储和 S3 都很便宜。我也可以保存到其他地方,不一定是它们。重点是说“把它存到某处”或“保存在某处”。在业务逻辑中,一旦文档通过审核,你就想把它存起来。这就是命名的含义。实际上你可以用 save 或 persist。

参与者 2: 现在创建无服务器应用有些焦虑。在大型公司,我们非常关注可观测性。你展示的内容听起来很吸引人,因为我们可以将相同的业务逻辑部署到多个云上。你在可观测性方面有经验吗?我们在代码中没看到很多日志消息。你是否在多个云上为相同的业务逻辑实现了相同的可观测性?

Elena van Engelen: 是的。当涉及到非功能性需求时,当然会有些差异。但在代码中,你不必体现这些差异。通常,在 Spring 中,你只需用 log.info,然后连接到特定的日志记录器。在基础设施设置中,你会说这个日志记录器将输出到 CloudWatch 之类的目标。这属于基础设施层。当你记录日志时,不需要修改日志 API。如果你想要将日志定向到某个日志系统,需要在基础设施即代码中做出改变。因为你可以记录到通用的、非云特定的目标,也可以记录到云特定的目标(如 CloudWatch)。本质上是将消息发送到不同的地方。同样的道理,你会把它放在基础设施层。代码中的实际日志行不会改变。你只需要确保使用通用的日志 API,而不是云 SDK 中的日志。在底层,你应该连接云 SDK 特定的日志记录器或你想记录到的地方。我认为即使你不迁移到另一个云,这也是一样的。


查看更多带有文字记录的演讲:https://www.infoq.com/transcripts/presentations/