개발 db와 운영 db url 달라 빌드를 2번해서 배포하는 경우 같은 소스코드에서 나온 빌드 결과물인지 검증하기 어렵다.
빌드는 한번만하고 각 환경에 맞추어 실행 시점에 외부 설정값을 주입하는 방법이 좋다.
빌드를 한번하고 외부 설정을 주입하는 방식 , 새로운 환경이 추가되어도 손쉽게 적용할 수 있다.
public class CommandLineBean {
private final ApplicationArguments arguments;
@PostConstruct
public void init() {
log.info("source {}", List.of(arguments.getSourceArgs()));
log.info("optionNames {}", arguments.getOptionNames());
Set<String> optionNames = arguments.getOptionNames();
for (String optionName : optionNames) {
log.info("option args {}={}",optionName,arguments.getOptionValues(optionName));
}
}
}
Command Line Argument도 스프링부트에서 빈으로 만들어서 쉽게 꺼낼수 있게 해놓음
하지만 OS 변수 ,JVM 변수 다 읽는 방법이 다 다르다.
외부 설정 - 스프링 통합
외부 설정값을 OS 환경변수를 사용하다가 자바 시스템 속성으로 변경하는 경우에 소스코드를 다시 빌드하지 않고 그대로 사용할 수 있다.
스프링은 Environment , PropertySource 라는 추상화로 가능하게 해준다.
스프링은 PropertySource라는 추상 클래스를 제공하고 , 각각의 외부 설정을 조회하는 ~~PropertySouce 구현체들을 만들어 두었다.
스프링은 로딩 시점에 필요한 PropertySource들을 생성하고 Environment에서 사용할 수 있게 연결해둔다.
Enviornment를 통해서 특정 외부 설정에 종속되지 않고 , 일관성 있게 key=value 형식의 외부설정에 접근 할 수 있다.
겹치는것이 있을때 우선순위 (jvm property , commad line property등 같은 key,value로 등록할떄)
- 더 유연한 것이 우선권을 가짐.
변경하기 어려운 파일보다 실행시 원하는 값을 줄 수 있는 jvm property이 더 우선권을 가짐 - 범위가 넓은것보다 좁은 것이 우선권을 가짐
jvm property은 해당 JVM 아넹서 모두 접근할 수 있음.
반면에 commad line property는 main 의 args를 통해서 들어오기 때문에 접근 범위가 더 좁음
설정 데이터 - 외부 파일
application.properties에 원하는 설정 데이터파일을 key=value 값으로 준비해둔다.
그러면 스프링이 해당 파일을 읽어서 PropertySource 구현체를 제공한다.
url=dev.db.com
username=dev_user
password=dev_pw
그러면 위의 코드 변경없이 Environment에서 다 잘 읽어오게 된다.
하지만 이런 설정파일이 각 서버마다 존재하면 각 서버에서 다 변경을 해줘야한다.
또한 설정 값만 따로 관리되니까 이게 소스코드에 어떤 영향을 끼치는 파악하기 어렵다.
application-dev.properties , application-prod.properties 를 한번에 빌드하고 profile을 주입해서 각각 다른 설정 파일들을 읽어오게 변경할 수 있다.
spring.profile.active=dev 하면 applicatino-dev.properties 를 사용하게되고 prod도 마찬가지이다.
url=local.db.com
username=local_user
password=local_pw
#---
spring.config.activate.on-profile=dev
url=dev.db.com
username=dev_user
password=dev_pw
#---
spring.config.activate.on-profile=prod
url=prod.db.com
username=prod_user
password=prod_pw
#--
url=hello.db.com
하나의 파일로 통합도 가능하다.
가장 최상위에 있는것은 아무 profile도 지정해주지 않았을때 사용하게 되는 default 설정이된다.
제일 처음에 default는 기본적으로 읽어들인후에 아래로 내려가면서 profile에 따라 덮어씌울지말지 결정하게 된다.
dev,prod profile을 다 지정해줘도 url의 경우 맨 아래에 있는 default로 덮어씌워지게된다.
설정 데이터(application.properties)
OS환경변수
자바시스템속성
커맨드 라인 옵션 인수
@TestPropertySource(테스트에서 사용함)
아래로 내려갈수록 우선순위가 높음. 변경하기 쉬우니까 (즉 필요할 때마다 변경했을 테니 우선순위가 더 높아야한다.)
외부 설정 사용 - Environment
Environment -> @Value -> @ConfigurationProperties 순으로 오른쪽으로 갈 수록 유용하고 쉽게 외부설정을 가져오게 해준다.
my.datasource.url=local.db.com
my.datasource.username=username
my.datasource.password=password
my.datasource.etc.max-connection=1
my.datasource.etc.timeout=3500ms
my.datasource.etc.options=CACHE,ADMIN
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");
Integer maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);
List<String> options = env.getProperty("my.datasource.etc.options", List.class);
return new MyDataSource(url,username,password,maxConnection,timeout,options);
}
}
타입에 대한 변환도 지원한다. 3500ms 는 Duration으로 List도 지원하게된다.
외부설정 사용 - @Value
public class MyDataSourceValueConfig {
@Value("${my.datasource.url}")
private String url;
@Value("${my.datasource.username}")
private String username;
@Value("${my.datasource.password}")
private String password;
@Value("${my.datasource.etc.max-connection}")
private int maxConnection;
@Value("${my.datasource.etc.timeout}")
private Duration timeout;
@Value("${my.datasource.etc.options}")
private List<String> options;
@Bean
public MyDataSource myDataSource1() {
return new MyDataSource(url,username,password,maxConnection,timeout,options );
}
public void myDataSource2( @Value("${my.datasource.url}") String url) {
System.out.println("url = " + url);
}
}
parameter에서도 잘 작동한다. 마찬가지로 타입(Duration, List등) 변환도 지원한다. 내부적으로 Environment를 사용하기 때문이다.
@Value("${my.datasource.etc.max-connection:2}")
2 와 같이 default 값을 줄 수 있다.
하지만 key 값들을 일일이 다 적어줘야하는것은 마찬가지이다.
외부설정 사용 - @ConfigurationProperties
type-safe configuration properties
외부 설정을 자바코드로 관리할 수 있게되고 타입을 가질 수 있게된다.
@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<>();
}
}
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
@RequiredArgsConstructor
public class MyDataSourceConfigV1 {
private final MyDataSourcePropertiesV1 properties;
public void test() {
String password = properties.getPassword();
Duration timeout = properties.getEtc().getTimeout();
}
}
@EnableConfigurationProperties에 빈으로 등록할 클래스를 명시하면 스프링에서 빈으로 만드는 시점에 외부설정 값들을 다 읽어와서 채워넣어서 생성해준다.
또한 타입을 자바단에서 정해놔서 외부설정에 잘못된 타입이 있으면 빈 생성시점에서 오류가 난다.
@ConfigurationPropertiesScan({"hello"})
public class ExternalReadApplication {
public static void main(String[] args) {
SpringApplication.run(ExternalReadApplication.class, args);
}
}
이미 @ConfigurationProperties도 달아줬는데 또 @Enable~ 을 해주는것은 불편하다.
이를 해결하기위해 @ConfigurationPropertiesScan이 @ConfigurationProperties 을 감지하여 다 빈으로 등록을 해준다. @Enable ~ 할 필요가 없어진다.
하지만 현재 Setter가 존재하여 제약이 부족하다.
@ConfigurationProperties 생성자
@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {
private String url;
private String username;
private String password;
private Etc etc;
public MyDataSourcePropertiesV2(String url, String username, String password, Etc etc) {
this.url = url;
this.username = username;
this.password = password;
this.etc = etc;
}
@Getter
public static class Etc {
private int maxConnection;
private Duration timeout;
private List<String> options = new ArrayList<>();
public Etc(int maxConnection, Duration timeout, List<String> options) {
this.maxConnection = maxConnection;
this.timeout = timeout;
this.options = options;
}
}
}
생성자주입으로도 값을 동일하게 넣어서줘서 빈으로 만들어주게 된다.
@인자 앞에 @DefaultValue Etc etc 이런식으로 넣어주면 외부설정 값이 존재하지 않아도 기본 객체를 만들어서 null, 0값들을 채워넣어준다.
또한 생성자가 클래스당 지금처럼 1개이면 상관없지만 2개 이상이면 @어떤 생성자로 바인딩할것인지 @ConstructorBinding을 써줘야한다.
하지만 max-connection =0 으로 해놓으면 db connection이 맺어지지 않으니 validation을 하고싶다.
@ConfigurationProperties 검증
java bean validation이라는 표준 검증기가 제공된다.
@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {
@NotEmpty
private String url;
@NotEmpty
private String username;
private String password;
private Etc etc;
public MyDataSourcePropertiesV2(String url, String username, String password, Etc etc) {
this.url = url;
this.username = username;
this.password = password;
this.etc = etc;
}
@Getter
public static class Etc {
@Min(1)
@Max(999)
private int maxConnection;
@DurationMin(seconds = 1)
@DurationMax(seconds = 60)
private Duration timeout;
private List<String> options = new ArrayList<>();
public Etc(int maxConnection, Duration timeout, List<String> options) {
this.maxConnection = maxConnection;
this.timeout = timeout;
this.options = options;
}
}
}
@Profile
설정값이 다른 정도가 아니라 각 환경마다 아예 서로 다른 빈을 등록하고 싶을 때 쓸 수 있는 기능이다.
@Configuration
public class PayConfig {
@Bean
@Profile("default")
public LocalPayClient localPayClient() {
System.out.println("PayConfig.localPayClient");
return new LocalPayClient();
}
@Profile("prod")
@Bean ProdPayClient prodPayClient() {
System.out.println("PayConfig.prodPayClient");
return new ProdPayClient();
}
}
spring.profile.active 값에 따라서 어떤 bean을 등록할지 결정할 수 있다.
@Conditional 을 쓰고있고 ProfileCondition 구현체를 통해 어떤 빈을 등록할지 구분하는 로직이 존재한다.
'WEB > Spring Boot' 카테고리의 다른 글
마이크로미터, 프로메테우스, 그라파나 (0) | 2023.05.18 |
---|---|
액츄에이터 (0) | 2023.05.16 |
자동 구성(Auto Configuration) (0) | 2023.05.13 |
스프링 부트 스타터와 라이브러리 관리 (0) | 2023.05.11 |
스프링 부트와 내장 톰캣 (0) | 2023.05.10 |