Spring Boot微服务中的十二因子方法论(12Factor) - Baeldung


在本教程中,我们将了解了解如何在Spring Boot的帮助下应用十二因子方法开发微服务。

什么是十二因子方法论?
十二因子方法论是一组十二种最佳实践,用于开发开发为作为服务运行的应用程序。该文件最初是由Heroku在2011年为在其云平台上部署为服务的应用程序起草的。随着时间的推移,事实证明,这对于任何软件即服务(SaaS)开发都是通用的。
那么,我们所说的软件即服务是什么意思?传统上,我们设计,开发,部署和维护软件解决方案以从中获取业务价值。但是,我们不必亲自自己参与这个过程构建,可以订阅此类业务服务产品,此类服务产品就是我们所说的软件即服务。
尽管软件即服务对它所开发的体系结构没有任何限制;采用一些最佳做法非常有用。
如果我们将软件设计为模块化,可移植且可在现代云平台上扩展,则该软件非常适合我们的服务产品。这就是十二因子方法的作用所在。我们将在本教程的后面部分看到它们的实际应用。

使用Spring Boot的微服务
微服务是一种架构风格,可以将软件开发为松散耦合的服务。这里的关键要求是服务应该围绕业务域边界进行组织。这通常是最难识别的部分。
而且,这里的服务对其数据拥有唯一的权限,并将操作公开给其他服务。服务之间的通信通常通过轻量级协议(例如HTTP)进行。这导致可独立部署和可扩展的服务。
现在,微服务架构和软件即服务不再相互依赖。但是,不难理解,在开发软件即服务时,利用微服务架构是非常有益的。它有助于实现我们前面讨论的许多目标,例如模块化和可伸缩性。
Spring Boot是基于Spring的应用程序框架,它消除了与开发企业应用程序相关的许多样板。它为我们提供了一个具有高度针对性但又灵活的平台来开发微服务。在本教程中,我们将利用Spring Boot通过十二因素方法来提供微服务。

应用十二因子方法
我们需要一个简单的服务来记录和查询我们观看的电影,这是具有数据存储和REST端点的相当简单和标准的微服务。我们需要定义一个模型,该模型也将映射到持久性:

@Entity
public class Movie {
    @Id
    private Long id;
    private String title;
    private String year;
    private String rating;
    // getters and setters
}

我们已经定义了一个具有ID和其他一些属性的JPA实体。现在让我们看一下REST控制器的外观:

@RestController
public class MovieController {
  
    @Autowired
    private MovieRepository movieRepository;
    @GetMapping("/movies")
    public List<Movie> retrieveAllStudents() {
        return movieRepository.findAll();
    }
 
    @GetMapping(
"/movies/{id}")
    public Movie retrieveStudent(@PathVariable Long id) {
        return movieRepository.findById(id).get();
    }
 
    @PostMapping(
"/movies")
    public Long createStudent(@RequestBody Movie movie) {
        return movieRepository.save(movie).getId();
    }
}

这涵盖了我们简单服务的基础。在下面的小节中,我们将讨论本应用程序的其余部分,以讨论如何实现十二因子方法。

1.代码库
十二因子应用程序的第一个最佳实践是在版本控制系统中对其进行跟踪。Git是当今使用最广泛的版本控制系统,几乎无处不在。该原则指出,应在单个代码存储库中跟踪某个应用程序,并且不得与任何其他应用程序共享该存储库。
Spring Boot提供了许多引导应用程序的便捷方法,包括命令行工具和Web界面。生成引导应用程序后,可以将其转换为git存储库:

git init

此命令应从应用程序的根目录运行。此阶段的应用程序已经包含一个.gitignore文件,该文件有效地限制了生成文件的版本控制。因此,我们可以立即创建一个初始提交:
git add .
git commit -m "Adding the bootstrap of the application."

最后,如果需要,我们可以添加一个远程并将提交提交到该远程(这不是严格的要求):
git remote add origin https://github.com/<username>/12-factor-app.git
git push -u origin master

2. 依赖
接下来,十二因子应用程序应始终显式声明其所有依赖项。我们应该使用依赖声明清单来实现。Java有多个依赖管理工具,例如Maven和Gradle。我们可以使用其中之一来实现这一目标。
因此,我们的简单应用程序依赖于一些外部库,例如方便REST API和连接数据库的库。让我们看看如何使用Maven声明性地定义它们。
Maven要求我们在XML文件(通常称为项目对象模型(POM))中描述项目的依赖项:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

尽管这看起来很简单,但是这些依赖项通常还具有其他传递性依赖项。这在一定程度上使其复杂化,但可以帮助我们实现目标。现在,我们的应用程序没有直接依赖关系,因此没有明确描述。

