领先一步
VMware 提供培训和认证,助您加速进步。
了解更多《12 因素应用宣言》中详细讨论了“后端服务”。后端服务基本上就是您的应用程序为了完成工作而使用的任何网络附加服务。这可能是一个 MongoDB 实例、PostgreSQL 数据库、Amazon S3 等二进制存储、New Relic 等指标收集服务、RabbitMQ 或 ActiveMQ 消息队列、Memcached 或 Redis 缓存、FTP 服务、电子邮件服务,或者其他任何东西。关键在于应用程序如何暴露和使用这些服务,而不是服务本身是什么。对于应用程序来说,两者都是附加资源,通过 URL 或存储在配置中的其他定位符/凭据进行访问。
我们探讨了如何使用配置来提取诸如定位符和凭据之类的魔术字符串,并将其从应用程序代码中移出并外部化。我们还研究了服务注册表的使用,以便在动态(通常是云)环境中维护一个微服务的“电话簿”。
在本文中,我们将探讨平台即服务(PaaS)环境(如Cloud Foundry或 Heroku)通常如何暴露后端服务,并研究如何在 Spring 应用程序内部使用这些服务。在我们的示例中,我们将使用 Cloud Foundry,因为它开源且易于在任何数据中心或托管环境中运行,尽管其中大部分内容也适用于 Heroku。
我的朋友Abby Gregory Kearns对Cloud Foundry 的后端服务组合的角色和价值进行了非常好的高层概述。
像 Cloud Foundry 这样的 PaaS 将后端服务公开为操作系统进程本地环境变量。环境变量很方便,因为它们适用于所有语言和运行时,并且易于在不同环境之间更改。这比尝试在本地计算机上启动和运行 JNDI 要简单得多,并且有助于实现可移植构建。我打算在本文中通过当前 Cloud Foundry 的视角专门探讨后端服务。请记住,这种方法特别旨在促进云环境外部的可移植构建。Spring 是为可移植性量身定制的;依赖注入促进了 bean(例如来自后端服务的 bean)的初始化和获取逻辑与其使用位置的分离。我们可以使用 Spring 编写考虑 javax.sql.DataSources 的代码,然后编写配置以从正确的上下文和配置中源化该 DataSource,使应用程序能够在不同环境之间迁移。Spring 框架专为可移植性而设计。
下一版本 Cloud Foundry 的运行时(在新闻报道中通常称为Diego)是 Docker 优先且 Docker 原生的。当然,Docker 可以轻松地容器化应用程序,并且容器化应用程序与外部世界之间的接口故意保持最小化,再次强调,以促进可移植应用程序。Docker 镜像的关键输入之一是,您猜对了,环境变量!我们的朋友 Chris Richardson 撰写了几篇关于打包和构建基于 Spring Boot 的 Docker 镜像以及关于启动后端服务的优秀博文。本文中我们不讨论 Docker(但请继续关注!),但重要的是要理解:环境变量是外部化后端服务连接信息的简单且灵活的方式。
DataSource 的简单 Spring Boot 应用程序这是一个简单的 Spring Boot 应用程序,它插入并公开了来自 DataSource bean 的一些记录,Spring Boot 会自动为我们创建该 bean,因为我们的 CLASSPATH 中有 H2 嵌入式数据库驱动程序。如果 Spring Boot 未检测到类型为 javax.sql.DataSource 的 bean,但确实检测到嵌入式数据库驱动程序(H2、Derby、HSQL),它将自动创建一个嵌入式 javax.sql.DataSource bean。此示例使用 JPA 将记录映射到数据库。以下是 Maven 依赖项
| 组 ID | Artifact ID |
|---|---|
com.h2database |
h2 |
org.springframework.boot |
spring-boot-starter-data-jpa |
org.springframework.boot |
spring-boot-starter-data-rest |
org.springframework.boot |
spring-boot-starter-test |
org.springframework.boot |
spring-boot-starter-actuator |
以下是示例 Java 代码
package demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Arrays;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
CommandLineRunner seed(ReservationRepository rr) {
return args -> Arrays.asList("Phil,Webb", "Josh,Long", "Dave,Syer", "Spencer,Gibb").stream()
.map(s -> s.split(","))
.forEach(namePair -> rr.save(new Reservation(namePair[0], namePair[1])));
}
}
@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}
@Entity
class Reservation {
@Id
@GeneratedValue
private Long id;
private String firstName, lastName;
Reservation() {
}
public Reservation(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
该应用程序可以在本地运行,但现在让我们将其部署到 Cloud Foundry,届时需要告诉它如何连接到为其公开的后端服务(PostgreSQL 实例)。
Cloud Foundry 有许多版本。与 Heroku 类似,您可以运行托管在基于 AWS 的版本上,该版本可通过 Pivotal Web Services 获得。您可以获得免费试用账户。您可以采用 Pivotal 的打包版本Pivotal Cloud Foundry,并在您的数据中心运行它。或者,您也可以使用开源版本并运行它。或者使用 IBM 和 HP 等众多其他实现。无论如何,您将获得一个在后端服务方面行为基本相同的 PaaS。
添加后端服务是一个声明性操作:只需创建一个服务,然后将其绑定。Cloud Foundry 提供了一个市场命令:cf marketplace。对于 80% 的情况,您应该能够从 cf marketplace 输出的选项中进行选择。选择服务后,创建一个实例,如下所示
cf create-service elephantsql turtle postgresql-db
请注意,postgresql-db 是后端服务名称,elephantsql 是服务提供商名称,turtle 是(免费)套餐的计划名称。有了这些信息,您就可以将服务绑定到已部署的应用程序。您可以使用 cf CLI,或者简单地在应用程序的 manifest.yml 文件中声明后端服务依赖项。
以下是我们所有示例的基本 manifest.yml。name 和 host 在不同清单中会有所不同,但本文的示例使用了新创建的 postgresql-db 服务。
---
applications:
- name: simple-backing-services
memory: 512M
instances: 1
host: simple-backing-services-${random-word}
domain: cfapps.io
path: target/simple.jar
services:
- postgresql-db
env:
SPRING_PROFILES_ACTIVE: cloud
DEBUG: "true"
debug: "true"
现在,让我们看看使用后端服务的几种不同方法,以及它们各自的优缺点。
我们至少将使用Spring Boot来处理所有这些示例。Spring Boot 提供了 Spring Boot Actuator 模块。它提供了关于应用程序的非常有用的信息 - 指标、环境转储、所有已定义 bean 的列表等。我们将使用其中一些端点来深入了解在运行 Spring 应用程序时暴露的环境变量和系统属性,以及了解 Spring Boot 运行的配置文件等内容。在您的 Maven 或 Gradle 构建中添加 Spring Boot Actuator。groupId 是 org.springframework.boot,artifactId 是 spring-boot-starter-actuator。如果您使用的是从start.spring.io生成的 Spring Boot 或 spring init CLI 命令,则无需指定版本。
Cloud Foundry Java buildpack 会为您执行自动重新配置。 根据文档
自动重新配置包括三个部分。首先,它将
cloudprofile 添加到 Spring 的活动 profile 列表中。其次,它将 Cloud Foundry 提供的所有属性作为PropertySource暴露在ApplicationContext中。最后,它会重写各种类型的 bean 定义,以便自动连接到应用程序绑定的服务。重写的类型如下
Bean 类型 服务类型 javax.sql.DataSource关系型数据服务(例如 ClearDB、ElephantSQL) org.springframework.amqp.rabbit.connection.ConnectionFactoryRabbitMQ 服务(例如 CloudAMQP) org.springframework.data.mongodb.MongoDbFactoryMongo 服务(例如 MongoLab) org.springframework.data.redis.connection.RedisConnectionFactoryRedis 服务(例如 Redis Cloud) org.springframework.orm.hibernate3.AbstractSessionFactoryBean关系型数据服务(例如 ClearDB、ElephantSQL) org.springframework.orm.hibernate4.LocalSessionFactoryBean关系型数据服务(例如 ClearDB、ElephantSQL) org.springframework.orm.jpa.AbstractEntityManagerFactoryBean关系型数据服务(例如 ClearDB、ElephantSQL)
其效果是,在本地运行 H2 的应用程序将在 Cloud Foundry 上自动运行 PostgreSQL,前提是您有一个后端服务指向已创建并绑定到应用程序的 PostgreSQL 实例,如上所示。这非常方便!如果您的两个目标环境只有 localhost 和 Cloud Foundry,那么这会完美运行。
Environment 属性默认的 Java buildpack(您可以在执行 cf push 时覆盖它,或在 manifest.yml 中声明它)会添加一个 Spring Environment PropertySource,它会注册大量以 cloud. 开头的属性。一旦将应用程序推送到 Cloud Foundry,您就可以通过访问上述应用程序中的 REST /env 端点来查看它们。以下是我应用程序的部分输出
{
...
cloud.services.postgresql-db.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
...
cloud.services.postgresql-db.connection.uri: "postgres://AUSER:[email protected]:5432/AUSER",
cloud.services.postgresql-db.connection.scheme: "postgres",
cloud.services.postgresql.connection.jdbcurl: "jdbc:postgresql://babar.elephantsql.com:5432/AUSER?user=AUSER&password=WOULDNTYOULIKETOKNOW",
cloud.services.postgresql.connection.port: 5432,
cloud.services.postgresql.connection.path: "AUSER",
cloud.application.host: "0.0.0.0",
cloud.services.postgresql-db.connection.password: "******",
cloud.services.postgresql-db.connection.username: "AUSER",
...
cloud.application.application_name: "simple-backing-services",
cloud.application.limits: {
mem: 512,
disk: 1024,
fds: 16384
},
cloud.services.postgresql-db.id: "postgresql-db",
cloud.application.application_uris: [
"simple-backing-services-fattiest-teniafuge.cfapps.io",
"simple-backing-services-unmummifying-prehnite.cfapps.io"
],
cloud.application.instance_index: 0,
...
}
您可以像使用其他任何属性一样在 Spring 中使用这些属性。它们也非常方便,因为它们不仅提供了一个相当标准的 Heroku 式连接 URI(cloud.services.postgresql-db.connection.uri),还提供了一个可以直接在 JDBC 上下文中使用的 URI,cloud.services.postgresql.connection.jdbcurl。只要您使用此(或其分支)buildpack,您就可以从这些属性中受益。
Cloud Foundry 将所有这些信息公开为标准的、与语言和技术无关的环境变量(VCAP_SERVICES 和 VCAP_APPLICATION)。理论上,您应该能够为任何 Cloud Foundry 实现编写应用程序并定位这些变量。Spring Boot 还为这些变量提供了自动配置,无论您是否使用上述 Java buildpack,这种方法都有效。
Spring Boot 将这些环境变量映射到一组可通过 Spring Environment 抽象访问的属性。以下是 Spring Boot 公开的 VCAP_* 属性的一些示例输出,同样来自 /env
{
...
vcap.application.start: "2015-01-27 09:58:13 +0000",
vcap.application.application_version: "9e6ba76e-039f-4585-9573-8efa9f7e9b7e",
vcap.application.application_uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
vcap.application.uris: "simple-backing-services-fattiest-teniafuge.cfapps.io,simple-backing-services-grottoed-distillment.cfapps.io,...",
vcap.application.space_name: "joshlong",
vcap.application.started_at: "2015-01-27 09:58:13 +0000",
vcap.services.postgresql-db.tags: "Data Stores,Data Store,postgresql,relational,New Product",
vcap.services.postgresql-db.credentials.uri: "postgres://AUSER:[email protected]:5432/hqsugvxo",
vcap.services.postgresql-db.tags[1]: "Data Store",
vcap.services.postgresql-db.tags[4]: "New Product",
vcap.application.application_name: "simple-backing-services",
vcap.application.name: "simple-backing-services",
vcap.application.uris[2]: "simple-backing-services-detersive-sterigma.cfapps.io",
...
}
我倾向于结合使用这两种方法。Spring Boot 属性很方便,因为它们提供了索引属性。vcap.application.application_uris[2] 提供了一种索引应用程序可能路由的数组的方式。这对于告诉正在运行的应用程序其外部可寻址 URI 非常理想,例如,如果它需要在启动时建立回调,而没有任何请求进来。它还提供了等效的技术无关 URI,但没有 JDBC 特定连接字符串。因此,我将两者都使用。这种方法很方便,尤其是在 Spring Boot 中,因为我可以在配置中显式设置属性(如 spring.datasource.*),以指示 Spring Boot 如何进行设置。这对于明确性很有用,或者如果我有多个同类型的后端服务(如 JDBC javax.sql.DataSource)绑定到同一个应用程序。在这种情况下,buildpack 将不知道该怎么做,因此您需要明确并区分要注入哪个后端服务引用以及注入到哪里。
Spring Boot 默认加载 src/main/resources/application.(properties,yml)。它还会加载形式为 src/main/resources/application-PROFILE.yml 的特定于 profile 的属性文件,其中 PROFILE 是活动 Spring profile 的名称。之前,我们看到我们的 manifest.yml 通过设置环境变量来激活 cloud profile。因此,假设您希望有一个配置仅在 cloud profile 中运行时激活,而另一个配置在没有特定 profile 激活时激活 - 这称为 default profile。您可以创建三个文件:src/main/resources/application-cloud.(properties,yml),它将在 cloud profile 激活时激活;src/main/resources/application-default.(properties,yml),它将在没有其他 profile 被特别激活时激活;以及 src/main/resources/application.(properties,yml),它将在所有情况下被激活,无论如何。
一个示例 src/main/resources/application.properties
spring.jpa.generate-ddl=true
一个示例 src/main/resources/application-cloud.properties
spring.datasource.url=${cloud.services.postgresql-db.connection.jdbcurl}
一个示例 src/main/resources/application-default.properties
# empty in this case because I rely on the embedded H2 instance being created
# though you could point it to another, local,
# PostgresSQL instancefor dev workstation configuration
到目前为止,所有这些选项都利用了 Environment 抽象。毫无疑问,它们比手动解析 VCAP_SERVICES 变量中的 JSON 结构要简单得多,但我们可以做得更好。正如Spring Cloud Connectors 项目的文档中所述
Spring Cloud 为运行在云平台上的基于 JVM 的应用程序提供了一个简单的抽象,用于在运行时发现绑定的服务和部署信息,并支持将发现的服务注册为 Spring bean。它基于插件模型,因此相同的已编译应用程序可以在本地或多个云上部署,并且它支持通过 Java SPI 进行自定义服务定义。
让我们看一个修改后的示例
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.sql.DataSource;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Configuration
@Profile("cloud")
public static class DataSourceConfig extends AbstractCloudConfig {
@Bean
DataSource reservationsPostgreSqlDb() {
return connectionFactory().dataSource("postgresql-db");
}
}
}
@RepositoryRestResource
interface ReservationRepository extends JpaRepository<Reservation, Long> {
}
@Entity
class Reservation {
@Id
@GeneratedValue
private Long id;
public Long getId() {
return id;
}
private String firstName, lastName;
Reservation() {
}
public Reservation(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
我们不使用属性和 Spring Boot 来配置 javax.sql.DataSource,而是显式地创建一个正确类型的对象工厂。对于其他后端服务,如MongoDB、Redis、SendGrid等,也有其他方法,您可以轻松提供自己的方法。我们正在使用 Cloud Foundry 的 Spring Cloud Connectors 插件,但没有理由不能使用 Heroku 的 Spring Cloud Connectors 插件,或者本地应用程序的基于属性的替代方案。这样,您的应用程序在不同环境中的行为将完全相同,只有外部配置有所不同。
@Bean到目前为止,我们一直依赖平台或框架提供的常识性默认设置,但您不必放弃任何控制。例如,您可以显式地在 XML 或 Java 配置中定义一个 bean,使用环境中的值。如果您的应用程序想使用自定义连接池或以其他方式自定义后端服务的配置,您可能会这样做。如果您尝试使用的后端服务没有自动支持,您也可能会这样做。
@Bean
@Profile("cloud")
DataSource dataSource(
@Value("${cloud.services.postgresql-db.connection.jdbcurl}") String jdbcUrl) {
try {
return new SimpleDriverDataSource(
org.postgresql.Driver.class.newInstance() , jdbcUrl);
}
catch (Exception e) {
throw new RuntimeException(e) ;
}
}
到目前为止,我们只研究了如何使用 Cloud Foundry 公开的服务。如果您想使用 PaaS 之外的服务,足够简单,可以使用Cloud Foundry 的用户提供的服务将其视为任何其他后端服务。这种机制只是一个花哨的方式,用于告诉 Cloud Foundry 您希望应用程序与之通信的自定义服务的定位符和凭据信息。完成此操作后,Cloud Foundry 应用程序和服务就可以像往常一样将该服务绑定到其应用程序并使用它。用户提供的服务非常适合像固定的 Oracle 实例这样的服务,您不打算让 Cloud Foundry 管理它们。Cloud Foundry 不会添加新实例,也不会删除它们,它不控制授权。
如果您希望 Cloud Foundry 管理服务,您需要使用服务代理 API将其适配到 Cloud Foundry。当您在自己的环境中部署 Cloud Foundry 时,这一点更为重要,并且需要管理员权限(例如,在托管的 Pivotal Cloud Foundry 中您将没有这些权限)。服务代理 API 是一组 Cloud Foundry 需要了解的已知 REST 回调。实现自己的自定义服务代理非常简单,甚至还有一个方便的基于 Spring Boot 的项目和相应的示例。
您也可以查看示例!