配置即代码:Pkl语言与Spring Boot集成

在本教程中,我们将学习如何使用Pkl(发音为 Pickle)(一种配置即代码语言)在 Spring Boot 应用程序中定义配置。

传统上,我们可能在 YAML、JSON 或基于键值的属性文件中定义应用程序配置。但是,这些都是静态格式,验证属性具有挑战性。此外,随着配置属性数量的增加,定义更复杂的分层配置变得越来越困难。

因此,通过 Pkl、 HCL(Hashicorp 配置语言)和Dhal 配置语言等特殊语言使用配置即代码(CaC)可以帮助我们克服静态属性格式文件的挑战。

在本文中,我们了解了使用 Pickle 文件定义 Spring Boot 应用程序配置的好处和方法。但是,掌握 Pkl 语言概念对于设计可扩展且可维护的复杂配置同样重要。此外,了解 Spring 框架将外部属性绑定到 POJO 的功能也是必不可少的。

Pkl 简介
配置即代码是一个流行的概念,它提倡“不要重复自己”(DRY)原则并有助于简化配置管理。它采用声明式编码风格,提供了一种结构化且高效的方法来定义配置模板。它还提高了配置的可读性。

Pkl 有助于定义各种元素,例如对象类型、集合、映射和各种原始数据类型。这种灵活性使该语言具有可扩展性,并有助于清晰简洁地轻松建模复杂配置。此外,其类型和验证机制有助于在应用程序部署之前捕获配置错误。

此外,Pkl 还提供了出色的工具支持,便于轻松采用。它具有用于生成不同语言(如 Java、Kotlin、Go 和 Swift)代码的工具。这对于在这些编程语言构建的应用程序中嵌入和读取 Pickle 配置至关重要。

此外,IntelliJ和VS Code等 IDE具有插件,可帮助使用 Pkl 开发配置。Pkl 还附带一个名为pkl的 CLI ,可帮助评估 pickle 模块。它的一个功能是将.pkl配置转换为 JSON、YAML 和 XML 格式。

Spring 配置 Java Bean 绑定
定义 Spring 配置的最常见方法是创建属性文件并使用@Value注释注入它们。然而,这通常会导致过多的样板代码。

值得庆幸的是,Spring 框架借助@ConfigurationProperties注释简化了这个过程,实现了配置与 Java bean 的无缝绑定。

但是,使用这种方法,我们仍然必须手动创建 Java bean,并对成员字段进行必要的验证。因此,配置漂移很难检测,并且经常会导致应用程序中出现严重错误。因此,像 Pkl 这样具有自动 Java 代码生成支持的配置定义语言是集成我们的应用程序配置的更可靠的方法。

Spring Boot 集成
Pkl 提供库来从 Pickle 文件生成 POJO。稍后在运行时,pkl-spring 库可以将 Pickle 配置填充到 POJO 中。让我们了解如何将其与 Spring Boot 应用程序集成。

1. 先决条件
首先,我们将包含pkl-spring库的Maven 依赖项:

<dependency>
    <groupId>org.pkl-lang</groupId>
    <artifactId>pkl-spring</artifactId>
    <version>0.16.0</version>
</dependency>

此外,我们将使用exec-maven-plugin从 Pickle 配置文件生成 POJO:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>gen-pkl-java-bind</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>java</goal>
            </goals>
            <configuration>
                <mainClass>org.pkl.codegen.java.Main</mainClass>
                <includeProjectDependencies>false</includeProjectDependencies>
                <additionalClasspathElements>
                    <classpathElement>${pkl-tools-filepath}/pkl-tools-0.27.2.jar</classpathElement>
                </additionalClasspathElements>
                <arguments>
                    <argument>${project.basedir}/src/main/resources/ToolIntegrationTemplate.pkl</argument>
                    <argument>-o</argument>
                    <argument>${project.build.directory}/generated-sources</argument>
                    <argument>--generate-spring-boot</argument>
                    <argument>--generate-getters</argument>
                </arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

Maven 插件执行 Java 代码生成器工具pkl-tools:

java -cp pkl-tools-0.27.2.jar org.pkl.codegen.java.Main \
src/main/resources/ToolIntegration.pkl \ 
-o ${project.basedir}/src/main --generate-spring-boot

假设有一个Pickle文件ToolIntegrationTemplate.pkl,该工具会在target/generated-sources文件夹中生成与Pickle文件对应的源代码 。

–generate-spring-boot参数将必要的 Spring Boot 类级别@ConfigurationProperties注释包含在 Java bean 中。此外,–generate-getters参数将属性键声明为私有。

对于 Gradle 用户来说幸运的是,pkl-gladle 插件提供了对生成 POJO 的开箱即用支持。

2. 在 Pkl 中创建配置
让我们考虑一个数据集成应用程序,它从 Git 和 Jira 等源系统获取数据并将其发送到其他目标系统。

让我们首先定义 Pickle 模板ToolIntegrationTemplate.pkl来保存连接属性:

module com.baeldung.spring.pkl.ToolIntegrationProperties
class Connection {
  url:String
  name:String
  credential:Credential
}
class Credential {
  user:String
  password:String
}
gitConnection: Connection
jiraConnection: Connection

总的来说,我们定义了两个 Pkl 类Connection和Credential,并声明了两个Connection类型的对象gitConnection和jiraConnection。