3.配置
一个应用程序通常具有许多配置,其中一些配置可能因部署而异,而其他配置则保持不变。
在我们的示例中,我们有一个持久数据库。我们需要数据库的地址和凭据才能连接。这在部署之间最有可能改变。
十二个因子的应用程序应该外部化所有因部署而异的配置。建议在此配置中使用环境变量。这导致配置和代码的清晰分离。
Spring提供了一个配置文件,我们可以在其中声明此类配置并将其附加到环境变量:

spring.datasource.url=jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/movies
spring.datasource.username=${MYSQL_USER}
spring.datasource.password=${MYSQL_PASSWORD}

在这里,我们将数据库URL和凭据定义为配置,并映射了要从环境变量中选取的实际值。
在Windows上,我们可以在启动应用程序之前设置环境变量:
set MYSQL_HOST=localhost
set MYSQL_PORT=3306
set MYSQL_USER=movies
set MYSQL_PASSWORD=password

我们可以使用AnsibleChef等配置管理工具来自动执行此过程。

4.支持服务
支持服务是应用程序依赖其进行操作的服务。例如数据库或消息代理。十二要素应用程序应将所有此类支持服务视为附加资源。这实际上意味着,它不需要更改任何代码即可交换兼容的支持服务。唯一的变化应该是配置。
在我们的应用程序中,我们使用MySQL作为提供持久性的支持服务。
Spring JPA使代码与实际的数据库提供程序完全无关。我们只需要定义一个存储库即可提供所有标准操作:

@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}

如我们所见,这并不直接依赖于MySQL。Spring在类路径上检测MySQL驱动程序,并动态提供该接口的MySQL特定实现。而且,它直接从配置中提取其他细节。
因此,如果必须从MySQL更改为Oracle,我们要做的就是替换依赖项中的驱动程序并替换配置。

5.构建、发布和运行
十二要素方法严格将将代码库转换为正在运行的应用程序的过程分为三个不同的阶段:

  • 构建阶段:在这里,我们获取代码库,执行静态和动态检查,然后生成可执行文件包,例如JAR。使用Maven之类的工具,这很简单:
    mvn clean compile test package
  • 发布阶段:这是我们获取可执行包并将其与正确的配置结合在一起的阶段。在这里,我们可以将Packer与类似Ansible的供应器一起使用来创建Docker镜像:
    packer build application.json
  • 运行阶段:最后,这是我们在目标执行环境中运行应用程序的阶段。如果我们使用Docker作为发布应用程序的容器,则运行该应用程序可能非常简单:
    docker run --name <container_id> -it <image_id>

最后,我们不必手动执行这些步骤。这就是Jenkins 的声明式管道非常方便的地方。

6. 处理进程
一个十二因子应用程序将作为无状态进程在执行环境中运行。换句话说,它们不能在请求之间本地存储持久状态。它们可能会生成需要存储在一个或多个有状态支持服务中的持久性数据。
在我们的示例中,我们暴露了多个端点。这些端点中的任何一个上的请求完全独立于在其之前进行的任何请求。例如,如果我们跟踪内存中的用户请求并使用该信息来满足将来的请求,则它违反了十二因素应用程序。
因此,十二要素应用程序不会像粘性会话httpsession那样对应用程序产生任何限制。这使得此类应用程序具有高度的可移植性和可扩展性。在提供自动扩展的云执行环境中,这是应用程序非常理想的行为。

7.端口绑定
Java中的传统Web应用程序被开发为WAR或Web存档。这通常是具有相关性的Servlet的集合,并且它期望像Tomcat这样的一致的容器运行时。相反,十二要素应用程序不希望有此类运行时依赖项。它是完全独立的,只需要像Java这样的执行运行时。
在我们的案例中,我们使用Spring Boot开发了一个应用程序。除了许多其他优点之外,Spring Boot还为我们提供了默认的嵌入式应用程序服务器。因此,我们早先使用Maven生成的JAR仅具有兼容的Java运行时就完全能够在任何环境中执行:

java -jar application.jar

在这里,我们的简单应用程序通过HTTP绑定将其端点公开给特定端口,例如8080。像上面一样启动应用程序后,应该可以访问导出的服务,例如HTTP。
应用程序可以通过绑定到多个端口来导出FTP或WebSocket之类的多个服务。

8. 并发
Java提供了Thread作为经典模型来处理应用程序中的并发性。线程就像轻量级进程,代表程序中的多个执行路径。线程功能强大,但是在帮助应用程序扩展方面有一定的限制。
十二要素方法论建议应用程序依靠进程进行扩展。这实际上意味着应将应用程序设计为在多个进程之间分配工作负载。但是,各个进程可以在内部自由利用诸如Thread的并发模型。
Java应用程序在启动时会获得绑定到底层JVM的单个进程。我们实际上需要的是一种启动应用程序的多个实例并在它们之间进行智能负载分配的方法。由于我们已经将应用程序打包为Docker容器,因此Kubernetes是这种编排的自然选择。

