抢先一步
VMware 提供培训和认证,助您快速提升技能。
了解更多《12 要素应用宣言》详细讨论了后端服务。基本上,后端服务是应用程序用来完成其工作的任何联网附加服务。这可能是一个 MongoDB 实例、PostgreSQL 数据库、像亚马逊 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(例如,来自后端服务的内容)的初始化和获取逻辑与其使用位置分离。我们可以使用 Spring 来编写考虑javax.sql.DataSources
的代码,然后编写配置以根据应用程序从一个环境移动到另一个环境,从正确的上下文和配置中获取该DataSource
。
下一版本的 Cloud Foundry(在新闻报道中称为Diego)下的运行时是首选 Docker 和原生 Docker。当然,Docker 使得容器化应用程序变得很容易,并且容器化应用程序与外部世界之间的接口有意保持最小化,同样是为了促进可移植应用程序。Docker 镜像中的关键输入之一是,你猜对了,环境变量!我们的朋友 Chris Richardson 已经写了几篇关于打包和构建基于 Spring Boot 的 Docker 镜像以及关于建立后端服务的不错的文章。我们不会在这篇文章中讨论 Docker(尽管敬请期待!),但重要的是要记住:环境变量是外部化后端服务连接信息的一种简单且灵活的方法。
DataSource
通信的简单 Spring Boot 应用程序这是一个简单的 Spring Boot 应用程序,它插入并公开来自DataSource
bean 的一些记录,Spring Boot 将自动为我们创建这些记录,因为我们在 CLASSPATH 上有 H2 嵌入式数据库驱动程序。如果 Spring Boot 没有检测到类型为javax.sql.DataSource
的 bean,并且确实检测到嵌入式数据库驱动程序(H2、Derby、HSQL),它将自动创建一个嵌入式javax.sql.DataSource
bean。此示例使用 JPA 将记录映射到数据库。以下是 Maven 依赖项
组 ID | 构件 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 init
CLI 命令生成的 Spring Boot,则无需指定版本。
Cloud Foundry Java 构建包为你执行自动重新配置。根据文档
自动重新配置包括三个部分。首先,它将
cloud
配置文件添加到 Spring 的活动配置文件列表中。其次,它将 Cloud Foundry 提供的所有属性作为ApplicationContext
中的PropertySource
公开。最后,它重写各种类型的 bean 定义,以便自动连接到绑定到应用程序的服务。被重写的类型如下所示
Bean 类型 服务类型 javax.sql.DataSource
关系型数据库服务 (例如 ClearDB, ElephantSQL) org.springframework.amqp.rabbit.connection.ConnectionFactory
RabbitMQ 服务 (例如 CloudAMQP) org.springframework.data.mongodb.MongoDbFactory
MongoDB 服务 (例如 MongoLab) org.springframework.data.redis.connection.RedisConnectionFactory
Redis 服务 (例如 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 实例的后端服务,就像我们上面所做的那样。这非常方便!如果您的只有两个目标是本地主机和 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
是活动 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 的项目和相应的示例。
也请查看这些示例!