接下来我们在application.pkl文件中定义gitConnection和jiraConnection:

amends "ToolIntegrationTemplate.pkl"
gitConnection {
  url =
"https://api.github.com"
  name =
"GitHub"
  credential {
    user =
"gituser"
    password = read(
"env:GITHUB_PASSWORD")
  }
}
jiraConnection {
  url =
"https://jira.atlassian.com"
  name =
"Jira"
  credential {
    user =
"jirauser"
    password = read(
"env:JIRA_PASSWORD")
  }
}

我们已经用数据源的连接属性填充了模板。值得注意的是,我们没有在配置文件中硬编码密码。相反,我们使用 Pkl read()函数从环境变量中检索密码,最好是一种安全的方法。

3.将 Pkl 模板转换为 POJO
在 Pickle 文件中定义配置后,可以通过执行pom.xml文件中定义的Maven 目标exec:java来生成 POJO:

mvn exec:java@gen-pkl-java-bind

最终,pkl-tools库生成一个POJO和一个属性文件:

Pkl 代码生成器
ToolIntegrationProperties类由两个内部类Connection和Credential组成。它还带有@ConfigurationProperties注释,可帮助将application.pkl中的属性与ToolIntegrationProperties对象绑定。

4. 将 Pickle 配置绑定到 POJO
我们先定义与源系统集成的服务类JiraService和GitHubService :

public class JiraService {
    private final ToolIntegrationProperties.Connection jiraConnection;
    public JiraService(ToolIntegrationProperties.Connection connection) {
        this.jiraConnection = connection;
    }
   // ...methods getting data from Jira
}
public class GitHubService {
    private final ToolIntegrationProperties.Connection gitConnection;
    public GitHubService(ToolIntegrationProperties.Connection connection) {
        this.gitConnection = connection;
    }
   
// ...methods getting data from GitHub
}

这两个服务都有以ToolIntegrationProperties作为参数的构造函数。稍后,参数中的连接详细信息将分配给 ToolIntegrationProperties.Connection 类型的类级属性。

接下来,我们将定义一个配置类,实例化服务并将其声明为 Spring 框架 bean:

@Configuration
public class ToolConfiguration {
    @Bean
    public GitHubService getGitHubService(ToolIntegrationProperties toolIntegration) {
        return new GitHubService(toolIntegration.getGitConnection());
    }
    @Bean
    public JiraService getJiraService(ToolIntegrationProperties toolIntegration) {
        return new JiraService(toolIntegration.getJiraConnection());
    }
}

在应用程序启动时,Spring 框架将方法参数与application.pkl文件中的配置绑定在一起。最终,通过调用服务构造函数来实例化 bean。

5. 服务注入和执行
最后,我们可以在其他 Spring 组件中使用@Autowired注释注入服务 Bean。

使用@SpringBootTest,我们将验证应用程序是否可以从 Pickle 文件加载 Jira 连接配置:

@SpringBootTest
public class SpringPklUnitTest {
    @Autowired
    private JiraService jiraService;
    @Test
    void whenJiraConfigsDefined_thenLoadFromApplicationPklFile() {
        ToolIntegrationProperties.Connection jiraConnection = jiraService.getJiraConnection();
        ToolIntegrationProperties.Credential jiraCredential = jiraConnection.getCredential();
        assertAll(
          () -> assertEquals("Jira", jiraConnection.getName()),
          () -> assertEquals(
"https://jira.atlassian.com", jiraConnection.getUrl()),
          () -> assertEquals(
"jirauser", jiraCredential.getUser()),
          () -> assertEquals(
"jirapassword", jiraCredential.getPassword()),
          () -> assertEquals(
"Reading issues from Jira URL https://jira.atlassian.com"
            jiraService.readIssues())
        );
    }
}

为了演示,我们通过pom.xml文件在环境变量中设置了 Git 和 JIRA 密码。运行测试后,我们确认 Jira 连接和凭据详细信息与application.pkl文件中定义的值匹配。最后,我们还假设如果正确实现,  JiraService#readIssues()将成功执行。目前,该方法仅返回一个虚拟字符串。

类似地,让我们检查应用程序启动后是否从 Pickle 文件加载 GitHub 连接配置:

@SpringBootTest
public class SpringPklUnitTest {
    @Autowired
    private GitHubService gitHubService;
    @Test
    void whenGitHubConfigsDefined_thenLoadFromApplicationPklFile() {
        ToolIntegrationProperties.Connection gitHubConnection = gitHubService.getGitConnection();
        ToolIntegrationProperties.Credential gitHubCredential = gitHubConnection.getCredential();
        assertAll(
          () -> assertEquals("GitHub", gitHubConnection.getName()),
          () -> assertEquals(
"https://api.github.com", gitHubConnection.getUrl())
          () -> assertEquals(
"gituser", gitHubCredential.getUser()),
          () -> assertEquals(
"gitpassword", gitHubCredential.getPassword()),
          () -> assertEquals(
"Reading issues from GitHub URL https://api.github.com"
            gitHubService.readIssues())
        );
    }
}

正如预期的那样,这次 GitHub 连接和凭据详细信息也与application.pkl文件中定义的值相匹配。因此,有了正确的连接详细信息,我们可以推测GitHubServiceService#readIssues()如果正确实现,将连接到 GitHub 并获取问题。与JiraService#readIssues()一样,该方法目前仅返回一个虚拟字符串。