介绍 Spring Cloud Function

工程 | Mark Fisher | 2017年7月5日 | ...

Spring Cloud Function 是一个新的项目,具有以下高级目标

  • 促进通过函数实现业务逻辑。
  • 将业务逻辑的开发生命周期与任何特定的运行时目标解耦,以便相同的代码可以作为 Web 端点、流处理器或任务运行。
  • 支持跨无服务器提供商的统一编程模型,以及能够独立运行(本地或在 PaaS 中)。
  • 在无服务器提供商上启用 Spring Boot 功能(自动配置、依赖注入、指标)。

就像 Spring 一直以来都提倡基于普通 Java 对象 (POJO) 的编程模型一样,Spring Cloud Function 提倡基于普通函数的编程模型。我们的意思是 java.util.function 包中定义的核心接口:FunctionConsumerSupplier

这些类型的实现可以通过 @FunctionScan 启用的类路径扫描显式或隐式地注册为 bean。参数和/或返回类型可以选择使用 Reactor 的 Flux,它是一个 Reactive Streams Publisher。这使得它能够与其他 Reactive Streams 组件互操作,即使这些组件基于其他实现,例如 RxJava 2,并且它将诸如非阻塞 IO 和背压等响应式特性引入此处理模型(有关更多信息,请参阅 Project Reactor)。每当参数和/或返回类型不是 Flux 时,Spring Cloud Function 都会包装它们,以便函数可以通过 Flux 互操作。对于简单的逐项处理用例,您可以保持简单

public class Greeter implements Function<String, String> {
  public String apply(String name) {
    return "Hello " + name;
  }
}

但是,如果您需要实现通过窗口或归约操作将数据集作为处理单元处理的函数,则可以使用 Flux 类型

public static class WordCount
    implements Function<Flux<String>, Flux<Map<String, Integer>>> {
  public Flux<Map<String, Integer>> apply(Flux<String> phrases) {
    return phrases.window(3)
      .flatMap(f -> f.flatMap(phrase -> Flux.fromArray(phrase.split("\\W")))
      .reduce(new HashMap<String, Integer>(),
        (map, word) -> { map.merge(word, 1, Integer::sum); return map; }));
  }
}

依靠函数类型也使得组合功能变得很容易,例如

twistAndShout = twist.andThen(shout);

当然,函数也可以使用 lambda 表达式定义,例如

Function<String, String> shout =  s -> s.toUpperCase() + “!”;

事实上,Spring Cloud Function 支持将基于字符串的 lambda 表达式动态编译成函数实例。这在原型设计或添加一些琐碎的转换逻辑时特别有用,因为 Spring 表达式语言在今天被普遍使用。

您可能想知道为什么 Spring 需要推广此模型,因为无论如何您都可以轻松创建 FunctionConsumerSupplier 实例。了解答案涉及控制反转应该不足为奇。多年来,从基本的依赖注入到 Spring 对模板模式的普遍使用,都用好莱坞原则来描述:“不要给我们打电话,我们会给你打电话”。上面提到的 Flux 适配实际上是控制反转的一个例子,但更重要的是将业务逻辑与部署配置文件解耦。在这种情况下,业务逻辑指的是函数,而部署配置文件可以是 REST 应用程序、流处理应用程序或有限任务。Spring Cloud Function 为每种类型提供了一个 JAR,并且在每种情况下,都会使用自动配置的 FunctionCatalogApplicationContext 中查找 FunctionsConsumersSuppliers

例如,要将上面显示的 Greeter 函数作为 REST 端点部署,只需要添加“spring-cloud-function-web”依赖项,如 POM 中所示。这也包括 Spring Boot Maven 插件,以便构建生成可执行 JAR

./mvnw clean install
java -jar greeter/target/greeter-0.0.1-SNAPSHOT.jar

然后可以使用 curl 调用它

$ curl -H "Content-Type: text/plain" :8080/greeter -d World
Hello World

同样,要将函数部署为流处理器,只需要添加“spring-cloud-function-stream”依赖项,该依赖项又基于 Spring Cloud Stream。就像 Spring Cloud Stream 提供了 绑定器抽象,消除了定义通道适配器的需要一样,Spring Cloud Function 消除了声明服务激活器、转换器或 Spring Cloud Stream 委派到的 @StreamListener 注解方法的需要。“spring-cloud-function-stream”JAR 本身提供了所有这些。这是将控制反转提升到另一个层次的另一个例子。

在本博客系列的第 2 部分中,我们将提供有关如何在下一版本的 Spring Cloud Data Flow 中使用 SuppliersFunctionsConsumers 的示例。基本思想是,每当您需要提供一些自定义逻辑时,您只需实现简单的函数即可。这是意见领袖模型的完美示例,不仅您不需要提供样板代码,而且框架处理它更好。例如,您将能够仅注册函数 - 内联或打包为 JAR(而不是 Spring Cloud Stream 应用程序),然后在 DSL 中引用这些函数,同时依靠 Spring Cloud Data Flow 为您包装它们

mySupplier | myFunction | myConsumer

部署配置文件甚至扩展到无服务器 (又名函数即服务) 提供商领域,例如 AWS Lambda 和 Apache OpenWhisk(以及 Azure Functions 和 Google Cloud Functions,一旦它们提供对 Java 的支持)。在本博客系列的第 3 部分中,我们将深入探讨该主题的更多详细信息,但现在您可以仔细阅读 AWS Lambda 适配器Apache OpenWhisk 适配器 的文档。即将发布的博客还将涵盖与基于 Kubernetes 的无服务器框架(如 Fission)的集成。

除了解耦业务逻辑和基础设施的作用之外,各种部署配置文件 JAR 和 FaaS 适配器还提高了可移植性。开发人员可以完全隔离地实现函数,包括仅关注输入和输出参数的单元测试。然后,该函数可以与允许它在目标环境中运行的依赖项一起打包,范围从独立的 REST 应用程序到 Spring Cloud Data Flow 或 FaaS 提供商。

这将我们带到这篇入门博客的最后一点。“无服务器”一词引起了很多反感,并且几乎总是伴随着解释:“当然仍然存在服务器,但您不必考虑它们。”因此,虽然我们不会引入“无框架”一词,但相同的概念确实可以应用于框架。在上面的 Spring Cloud Data Flow 示例中,函数开发人员无需考虑框架,甚至无需生成其依赖项中包含任何框架代码的工件。相同的想法将应用于 FaaS 适配器。我们基本上将控制反转推到了我们可以将好莱坞原则扭曲成:“不要依赖我们,我们会依赖你”。这在好莱坞可能不受欢迎,但对于开发人员来说,这意味着您只需编写一个函数,将其打包到 JAR 中,并将其注册以用于各种端点或适配器。一如既往,Spring 遵循 Alan Kay 精辟地陈述的原则:“简单的事情应该很简单。复杂的事情应该是可能的。”在即将发布的博文中,我们将深入探讨由于 Spring Cloud Function 而可能发生的一些更复杂的事情,但我们永远不会忘记保持简单的事情简单。

敬请期待!

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部