* 예제 프로젝트: 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``가 적용된다.