9. Disposability可处理性/幂等性
可以有意或通过意外事件关闭应用程序进程。无论哪种情况,都应该由十二个因素的应用程序优雅地处理。换句话说,关闭进程应完全是一次性的,没有任何不希望的副作用。此外,进程应迅速启动
例如,在我们的应用程序中,端点之一是为电影创建新的数据库记录。现在,处理此类请求的应用程序可能会意外崩溃。但是,这不应影响应用程序的状态。当客户端再次发送相同的请求时,它不应导致重复的记录。
总之,应用程序应公开幂等性的服务。这是用于云部署的服务的另一个非常理想的属性。这提供了随时停止,移动或旋转新服务的灵活性,而无需任何其他考虑。

10. Dev/Prod完整性
通常应用程序是在本地计算机上开发,然在其他一些环境上进行测试并最终部署到生产环境中。这些环境通常是不同的情况。例如,开发团队在Windows计算机上工作,而生产部署在Linux计算机上进行。
十二要素方法论建议将开发和生产环境之间的差距保持在最小。这些差距可能是由于开发周期长,涉及的团队不同或使用的技术堆栈不同而导致的。
现在,诸如Spring Boot和Docker之类的技术会在很大程度上弥补这一差距。无论我们在何处运行,容器化应用程序的行为都应相同。我们还必须使用相同的支持服务,例如数据库。
此外,我们应该有正确的流程,例如持续集成和交付,以促进进一步弥合这一差距。

11. 日志
日志是应用程序在其生命周期内生成的基本数据。他们提供了有关应用程序工作的宝贵见解。通常,应用程序可以在多个级别上生成具有不同详细信息的日志,并以多种不同格式输出。
但是,有十二个因素的应用程序将自身与日志生成及其处理分开。对于这样的应用程序,日志只不过是按时间顺序排列的事件流。它只是将这些事件写到执行环境的标准输出中。此类流的捕获,存储,管理和存档应由执行环境处理。
为此目的,我们可以使用多种工具。首先,我们可以使用SLF4J在我们的应用程序中抽象地处理日志。此外,我们可以使用Fluentd之类的工具来收集来自应用程序和支持服务的日志流。
我们可以将其输入到Elasticsearch中进行存储和索引。最后,我们可以为Kibana中的可视化生成有意义的仪表板。

12. 管理例程
通常,我们需要根据应用程序状态执行一些一次性任务或例行程序。例如,修复不良记录。现在,有多种方法可以实现这一目标。由于我们可能并不经常需要它,因此我们可以编写一个小脚本来与其他环境分开运行。
现在,十二因素方法强烈建议将此类管理脚本与应用程序代码库一起保留。这样,它应遵循与应用于主应用程序代码库相同的原则。还建议使用执行环境的内置REPL工具在生产服务器上运行此类脚本。
在我们的示例中,我们如何搜索到目前为止已经看过的电影?虽然我们可以使用我们的小端点,但这似乎是不切实际的。我们需要一个脚本来执行一次性加载。我们可以编写一个小的Java函数来从文件中读取电影列表并将其批量保存到数据库中。
此外,我们可以使用与Java运行时集成的Groovy来启动此类过程。

实际应用
因此,现在我们已经看到了十二因素方法建议的所有因素。将应用程序开发为包含十二个要素的应用程序无疑具有其优势,尤其是当我们希望将它们作为服务部署在云上时。但是,像所有其他准则,框架,模式一样,我们必须要问,这是灵丹妙药吗?
坦白说,没有任何软件设计和开发方法论可以说是万灵丹。十二因素方法论也不例外。尽管其中一些因素非常直观,并且很可能我们已经在做这些,但其他因素可能不适用于我们。在我们的目标背景下评估这些因素,然后进行明智的选择至关重要。
重要的是要注意,所有这些因素都可以帮助我们开发模块化,独立,可移植,可伸缩和可观察的应用程序。根据应用程序的不同,我们也许可以通过其他方式更好地实现它们。也没有必要将所有因素放在一起,即使采用其中一些因素也可能使我们比以前更好。
最后,这些因素非常简单而优雅。在我们要求应用程序具有更高的吞吐量和更低的延迟而几乎没有停机和故障的时代,它们显得尤为重要。采用这些因素为我们从一开始就提供了正确的开始。结合微服务体系结构和应用程序容器化,它们似乎是正确的选择。