Integrating Pkl With Spring Boot
1. 개요
이 튜토리얼에서는 Pkl (픽클이라고 발음함)이라는 구성 코드 언어를 사용하여 Spring Boot 애플리케이션의 구성을 정의하는 방법을 배웁니다.
전통적으로 우리는 애플리케이션 구성을 YAML, JSON 또는 키-값 기반 속성 파일로 정의할 수 있습니다. 그러나 이러한 형식은 정적이며 속성을 검증하는 것이 어려운 문제를 안고 있습니다. 또한, 구성 속성의 수가 증가함에 따라 더 복잡한 계층적 구성을 정의하는 것이 점점 더 어려워집니다.
따라서 Pkl, HCL (Hashicorp 구성 언어), Dhal 구성 언어와 같은 특별한 언어를 이용해 코드로 구성(Configuraton-as-Code, CaC)을 사용하는 것은 정적 속성 형식 파일의 문제를 극복하는 데 도움이 될 수 있습니다.
2. Pkl 소개
Configuration-as-Code는 반복하지 말라는 원칙(DRY)을 촉진하고 구성 관리의 효율성을 높이는 대중적인 개념입니다. 선언적 코딩 스타일을 채택하여 구성 템플릿을 정의하는 데 구조적이고 효율적인 접근 방식을 제공합니다. 이것은 구성 가독성을 향상시킵니다.
Pkl은 객체 유형, 집합, 맵 및 다양한 기본 데이터 유형과 같은 다양한 요소를 정의하는 데 도움을 줍니다. 이러한 유연성은 언어가 확장 가능하게 만들고 복잡한 구성을 쉽게 모델링할 수 있도록 도와줍니다. 또한, Pkl의 유형 및 검증 메커니즘은 애플리케이션 배포 전에 구성 오류를 감지하는 데 도움을 줍니다.
Pkl은 쉬운 채택을 위한 훌륭한 툴 지원도 제공합니다. Java, Kotlin, Go, Swift와 같은 다양한 언어로 코드를 생성하기 위한 툴이 있습니다. 이는 이러한 프로그래밍 언어로 구축된 애플리케이션에서 픽클 구성을 포함하고 읽는 데 필수적입니다.
또한, IntelliJ와 VS Code와 같은 IDE는 Pkl을 사용하여 구성을 개발하는 데 도움이 되는 플러그인을 제공합니다. Pkl은 픽클 모듈을 평가하는 데 도움을 주는 CLI인 pkl와 함께 제공되며, .pkl 구성을 JSON, YAML 및 XML 형식으로 변환하는 기능을 가지고 있습니다.
3. Spring 구성 Java Bean 바인딩
Spring 구성 정의의 가장 일반적인 접근 방식은 속성 파일을 만들고 @Value 주석을 사용하여 주입하는 것입니다. 그러나 이로 인해 지나치게 많은 보일러플레이트 코드가 발생하는 경우가 많습니다.
다행히도 Spring 프레임워크는 @ConfigurationProperties 주석의 도움으로 이 프로세스를 단순화하여 구성과 Java 빈 간의 원활한 바인딩을 가능하게 합니다.
하지만 이 방법을 사용할 경우, 필요한 검증을 포함하여 Java 빈을 여전히 수동으로 생성해야 합니다. 이로 인해 구성 드리프트를 감지하기 어려워지며, 종종 애플리케이션의 치명적인 버그로 이어질 수 있습니다. 따라서 Pkl과 같은 구성 정의 언어와 자동 Java 코드 생성 지원을 사용하는 것이 애플리케이션 구성을 통합하는 더 강력한 방법입니다.
4. Spring Boot 통합
Pkl은 픽클 파일에서 POJO를 생성하기 위한 라이브러리를 제공합니다. 런타임 시 pkl-spring 라이브러리가 픽클 구성을 POJO로 채울 수 있습니다. Spring Boot 애플리케이션과 어떻게 통합할 수 있는지 배워보겠습니다.
4.1. 전제 조건
우선, pkl-spring 라이브러리에 대한 Maven 의존성을 포함하겠습니다:
<dependency>
<groupId>org.pkl-lang</groupId>
<artifactId>pkl-spring</artifactId>
<version>0.16.0</version>
</dependency>
또한, 픽클 구성 파일에서 POJO를 생성하는 exec-maven-plugin을 사용합니다:
<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
픽클 파일 ToolIntegrationTemplate.pkl이 있다고 가정하면, 도구는 픽클 파일에 해당하는 소스 코드를 target/generated-sources 폴더에 생성합니다.
–generate-spring-boot 인자는 Java 빈에 필요한 Spring Boot 클래스 수준의 @ConfigurationProperties 주석을 포함합니다. 또한, –generate-getters 인자는 속성 키를 private으로 선언합니다.
다행히 Gradle 사용자는 pkl-gradle 플러그인을 통해 POJO 생성을 위한 즉각적인 지원을 제공받을 수 있습니다.
4.2. Pkl에서 구성 생성
데이터 통합 애플리케이션이 있어 Git 및 Jira와 같은 소스 시스템에서 데이터를 가져오고 다른 대상 시스템으로 배포한다고 가정해 보겠습니다.
연결 속성을 보유하기 위해 픽클 템플릿 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() 함수를 사용하여 환경 변수에서 암호를 가져오는 더욱 안전한 접근 방식을 사용했습니다.
4.3. Pkl 템플릿을 POJO로 변환
픽클 파일에서 구성을 정의한 후, pom.xml 파일에 정의된 Maven 목표 exec:java를 실행하여 POJO를 생성할 수 있습니다:
mvn exec:java@gen-pkl-java-bind
결국, pkl-tools 라이브러리는 POJO와 속성 파일을 생성합니다.
ToolIntegrationProperties 클래스에는 두 개의 내부 클래스인 Connection과 Credential이 포함되어 있습니다. 또한 @ConfigurationProperties로 주석이 달려 있어 application.pkl의 속성을 ToolIntegrationProperties 객체에 바인딩하는 데 도움이 됩니다.
4.4. 픽클 구성 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 프레임워크 빈으로 선언하는 구성 클래스를 정의하겠습니다:
@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 파일의 구성으로 바인딩합니다. 결국, 서비스 생성자를 호출하여 빈이 인스턴스화됩니다.
4.5. 서비스 주입 및 실행
마지막으로 다른 Spring 구성 요소에서 @Autowired 주석을 사용하여 서비스 빈을 주입할 수 있습니다.
@SpringBootTest를 사용하여 애플리케이션이 픽클 파일에서 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() 메서드가 성공적으로 실행될 것이라고 추정할 수 있습니다. 현재 이 메서드는 단순히 더미 문자열을 반환합니다.
마찬가지로, 애플리케이션이 시작된 후 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에 연결하여 문제를 가져올 것이라고 추정할 수 있습니다. 현재 이 메서드도 마찬가지로 단순히 더미 문자열을 반환합니다.
5. 결론
이 기사에서는 픽클 파일을 사용하여 Spring Boot 애플리케이션 구성을 정의하는 방법과 그 이점에 대해 배웠습니다. 하지만 구성 가능한 복잡한 디자인을 위해 Pkl 언어 개념을 숙달하는 것 또한 중요합니다. 또한, 외부 속성을 POJO에 바인딩하는 Spring 프레임워크의 기능에 대한 지식도 필수적입니다.
언제나 그렇듯이 이 기사에서 사용된 코드는 GitHub에서 확인할 수 있습니다.