SpringBoot 외부설정과 프로파일(Profile), 그리고 Dspring 옵션

devdo·2023년 3월 6일
0

SpringBoot

목록 보기
33/35
post-thumbnail

스프링과 설정 데이터

개발자가 파일을 읽어서 설정값으로 사용할 수 있도록 개발을 해야겠지만, 스프링 부트는 이미 이런 부분을 다 구현해두었다. 개발자는 application.properties 라는 이름의 파일을 자바를 실행하는 위치에 만들어 두기만 하면 된다. 그러면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource 의 구현체를 제공한다.

스프링에서는 이러한 application.properties 파일을 설정 데이터(Config data)라 한다.

당연히 설정 데이터도 Environment를 통해서 조회할 수 있다.

예를 들어, 다음과 같이 옵션을 줄수도 있다.

-Dspring.profiles.active=dev


command line arguments

커맨드라인 옵션 인수
key=value 형식으로 구분

--key=value 형식
단점 : 너무 길면 사용하기 불편해져..

예를 들어서 OS 환경 변수에 두면 System.getenv(key) 를 사용해야 하고, 자바 시스템 속성을 사용하면 System.getProperty(key) 를 사용해야 한다. 만약 OS에 환경 변수를 두었는데, 이후에 정책이
변경되어서 자바 시스템 속성에 환경 변수를 두기로 했다고 가정해보자. 그러면 해당 코드들을 모두 변경해야 한다.

외부 설정값이 어디에 위치하든 상관없이 일관성 있고, 편리하게 key=value 형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.

예를 들어, 외부 설정값을 OS 환경변수를 사용하다가 자바 시스템 속성으로 변경하는 경우에 소스코드를 다시 빌드하지 않고 그대로 사용할 수 있다.

스프링은 이 문제를 Environment 와 PropertySource 라는 추상화를 통해서 해결한다.

예시

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;


@Slf4j
@Component
public class EnvironmentCheck {
 private final Environment env;
 
 public EnvironmentCheck(Environment env) {
 	this.env = env;
 }
 
 @PostConstruct
 public void init() {
 
 	String url = env.getProperty("url");
 	String username = env.getProperty("username");
 	String password = env.getProperty("password");
 
 	log.info("env url={}", url);
 	log.info("env username={}", username);
 	log.info("env password={}", password);
 	}
}

커맨드 라인 옵션 인수 실행
--url=devdb --username=dev_user --password=dev_pw

자바 시스템 속성 실행
-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw

ExternalApplication.main 을 실행하자.

실행 결과

env username=dev_user
env password=dev_pw

정리

커맨드 라인 옵션 인수, 자바 시스템 속성 모두 Environment 를 통해서 동일한 방법으로 읽을 수 있는 것을 확인했다.
스프링은 Environment 를 통해서 외부 설정을 읽는 방법을 추상화했다. 덕분에 자바 시스템 속성을 사용하다가 만약 커맨드 라인 옵션 인수를 사용하도록 읽는 방법이 변경되어도, 개발 소스 코드는 전혀 변경하지 않아도 된다.


외부파일

  • properties, yaml(yml) 파일에 저장 (실무에서는 계층구조를 가지고 있어 읽기 편한 yml 파일을 더 선호)

  • 우선순위: properties > yml

  • properties 같이 설정파일들은 케밥케이스(-)를 권장

  • profile 설정
    1) 파일로 분리 ex. application-dev.yml, application-prod.yml
    2) application.yml 1개의 파일에서 profile을 분리
    --- dash 3 보통 이 방법으로 쓰임

  • 읽는 순서!
    위 -> 아래

application.yml(Profile 분리) 예시

1) application.yml , --- Profile 분리
2) Profile 분리 설정

  • default profile 설정
# default 설정, profile옵션을 안해주면 이 설정 profile을 선택 
spring:
  profiles:
  	active: "profile"
   
  • active 일시, profile 적용 설정
spring:
  config:
    activate:
      on-profile: "profile"
  • 실제 application.yml 예시
server:
  port: 8080

spring:
  config:
    import: optional:file:.env[.properties] # .env 파일 로드

  profiles:
    active: local # 기본 활성화 프로파일로 local 설정

  jpa:
    hibernate:
      ddl-auto: update # 테이블 생성 및 업데이트 전략
    properties:
      hibernate:
        format_sql: true # SQL 포맷팅
        highlight_sql: true # 하이라이트 SQL 출력
        use_sql_comments: true # 실제 JPQL SQL 주석 사용

