스프링 부트 - 핵심 원리와 활용 : 외부 설정과 프로필 2

jkky98·2024년 11월 4일
0

Spring

목록 보기
67/77

환경설정 객체

my.datasource.url=local.db.com
my.datasource.username=username
my.datasource.password=password
my.datasource.etc.max-connection=10
my.datasource.etc.options=CACHE,ADMIN
my.datasource.etc.timeout=3500ms

application.properties에 들어갈 외부설정은 위와 같다. 이를 읽을 방법으로, 우리는 스프링의Environment 인터페이스를 활용했었다. 스프링이 지원하는 다른 방법들이 존재하는데 애노테이션 방식인@ConfigurationProperties로 하여금 더 편리한 이용이 가능하다.

환경설정 객체 빈 등록 Config


@Slf4j
@Configuration
@RequiredArgsConstructor
public class MyDataSourceEnvConfig {

    private final Environment env;

    @Bean
    public MyDataSource myDataSource() {
        String url = env.getProperty("my.datasource.url");
        String username = env.getProperty("my.datasource.username");
        String password = env.getProperty("my.datasource.password");
        int maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
        List<String> options = env.getProperty("my.datasource.etc.options", List.class);
        Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);

        MyDataSource myDataSource = new MyDataSource(url, username, password, maxConnection, timeout, options);
        return myDataSource;
    }
}

MyDataSource는 환경설정의 값들을 보관할 객체이다. 기존에 배웠던 Environment를 통해 외부설정을 곧바로 읽어들여와 MyDataSource를 빈 등록하는 시점에 값들을 채워넣는다.

ConfigurationProperties

기존 프로세스는 Environment로 하여금 설정을 읽어와서 MyDataSource에 넣었다. 이 방식에서 Envrioment로 읽기를 ConfigurationProperties애노테이션으로 하여금 자동화할 수 있다.

@Data
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV1 {

    private String url;
    private String username;
    private String password;
    private Etc etc;

    @Data
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options = new ArrayList<>();
    }
}

위와 같이 @ConfigurationProperties("my.datasource")를 주면 그 아래 설정 값들을 모두 필드로 읽어오게 된다. @Data로 하여금 setter와 getter를 확보한 클래스이므로 프로퍼티 주입방식으로 설정정보를 읽어오게 된다.

이 클래스를 빈으로 등록하면 빈으로 등록될 때 자동적으로 설정값을 읽어와 빈으로 등록된다. 이 빈 객체(MyDataSourcePropertiesV1)에서 값을 뽑아 MyDataSource에 담아주면 된다.

@ConfigurationProperties 생성자 방식

위에서는 Setter 사용으로 하여금 설정 값을 읽어오는 방식이지만 누군가가 setter를 사용할 수 있다는 단점이 존재한다. 설정 값을 애플리케이션 로직 내에서 바꾸는 시나리오는 100% 존재할 수 없기에 Setter를 열어놓는 것은 좋지 않다. 생성자 방식은 이에 대한 해답이 된다.

@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {

    private String url;
    private String username;
    private String password;
    private Etc etc;

    @Getter
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }

    public MyDataSourcePropertiesV2(String url, String username, String password, Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }
}

클래스에 생성자를 만들고 내부 클래스인 Etc에도 생성자를 만들어 주입하는 길을 열어준다면 스프링은 설정 값을 이에 주입해준다.

ConfigurationProperties 검증

만약 누군가가 max-connection값에 10000을 대입했고 이는 시스템에서 매우 과한 숫자로 받아들여진다고 가정해보자. 그렇기에 우리는 최소1, 최대999라는 숫자만 max-connection 값으로 들어와도 괜찮도록 제한을 두기 위해 자바 빈 검증기(java bean validation)를 사용한다.

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation' //추가
@Getter
@ConfigurationProperties("my.datasource")
@Validated
public class MyDataSourcePropertiesV3 {

    @NotEmpty
    private String url;
    @NotEmpty
    private String username;
    @NotEmpty
    private String password;

    private Etc etc;

위와 같이 @Validated애노테이션을 주고 @NotEmpty같은 애노테이션을 필드에서 이용할 수 있다.

yml(yaml)

보통 application.properties보다 application.yml을 많이 사용한다. 실무에서는 설정 값이 복잡하기 때문에 .yml이 주로 이용된다. 차이를 직접 관찰해보자.

my:
  datasource:
    url: local.db.com
    username: username
    password: password
    etc:
      max-connection: 10
      options: CACHE,ADMIN
      timeout: 60s
---
spring:
  config:
    activate:
      on-profile: dev
my:
  datasource:
    url: dev.db.com
    username: dev_user
    password: dev_pw
    etc:
      max-connection: 100
      options: DEV,ADMIN
      timeout: 60s

위와 같이 계층으로 구분하기 때문에 개발자가 시각적으로 설정에 대한 구조를 더 쉽게 파악할 수 있다.

설정을 읽는데에 있어 계층만 잘 맞추어준다면 properties와 바꿔치기만 해주면 되기 때문에 다른 조작은 필요없다.

다만 properties와 같이 사용하진 않도록하자.(우선순위는 properties에 있지만 두개의 형식을 같이 사용해서 헷갈릴 여지를 줄 이유가 없다.)

@Profile

환경마다 설정을 다르게 잡는 것을 더해 환경마다 등록될 빈을 결정하는 것도 @Profile애노테이션을 통해 가능하다.

@Slf4j
@Configuration
public class PayConfig {

    @Bean
    @Profile("default")
    public LocalPayClient localPayClient() {
        log.info("LocalPayClient 빈 등록");
        return new LocalPayClient();
    }

    @Bean
    @Profile("prod")
    public ProdPayClient prodPayClient() {
        log.info("ProdPayClient 빈 등록");
        return new ProdPayClient();
    }

}

개발단계에서 결제 기능을 확인하기 위해 가짜 결제 메서드를 만들어 사용할 경우 위와 같이 빈등록을 한다면 프로필 외부설정에 아무 값도 주지 않을 경우 LocalPayClient가 빈 등록될 것이고 prod(배포)의 경우 ProdPayClient가 등록될 것이다.

이를 통해 개발용 jar, 배포용 jar로 분리되지 않고 하나의 jar에서 설정 값을 통해 만들어질 초기화 프로세스가 달라지도록 설계할 수 있다.

profile
자바집사의 거북이 수련법

0개의 댓글