介绍 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

这些类型的实现可以通过显式或隐式方式注册为 bean,通过 @FunctionScan 启用的类路径扫描。参数和/或返回类型可以选择使用 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 Expression Language 现在常用一样。

您可能会问,既然您可以轻松创建 FunctionConsumerSupplier 实例,为什么 Spring 推广这种模型是必要的。答案涉及控制反转(Inversion of Control),这应该不足为奇。多年来,从基本的依赖注入到 Spring 广泛使用的模板模式,都被好莱坞原则描述为:“不要打电话给我们,我们会打电话给你”。上面提到的 Flux 适配实际上是控制反转的一个例子,但更重要的是业务逻辑与部署配置文件的解耦。在这种情况下,业务逻辑指的是函数,而部署配置文件可以是 REST 应用、流处理应用或有限任务。Spring Cloud Function 为每种类型提供了一个 JAR 包,并且在每种情况下,都会使用自动配置的 FunctionCatalog 来定位 ApplicationContext 中的 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 提供了 Binder 抽象,消除了定义 Channel Adapters 的需要一样,Spring Cloud Function 消除了声明 Service Activators、Transformers 或甚至是 Spring Cloud Stream 委托的带有 @StreamListener 注解的方法等组件的需要。“spring-cloud-function-stream” JAR 本身提供了所有这些功能。这是将控制反转提升到另一个层次的又一个案例。

在本博客系列的第二部分,我们将提供关于如何在下一版 Spring Cloud Data Flow 中使用 SuppliersFunctionsConsumers 的示例。基本思想是,无论何时您需要提供一些自定义逻辑,您都可以简单地实现函数。这是一个有主见的模型的完美示例,您不仅不需要提供样板代码,而且最好还是让框架来处理这些。例如,您将能够只注册函数——可以是内联的,也可以打包成 JAR(而不是 Spring Cloud Stream 应用),然后在 DSL 中引用它们,同时依靠 Spring Cloud Data Flow 为您包装它们

mySupplier | myFunction | myConsumer

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

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

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

敬请关注!

订阅 Spring 邮件列表

订阅 Spring 邮件列表,保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速发展。

了解更多

获取支持

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

了解更多

即将举办的活动

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

查看全部