Backend/Java

공통 lib -> spring-boot-starter로 바꾸기(1)

비비빅B 2023. 2. 13. 02:27

웹기반 공통 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 하는 클라이언트 프로젝트다.

 

slave(web project)가 master(공통 pom)과 worker(lib)를 참조하는 구조

 

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");
  }

}

 

테스트 결과 실패 없이 성공적으로 동작하는 것을 확인할 수 있었다. @SpringBootApplicationscanBasePackages 속성을 생략할 경우, 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