외부 설정
개발 환경과 운영 환경에서 서로 다른 설정 값을 사용해야할 수 있다.
예를 들어 개발 환경과 운영 환경에 따른 데이터베이스 url, username, password 등이 있다.
이 문제를 해결하는 가장 단순한 방법은 각각의 환경에 애플리케이션을 빌드하는 것이다.
그럼 개발app.jar, 운영app.jar가 만들어진다. 해당 파일들을 각 환경별로 배포하면 된다.
하지만 이런 방법은 환경에 따라 빌드를 여러 번하고 개발 버전과 운영 버전의 빌드 결과물이 다르므로 좋은 방법이 아니다.
그러므로 보통 빌드는 한번만 하고 각 환경에 맞추어 실행 시점에 외부 설정 값을 주입한다.
이렇게 하면 빌드도 한번만 하면 되고,
개발 버전과 운영 버전의 빌드 결과물이 같기 때문에 개발환경에서 검증되면 운영 환경에서도 믿고 사용할 수 있다.
외부 설정 방법
- OS 환경 변수
- 자바 시스템 속성
- 자바 커맨드 라인 인수
- 외부 파일
외부 설정은 일반적으로 위 4가지 방법이 있다.
OS 환경 변수
OS 환경 변수는 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값이다.
System.getenv()와 System.getenv(key)를 사용해 환경 변수를 확인했다.
자바 시스템 속성
인텔리제이에서 VM options에 값을 넣어서 실습을 진행했다. -D키=밸류 형식으로 작성해야 한다.
System.getProperties()를 사용해 자바 시스템 속성의 키와 값을 확인했다.
커맨드 라인 인수
커맨드 라인 인수는 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법이다.
다음과 같이 사용한다. java -jar app.jar dataA dataB. 필요한 데이터를 마지막 위치에 스페이스로 구분해서 전달한다.
dataA와 dataB가 전달된다.
key=value 형식 입력
애플리케이션을 개발할 때는 보통 key=value 형식으로 데이터를 받는 것이 편리하다.
커맨드 라인 인수에서 key=value로 데이터를 넘기고 애플리케이션에서 이를 출력 해보면 key=value가 그대로 출력된다.
커맨드 라인 인수는 key=value 형식으로 데이터를 받지 않고 단순히 문자를 여러개 입력 받는 형식이기 때문이다.
그래서 결국 key=value형식으로 애플리케이션을 개발하고 싶다면 개발자가 직접 파싱해서 map에 넣어줘야 한다.
커맨드 라인 옵션 인수
즉 커맨드 라인 인수를 key=value 형식으로 구분하는 방법이 필요하다.
그래서 스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링 만의 표준 방식을 정의했는데,
그것이 바로 커맨드 라인 옵션 인수다.
스프링은 커맨드 라인에 -(dash)2개를 연결해서 시작하면 key=value 형식으로 정하고 이것을 커맨드 라인 옵션 인수라 한다.
예시는 다음과 같다. java -jar app.jar --url=dev --username=debin.
스프링이 제공하는 ApplicationArguments 인터페이스와 DefaultApplicationArguments 구현체를 사용하면
커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있다.
또한 옵션 인수는 --username=A --username=B처럼 하나의 키에 여러 값을 포함할 수 있으므로 결과는 리스트로 반환한다.
커맨드 라인 옵션 인수는 자바 언어의 표준 기능이 아니다. 스프링이 편리함을 위해 제공하는 기능이다.
커맨드 라인 옵션 인수와 스프링 부트
스프링 부트는 커맨드 라인을 포함해서 커맨드 라인 옵션 인수를 활용할 수 있는 ApplicationArguments를 스프링 빈으로 등록한다.
그리고 그 안에 입력한 커맨드 라인을 저장해둔다. 그래서 해당 빈을 주입 받으면 커맨드 라인으로 입력한 값을 어디서든 사용할 수 있다.
@Component
public class CommandLineBean {
private final ApplicationArguments arguments;
//자동 주입 가능
public CommandLineBean(ApplicationArguments arguments) {
this.arguments = arguments;
}
}
외부 설정 - 스프링 통합
다양한 외부 설정을 살펴보았다. 외부 설정값은 모두 key=value인데 우리가 OS 환경 변수를 쓰다가 자바 시스템 속성을 사용하려면 환경변수와 관련된 코드를 전부 수정해야 한다.
외부 설정 값이 어디에 위치하든 상관없이 일관성 있고, 편리하게 key=value 형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.
예를 들어 외부 설정값을 OS 환경 변수를 사용하다가 자바 시스템으로 변경하는 경우에 소스코드를 다시 빌드하지 않고 사용할 수 있다.
스프링은 이 문제를 Environment와 PropertySource라는 추상화를 통해서 해결한다.
스프링은 PropertySource라는 추상 클래스를 제공하고,
각각의 외부 설정을 조회하는 SystemEnvironmentPropertySource와 같은 다양한 구현체를 만들어두었다.
스프링은 로딩 시점에 필요한 PropertySource들을 생성하고, Environment에서 사용할 수 있게 연결해둔다.
Environment를 이용해서 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있다.
우리가 아는 application.properties도 PropertySource에 추가된다. 따라서 Environment를 통해서 접근할 수 있다.
Environment 구현체의 인스턴스 변수로 MutablePropertySources가 있는데 여기에 PropertySource를 보관한다.
정리하면 스프링은 Environment를 통해서 외부 설정을 읽는 방법을 추상화했다.
설정 데이터와 외부 파일
환경 변수, 자바 시스템 변수 등 사용해야 하는 값이 늘어날 수록 사용하기가 불편해진다.
그래서 등장한 대안이 바로 설정값을 파일에 넣어서 관리하는 방법이다.
그리고 애플리케이션 로딩 시점에 해당 파일을 읽어들이면 된다.
그중에서도 .properties라는 파일은 key=value를 사용해 설정 값을 관리하기 아주 적합하다.
개발 서버와 운영 서버 각각에 application.properties라는 같은 이름의 파일을 준비한다.
그리고 애플리케이션 로딩 시점에 파일을 읽어서 그 속에 있는 값들을 외부 설정값으로 사용하면 된다.
개발자는 application.properties라는 이름의 파일을 자바를 실행하는 위치에 만들어두면 된다.
그러면 PropertySource의 구현체가 이 파일을 읽어들인다. 그럼 우리는 Environment를 통해 외부 설정 값으로 사용할 수 있다.
여기도 문제가 남아있는데 바로 외부 설정을 별도로 파일로 관리하게 되면 설정 파일 자체를 관리하기 번거롭다는 점이다.
서버가 10대면 변경사항이 있을 때 10대의 서버를 모두 변경해야 한다.
설정 데이터와 내부 파일 분리
이 문제를 해결하는 가장 쉬운 방법은 설정 파일을 프로젝트 내부에 포함해서 관리하는 것이다.
그리고 빌드 시점에 함께 빌드되게 하는 것이다.
위 그림처럼 개발용 설정 파일 application-dev.properties와 운영용 설정 파일 application-prod.properties를 함께 포함해서 관리한다. 개발 환경이라면 전자 파일을 읽어 들이고 운영 환경이라면 후자 파일을 읽어들여서 사용하면 된다.
그러면 구분이 필요한데 외부 설정을 사용해서 개발 서버는 dev라는 값을 제공하고, 운영 서버는 prod 값을 제공해야 한다.
이 값을 쉽게 프로필이라고 하자.
정리하면 dev 프로필이 넘어오면 application-dev.properties를 읽어서 사용하고
prod 프로필이 넘어오면 application-prod.properties를 읽어서 사용한다.
//application-dev.properties
url=dev.db.com
username=dev_user
password=dev_pw
//application-prod.properties
url=prod.db.com
username=prod_user
password=prod_pw
프로필
스프링은 이런 곳에서 사용하기 위해 프로필이라는 개념을 지원한다.
spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다.
그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 파일(설정 데이터)를 조회한다.
application-{profile}.properties
- 커맨드 라인 옵션 인수 실행 --spring.profiles.active=dev
- 자바 시스템 속성 실행 -Dspring.profiles.active=dev
- java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
이제 설정 데이터를 프로젝트 안에서 함께 관리할 수 있고, 배포 시점에 설정 정보도 함께 배포된다.
설정 데이터와 내부 파일 합체
위와 같은 방식으로 하면 파일을 한 눈에 볼 수 없다는 단점이 있다.
스프링은 이런 단점을 보완하기 위해 물리적인 하나의 파일 안에서 논리적으로 영역을 구분하는 방법을 제공한다.
url=local.db.com321
username=local_user321
password=local_pw321
spring.config.activate.on-profile=dev
url=dev.db.com321
username=dev_user321
password=dev_pw321
#---
spring.config.activate.on-profile=prod
url=prod.db.com321
username=prod_user321
password=prod_pw321
application.properties에서 논리적으로 영역을 나눌 수 있는데 방법은 #---과 !---이다.
그러면 application.properties는 하나의 파일이지만 내부에 2개의 논리 문서로 구분되어 있다.
위의 파일에서 맨 부분에는 프로필을 지정하지 않은 부분이 있는데 이는 설정 데이터의 기본 값이다.
즉 프로필과 무관하게 사용되는 값이다.
스프링은 이런 기본 값을 늘 읽고, 해당하는 프로필이나 다른 기본 값이 나오면 이를 덮어 씌운다. 중요한 건 위에서 아래로 읽는다는 것이다.
우선 순위
우선 순위는 위에서 아래로 적용된다. 아래가 더 우선순위가 높다.
- 설정 데이터(application.properties)
- OS 환경변수
- 자바 시스템 속성
- 커맨드 라인 옵션 인수
- @TestPropertySource
더 유연한 것이 우선권을 가진다고 생각하면 된다.
참고자료
댓글