* 예제 프로젝트: link:https://github.com/ihoneymon/multi-module[] 애플리케이션을 만들 때 다중 모듈을 가지는 프로젝트를 구성하고 각 모듈의 조합에 따라서 다른 기능을 가지는 애플리케이션(예: 관리자 애플리케이션, API 제공 애플리케이션과 웹페이지를 제공하는 웹 애플리케이션) 배포본을 구성한다. 나는 각 애플리케이션의 기본실행환경은 다음과 같이 구분짓는다: * 로컬(`local`): 개발자 실행환경 * 개발(`dev`): `develop` 브랜치를 기준으로 깃저장소 밀어넣기(push)가 발생날 때 빌드배포 발생 * 스테이지(`stage`): 개발된 기능을 기획자와 확인하기 위한 목적으로 준운영환경에 가깝게 운영 * 운영(`prod`): 실제 사용자에게 기능제공 [NOTE] ==== 프로파일 명칭은 팀이나 기업마다 조금씩 다르겠지만 대략 저런 구조를 가진다고 생각한다. ==== 기존에는 이렇게 구성을 했었고 이와 관련된 설정은 모두 애플리케이션 속성파일(`application.yml` 혹은 `application.properties`)에 스프링 프로파일을 이용하여 구분지었다. 스프링 부트는 애플리케이션 속성 파일에서 사용할 스프링 프로파일을 명시하여 활성화된 프로파일에 따라 다르게 적재하는 기능을 제공한다. 각 모듈별로 이 기능을 활용하여 프로파일로 속성값을 정의해놓고 최종적으로 애플리케이션 실행단계에 적용하는 것이 가능하다. [source] ---- multi-module: 루트 프로젝트 └─ core-module: Entity, Repository 및 Utility 등 └─ batch-module: 배치(batch) 모음 └─ api-module: API 애플리케이션 └─ admin-module: 관리자 애플리케이션 └─ (todo) web-module: 웹 애플리케이션 ---- 위와 같은 구성을 가지는 모듈을 기준으로 설명을 하고자 한다. 애플리케이션 실행환경은 위에서 언급했듯이 '로컬', '개발', '스테이지'와 '운영'으로 구성된다. 각 모듈 하위에는 다음과 같이 애플리케이션 속성파일을 지정한다. [source,java] ---- . ├── admin-module │ └── src │ └── main │ ├── java │ │ └── io.honeymon.springboot.multimodule │ │ └── admin │ │ ├── AdminModuleApplication.java │ │ └── view │ │ └── IndexController.java │ └── resources │ ├── application.yml │ └── logback-spring.xml ├── api-module │ └── src │ └── main │ ├── java │ │ └── io.honeymon.springboot.multimodule │ │ └── api │ │ ├── ApiApplication.java │ │ └── root │ │ └── RootController.java │ └── resources │ ├── application.yml │ └── logback-spring.xml ├── batch-module │ └── src │ └── main │ ├── java │ │ └── io.honeymon.springboot.multimodule │ │ └── batch │ │ ├── BatchModuleApplication.java │ │ └── batchjob │ │ └── SimpleBatchJob.java │ └── resources │ ├── application.yml │ └── logback-spring.xml └── core-module └── src └── main ├── java │ └── io.honeymon.springboot.multimodule │ └── core │ ├── entity │ │ ├── BaseAdmin.java │ │ └── BaseUser.java │ ├── package-info.java │ └── repository │ ├── BaseAdminRepository.java │ └── BaseUserRepository.java └── resources └── application-core.yml ---- 이 파일에서 살펴봐야할 공통적인 속성파일은 ``application-core.yml`` 파일이다. 파일은 각각 애플리케이션 데이터소스(DataSource) 구성과 외부연계API에 사용할 클라이언트 구성에 필요한 인증키 정보를 가지고 있다. .``application-core.yml`` [source,yaml] ---- spring: flyway: enabled: false --- spring.profiles: local spring: datasource: url: jdbc:h2:mem:multi username: SA password: --- spring.profiles: dev spring: datasource: url: jdbc:h2:mem:multi-dev username: dbdev password: dbdevpassword --- spring.profiles: stage spring: datasource: url: jdbc:h2:mem:multi-stage username: dbstage password: dbstagepassword --- spring.profiles: prod spring: datasource: url: jdbc:h2:mem:multi-prod username: dbprod password: dbprodpassword --- spring.profiles: db-prod spring: datasource: url: jdbc:h2:mem:multi-db-prod username: dbprod password: dbprodpassword ---- [NOTE] ==== 별도의 프로파일로 중복되는 속성을 정의하기 보다는 실행하는 환경에 따라서 환경변수로 외부 속성변경방식을 사용하길 바란다. ==== 위 파일을 살펴보면 공통적으로 ``spring.profiles`` 를 정의하였다. ``spring.profiles.active`` 에서 ``{"local", "dev", "stage", "prod"}`` 중에서 활성화된 프로파일을 따라 적재된다. ``application-core.yml`` 파일을 이용하는 ``api-module/application.yml``을 살펴보자: .``api-module/application.yml`` [source,yaml] ---- # commons spring: profiles: include: - core client: id: honeymon key: 20180416 --- spring.profiles: local client: id: honeymon-local key: 20180416-local --- spring,profiles: dev client: id: honeymon-dev key: 20180416-dev --- spring.profiles: stage client: id: honeymon-stage key: 20180416-stage --- spring.profiles: prod client: id: honeymon-prod key: 20180416-prod ---- ``api-module/application.yml``의 구성은 생각보다 간결해진다. ``spring.profiles.include``를 통해서 ``application-core.yml`` 파일을 프로파일로 읽어온다. ``spring.profiles.include``을 이용해서 다른 모듈이나 프로파일로 정의된 애플리케이션 속성파일을 지정할 수 있다. ``api-module/application.yml``에는 로깅레빌을 선언하는 정도의 선언이 기재되어 있을뿐, 데이터베이스 정보와 클라이언트 정보를 기술하고 있지는 않다. [NOTE] ==== 애플리케이션 속성은 애플리케이션 구동 시점에 적재되어 키밸류(Key-Value)의 Map 으로 합쳐지게 된다. 이 과정에서 중요한 것은 활성화하는 프로파일의 순서다. ==== 활성화화는 프로파일의 순서에 따라서 속성값이 달라진다. 즉, 나중에 활성화되는 프로파일의 값이 최종적으로 반영이 된다. 그 예로 다음과 같이 프로파일 순서를 ``{"prod", "db-prod"}``와 ``{"db-prod", "prod"}``로 선언했을 때 값이 변경되는 것을 볼 수 있다. [source,java] ---- @ActiveProfiles({"prod", "db-prod"}) @RunWith(SpringRunner.class) @SpringBootTest public class ProdAndDbProdTest { @Autowired private Environment env; @Test public void testGetDataSourceUrl() { assertThat(env.getProperty("spring.datasource.url")).isEqualTo("jdbc:h2:mem:multi-db-prod"); } } @ActiveProfiles({"db-prod","prod"}) @RunWith(SpringRunner.class) @SpringBootTest public class DbProdAndProdTest { @Autowired private Environment env; @Test public void testGetDataSourceUrl() { assertThat(env.getProperty("spring.datasource.url")).isEqualTo("jdbc:h2:mem:multi-prod"); } } ---- == 정리 * 애플리케이션은 기능에 따라 애플리케이션 속성파일에 프로파일을 정의한다. ** 예: DB 관련: ``application-db.yml`` ** 예: 외부API 호출시 인증: ``application-auth.yml`` * 기능정의 애플리케이션 속성파일을 애플리케이션 속성파일에서 포함시켜 선언한다. + [source,yml] ---- spring: profiles: include: - db - auth ---- * 사전 정의한 배포환경별 프로파일 ``{"local", "dev", "stage", "prod"}`` 외에 별도로 선언이 필요하다면 각 기능별 애플리케이션 속성파일을 추가한다. + .``application-dbprod.yml`` 변경 [source,yml] ---- spring: datasource: url: jdbc:h2:mem:multi-db-prod username: dbprod password: dbprodpassword flyway: enabled: false ---- 추가한 속성파일을 ``spring.profiles.active=prod,dbprod`` 처럼 활성화할 프로파일 마지막에 선언하여 속성을 한번에 변경하는 방법도 유효한 사용방법이다. * [red]#**주의**# :: ``spring.profiles.active``에서 여러 프로파일을 선언할 때 순서에 주의하자. ** 동일한 속성(키)을 정의한 프로파일이 있다면 마지막에 선언된 프로파일 속성값이 적용된다. * `spring.profiles.active=prod` 를 선언했을 때 ** `application-db.yml`: `spring.profile: prod` 속성 적용 ** `application-auth.yml`: `spring.profiles: prod` 속성 적용 ** `application.yml`: `spring.profiles: prod` 속성 적용 ** `application.yml` 포함선언(`spring.profiels.include`)한 각 속성파일(ex: `application-db.yml`, `application-auth.yml`) 내부에 정의한 ``spring.profiles: prod``가 적용된다.