ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 핵심 기능 4 - 외부설정(1)
    BackEnd/Spring Boot 2023. 5. 3. 22:00
    반응형

      유지보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 변하지 않는 것을 분리하는 것입니다. 각 환경에 따라 변하는 외부 설정값은 분리하고, 변하지 않는 코드와 빌드 결과물은 유지합니다.

     

      애플리케이션을 실행할 때 필요한 설정값을 외부에서 불러와 애플리케이션에 전달할 수 있는 방법은 일반적으로 다음 4가지가 존재합니다.

    • OS 환경 변수: OS에서 지원하는 외부 설정, 해당 OS를 사용하는 모든 프로세스에서 사용
    • 자바 시스템 속성: 자바에서 지원하는 외부 설정, 해당 JVM 안에서 사용
    • 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정, 실행 시 main(args) 메서드에서 사용
    • 외부 파일(설정 데이터): 프로그램에서 외부 파일을 읽어 사용

     

    OS 환경 변수

      OS 환경 변수(OS environment variables)는 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정 값입니다. 다른 외부 설정과 비교해서 사용 범위가 가장 넓습니다.

     

    조회 방법

    • Windows: set
    • Mac, Linux OS: printenv

     

    애플리케이션에서 OS 환경 변수의 값을 읽는 코드는 아래와 같습니다.

    package hello.external;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Map;
    
    @Slf4j
    public class OsEnv {
    
        public static void main(String[] args) {
            Map<String, String> envMap = System.getenv();
            for (String key : envMap.keySet()) {
                log.info("env {}={}", key, System.getenv(key));
            }
        }
    }

     

      OS 환경 변수를 설정하고, 필요한 곳에서 System.getenv()를 사용하면 외부 설정을 사용할 수 있습니다. 예를 들어, 개발 서버에서는 DBURL=dev.db.com으로 설정하고, 운영 서버에서는 DBURL=prod.db.com으로 설정하는 것입니다.

     

      OS 환경 변수는 전역 변수 같은 효과가 있어 이 프로그램 뿐만 아니라 다른 프로그램에서도 사용할 수 있습니다.

     

    자바 시스템 속성

      자바 시스템 속성(Java System properties)은 실행한 JVM 안에서 접근 가능한 외부 설정입니다. 자바 시스템 속성은 다음과 같이 자바 프로그램을 실행할 때 사용합니다.

    • VM 옵션(-D)을 통해서 key=value 형식을 주면 됩니다. 예) java -Durl=dev -jar app.jar: url=dev 속성이 추가됩니다.

     

      자바 시스템 속성을 읽는 코드는 다음과 같습니다. 자바가 기본으로 제공하는 수 많은 속성들이 추가되어 있으며, 사용자가 직접 정의하는 자바 시스템 속성 url, username, password는 실행할 때 추가해야 합니다.

    package hello.external;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Properties;
    
    @Slf4j
    public class JavaSystemProperties {
    
        public static void main(String[] args) {
    
            Properties properties = System.getProperties();
            for (Object key : properties.keySet()) {
                log.info("prop {}={}", key, System.getProperty(String.valueOf(key)));
            }
    
            String url = System.getProperty("url");
            String username = System.getProperty("username");
            String password = System.getProperty("password");
    
            log.info("url={}", url);
            log.info("username={}", username);
            log.info("password={}", password);
        }
    }

     

    IDE에서 실행 시 VM 옵션 추가

    VM options: -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw

     

      jar로 빌드되어 있다면 실행 시 다음과 같이 자바 시스템 속성을 추가할 수 있습니다.

    java -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw -jar app.jar

     

      자바 시스템 속성은 -D 옵션을 통해 실행 시점에 전달하는 것도 가능하지만, 다음과 같이 자바 코드 내부에서 추가하는 것도 가능합니다. 다만, 해당 방식은 코드 안에서 사용하는 것이기 때문에 외부로 설정을 분리하는 효과는 없습니다.

    • 설정: System.setProperty(propertyName, "propertyValue")
    • 조회: System.getProperty(propertyName)

     

    커맨드 라인 인수

      커맨드 라인 인수(Command line arguments)는 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법입니다.

    • 예) java -jar app.jar dataA dataB

     

    IDE에서 실행 시 커맨드 라인 인수 추가

    커맨드 라인 인수는 공백(space)으로 구분합니다. 예) dataA dataB

     

      jar로 빌드되어 있다면 실행 시 다음과 같이 커맨드 라인 인수를 추가할 수 있습니다.

    java -jar project.jar dataA dataB

     

    일반적인 커맨드 라인 인수

    • aaa bbb → [aaa, bbb] 값 2개
    • hello world → [hello, world] 값 2개
    • "hello world" → [hello world] (공백을 연결하려면 "를 사용하면 됩니다.) 값 1개
    • key=value → [key=value] (파싱되지 않은 통 문자  값입니다.) 값 1개

     

    커맨드 라인 옵션 인수(command line option arguments)

      스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링만의 표준 방식을 정의했습니다. 커맨드 라인에 -(dash) 2개 (--)를 연결해서 시작하면 key=value 형식으로 정하고, 이것을 커맨드 라인 옵션 인수라 합니다.

     

      스프링이 제공하는 ApplicationArguments 인터페이스와 DefaultApplicationArguments 구현체를 사용하면 커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있습니다.

    package hello.external;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.DefaultApplicationArguments;
    
    import java.util.List;
    import java.util.Set;
    
    @Slf4j
    public class CommandLineV2 {
    
        //--url=devdb --username=dev_user --password=dev_pw mode=on
        public static void main(String[] args) {
            for (String arg : args) {
                log.info("arg {}", arg);
            }
    
            ApplicationArguments appArgs = new DefaultApplicationArguments(args);
            log.info("SourceArgs = {}", List.of(appArgs.getSourceArgs()));
            log.info("NonOptionsArgs = {}", appArgs.getNonOptionArgs());
            log.info("OptionsNames = {}", appArgs.getOptionNames());
    
            Set<String> optionNames = appArgs.getOptionNames();
            for (String optionName : optionNames) {
                log.info("option arg {}={}", optionName, appArgs.getOptionValues(optionName));
            }
    
            List<String> url = appArgs.getOptionValues("url");
            List<String> username = appArgs.getOptionValues("username");
            List<String> password = appArgs.getOptionValues("password");
            List<String> mode = appArgs.getOptionValues("mode");
            log.info("url={}", url);
            log.info("username={}", username);
            log.info("password={}", password);
            log.info("mode={}", mode);
    
        }
    }

     

      커맨드 라인 인수를 다음과 같이 입력하고 실행하면 옵션 인수(--)와 옵션 인수가 아닌 것을 구분할 수 있습니다.

    --url=devdb --username=dev_user --password=dev_pw mode=on

     

    Note) 옵션 인수는 --username=userA --username=userB처럼 하나의 키에 여러 값을 포함할 수 있기 때문에 appArgs.getOptionValues(key)의 결과는 List를 반환합니다.

     

      스프링 부트는 커맨드 라인을 포함해서 커맨드 라인 옵션 인수를 활용할 수 있는 ApplicationArguments를 스프링 빈으로 등록해둡니다.

    package hello;
    
    import jakarta.annotation.PostConstruct;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    import java.util.Set;
    
    @Slf4j
    @Component
    public class CommandLineBean {
    
        private final ApplicationArguments arguments;
    
        public CommandLineBean(ApplicationArguments arguments) {
            this.arguments = 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));
            }
        }
    }

     

    외부 파일(설정 데이터)

    0. 프로젝트 안에 소스 코드 뿐만 아니라 각 환경에 필요한 설정 데이터도 함께 포함해서 관리합니다.

    1. 빌드 시점에 개발, 운영 설정 파일을 모두 포함해서 빌드합니다.

    2. app.jar는 개발, 운영 두 설정 파일을 모두 가지고 배포됩니다.

    3. 실행할 때 외부 설정으로 넘어온 프로필 값이 dev라면 application-dev.properties를 읽고 prod라면 application-prod.properties를 읽어서 사용합니다.

     

    프로필(profile)

      스프링은 프로필이라는 개념을 지원하며, spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단합니다. 그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 파일(설정 데이터)을 조회합니다.

    • application-{profile}.properties

     

    • IDE에서 커맨드 라인 옵션 인수 실행: --spring.profiles.active=dev
    • IDE에서 자바 시스템 속성 실행: -Dspring.profiles.active=dev
    • Jar실행: java -Dspring.profiles.active=dev -jar external-0.0.1-SNAPSHOT.jar
                    java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

     

      스프링은 물리적인 하나의 설정 파일(application.properties) 안에서 논리적으로 영역을 구분하는 방법도 제공합니다.

    • application.properties 구분 방법: #--- or !--- (dash 3)
    • application.yml 구분 방법: --- (dash 3)
    • 프로필에 따라 논리적으로 구분된 설정 데이터를 활성화 하는 방법: spring.config.activate.on-profile에 프로필 값 지정

     

    (주의) 속성 파일 구분 기호에는 선행 공백이 없어야 하며 정확히 3개의 하이픈 문자가 있어야 하며, 구분 기호 바로 앞과 뒤의 줄은 같은 주석 접두사가 아니어야 합니다.

     

    우선순위

      프로필을 지정하지 않고 실행하면 스프링은 기본으로 default라는 이름의 프로필을 사용합니다. 위 그림의 설정 파일(application.properties)에 기본 값을 추가하면 다음과 같습니다.

    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

      스프링은 단순하게 문서를 위에서 아래로 순서대로 읽으면서 사용할 값을 설정합니다. 이때 기존 데이터가 있으면 덮어씁니다.

     

    우선순위

      같은 값이 있을 경우를 대비해서 스프링은 미리 우선순위를 정해두었습니다.

     

    자주 사용하는 우선순위

    • 설정 데이터(application.properties)
    • OS 환경변수
    • 자바 시스템 속성
    • 커맨드 라인 옵션 인수
    • @TestPropertySource (테스트에서 사용)

     

    설정 데이터 우선순위

    • jar 내부 application.properties
    • jar 내부 프로필 적용 파일 application-{profile}.properties
    • jar 외부 application.properties
    • jar 외부 프로필 적용 파일 application-{profile}.properties

    설정 데이터 우선순위- 스프링 공식 문서

     

    우선순위 이해 방법

    • 더 유연한 것이 우선권을 가집니다. (변경하기 어려운 파일 보다 실행 시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가집니다.)
    • 범위가 넓은 것보다 좁은 것이 우선권을 가집니다. (자바 시스템 속성은 해당 JVM 안에서 모두 접근할 수 있습니다. 반면에 커맨드 라인 옵션 인수는 main의 args를 통해서 들어오기 때문에 접근 범위가 더 좁습니다.)

     

    반응형

    댓글

Designed by Tistory.