웹기반 공통 Library
중복되는 설정들 혹은 공통으로 관리해야 할모듈을 한 곳에서 관리하는 것은 흔한 일이다.
현재 내가 진행하고 있는 프로젝트 또한 모든 프로젝트에서 참조하는 스프링 웹 기반의 라이브러리 프로젝트가 존재하고, 여기에는 많은 스프링 Bean이 존재하고 있다.
처음에는 문제를 느끼지 못했지만, 시간이 갈수록 웹 환경에 의존적인 많은 Bean들이 다른 클라이언트 프로젝트에 맞지 않는 경우가 생겨났고, 설계가 잘못됐다고 확실히 느낄 수 있었다. 어디서부터 잘못된 걸까?
외부 jar에 정의된 스프링 Bean은 읽지 않는 것이 기본 원칙이다.
제일 헷갈린 부분이다. 내가 현업에서 사용하는 프로젝트는 외부 jar에 정의된 Bean들이 클라이언트 프로젝트 Context에서 등록돼 잘 사용하고 있다. 원인은 공통된 패키지 명에 있었다.
클라이언트 프로젝트와 외부 jar 프로젝트의 groupId가 다 똑같고 artifactId만 다르게 구성돼 있는데, @ComponentScan을 하는 범위가 groupId로 지정돼 있어서 스프링 컨텍스트에 모두 등록될 수 있었던 것이다.
클라이언트 프로젝트가 컴포넌트스캔할 때, 외부에서 import 된 jar의 패키지도 읽는 것이 확실하진 않아 상황을 재현해 봤다.
프로젝트 구성
프로젝트는 총 3개로 구성된다.
- Master
- 버전, 프로퍼티를 관리하는 pom 프로젝트로, worker 프로젝트를 모듈로 한다.
- Worker
- 공통 컴포넌트가 구현되는 jar 프로젝트로, 위에서 말한 웹기반 공통 Library다.
- Slave
- Master프로젝트를 parent로 하고 worker를 import 하는 클라이언트 프로젝트다.
Master
- 프로젝트에서 공통적으로 관리할 property와 library 버전, dependency, plugin을 관리하는 pom 프로젝트
- worker 프로젝트를 모듈 프로젝트로 명시
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
</parent>
<groupId>me.bbbicb</groupId>
<artifactId>master</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>master</name>
<packaging>pom</packaging>
<description>master</description>
<properties>
<java.version>17</java.version>
</properties>
<modules>
<module>worker</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>me.bbbicb</groupId>
<artifactId>worker</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Worker
- 공통적으로 사용할 클래스(소스)를 작성하는 jar 프로젝트
- 테스트를 위해 @ConfigurationProperty를 작성
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.bbbicb</groupId>
<artifactId>master</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>me.bbbicb</groupId>
<artifactId>worker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>worker</name>
<description>worker</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
package me.bbbicb.worker;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "master")
public class MasterProperty {
String hello;
public void setHello(String hello) {
this.hello = hello;
}
public String getHello() {
return hello;
}
}
// application.yml
master:
hello: "hi"
Slave
- 위에서 배포한 Library를 사용할 클라이언트 프로젝트
- master pom 프로젝트에 의존(<parent>)
- worker 프로젝트를 의존 lib에 명시
- @ComponentScan 범위를 worker 프로젝트를 포함하기 위해 me.bbbicb로 명시
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.bbbicb</groupId>
<artifactId>master</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../master</relativePath>
</parent>
<groupId>me.bbbicb</groupId>
<artifactId>slave</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>slave</name>
<description>slave</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>me.bbbicb</groupId>
<artifactId>worker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package me.bbbicb.slave;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "me.bbbicb")
public class SlaveApplication {
public static void main(String[] args) {
SpringApplication.run(SlaveApplication.class, args);
}
}
package me.bbbicb.slave;
import me.bbbicb.worker.MasterProperty;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SlaveApplicationTests {
@Autowired
MasterProperty masterProperty;
@Test
void contextLoads() {
Assertions.assertThat(masterProperty.getHello()).isEqualTo("hi");
}
}
테스트 결과 실패 없이 성공적으로 동작하는 것을 확인할 수 있었다. @SpringBootApplication 의 scanBasePackages 속성을 생략할 경우, me.bbbicb.slave 하위의 Bean만 등록해 테스트가 실패하는 것도 확인할 수 있었다.
결론은 외부 jar의 스프링 빈도 컴포넌트 스캔 범위에 추가하면 읽을 수 있다는 것이다. 언뜻 보면, 편리한 방법이지만 초반에 언급했듯이 이 방법은 몇 가지 문제점이 있었다.
- 클라이언트 프로젝트에서 Bean을 커스텀할 수 있음을 보장할 수 없다.
- Conditional 어노테이션을 활용할 순 있지만, 뒤에서 볼 auto-configuration과 다르게 Bean 등록 순서가 보장되지 않아, 스프링 공식문서에서도 추천하지 않는 방법이다.
- 클라이언트 프로젝트에서 필요 없는 Bean이 로드되는 걸 선택할 수 없다.
- 웹에 의존적인 Bean이 필요없는 경우(ex. 배치프로젝트)에도 모든 빈들이 컨텍스트에 등록된다.
이와 같은 문제점들은 auto-configure 방식의 spring-boot-starter로 변경하면 해결할 수 있다.
다음 포스팅에서 단순 Spring 기반 라이브러리 프로젝트를 spring-boot-starter로 변경하는 과정을 다뤄보겠다.
'Backend > Java' 카테고리의 다른 글
Stream에 Decorator 패턴 써먹기 (2) | 2023.04.03 |
---|---|
공통 lib -> spring-boot-starter로 바꾸기(2) (0) | 2023.03.20 |
Session은 Controller까지만 (0) | 2022.10.09 |
Spring Boot 2.4.x 이슈 (0) | 2022.07.08 |
UUID, 정말 안전할까? (2) | 2021.11.06 |