---
spring:
  config:
    activate:
      on-profile: local

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: root
    password: 1234

app:
  aws:
    ec2:
      instance-front-url: http://localhost:3000

logging:
  level:
    org.hibernate.SQL: debug # Hibernate의 SQL을 출력
    org.hibernate.orm.jdbc.bind: trace # Hibernate의 SQL 바인딩을 출력
    org.springframework.transaction.interceptor: trace # Hibernate의 SQL 바인딩을 출력

---
spring:
  config:
    activate:
      on-profile: prod

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://my-db:3306/test?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: root
    password: 1234
    hikari:
      max-lifetime: 600000 # 커넥션 최대 생존 시간
      idle-timeout: 300000 # 커넥션 최대 유휴 시간
      connection-timeout: 30000 # 커넥션 타임아웃

app:
  aws:
    ec2:
      instance-front-url: http://43.203.42.37

logging:
  level:
    org.hibernate.SQL: error # Hibernate의 SQL을 출력

외부설정 가져오기 방법

1) @Value 방법

application.yml

# Application Active Type(prod, dev)
spring:
  profiles:
    active: dev

@Value 어노테이션으로 가져올 수 있다.

@Component
public class EnvironmentProps {

    @Value("${spring.profiles.active}")
    private String activeProfile;

    public boolean isProdActiveProfile() {
        return Objects.equals(activeProfile, "prod");
    }

}
@Data
@Component
public class ElasticSearchProps {

    @Value("${app.props.elastic-search.product.enterprise-search-baseUrl}")
    private String productEngineUrl;

    @Value("${app.props.elastic-search.product.private-key}")
    private String productEnginePrivateKey;
}
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticSearchScheduler {

    private final ElasticSearchService elasticSearchService;
    private final EnvironmentProps environmentProps;

    @Scheduled(fixedRate = 30000)
    public void saveProductList() {
        // 실서버인 경우에만 동작
        if (environmentProps.isProdActiveProfile()) {
            elasticSearchService.indexProductList();
        }
    }

}

2) @ConfigurationProperties 방법

application.yml

...

# Properties
app:
  props:
    type: dev
    url: https://XXXXXXXX.com
    host: https://XXXXXXXX.com/
    ai:
      resnet50Url: http://api-XXXXXXXX/api/v1/classify
    allowOrigins: http://localhost:3001
    sms:
      smsTellNumber: "010-XXXX-XXX"
      client-key: "NCSQGSYTWQSHNXDR@@"
      secret-key: "ZTHWIUBEM2IKME8W044EU2WTX0EMNZQP@@"

yaml 파일의 app.props.sms @ConfigurationProperties 어노테이션을 사용해서, Java 코드로 변환해서(Java는 카멜케이스) 정보를 가져올 수 있다!

@Data
@Component  // 빈으로 등록하기 위함
@ConfigurationProperties("app.props.sms")
public class SmsProps {
    private String smsTellNumber;
    private String clientKey;
    private String secretKey;
}

ConfigurationProperties 장점

  • 외부 설정을 객체로 편리하게 변환해서 사용할 수 있다.
  • 외부 설정의 계층을 객체로 편리하게 표현할 수 있다.
  • 외부 설정을 타입 안전하게 사용할 수 있다.
  • 검증기를 적용할 수 있다.

3) @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();
	}
}
  • @Profile의 정체
package org.springframework.context.annotation;
...
@Conditional(ProfileCondition.class)
public @interface Profile {
	String[] value();
}

@Profile 은 특정 조건에 따라서 해당 빈을 등록할지 말지 선택한다. 어디서 많이 본 것 같지 않은가? 바로 @Conditional 이다.
코드를 보면 @Conditional(ProfileCondition.class) 를 확인할 수 있다.
스프링은 @Conditional 기능을 활용해서 개발자가 더 편리하게 사용할 수 있는 @Profile 기능을 제공합니다.

@Profile 을 사용하면 각 환경 별로 외부 설정 값을 분리하는 것을 넘어서, 등록되는 스프링 빈도 분리할 수 있습니다.

4) jar 파일 실행시 옵션설정

-Dspring.profiles.active=prod

or
--spring.profiles.active=prod

예시)

java -Dspring.profiles.active=prod -jar *-SNAPSHOT.jar

java -jar *-SNAPSHOT.jar --spring.profiles.active=prod


참고

  • 김영한 <스프링부트 핵심원리와 활용> 강의
profile
배운 것을 기록합니다.

0개의 댓글