(Spring) Spring Boot 2.0 with Gradle에서 환경 별로 profile 쪼개기
Spring Boot 1.x와 달리 Spring Boot 2.0에서는 Profile 설정하는 게 좀 달라졌다.
알아보자.
최종 결과물은 github 저장소에서 확인할수 있다.
디펜던시
우선 아래 이유로 Lombok을 추가할 것이다.
- Facade 패턴을 이용해서 어떤 로깅 라이브러리에서도 동작할 수 있게 만들어주는 @Slf4j
- DI 할 때 코딩할 양을 줄여줘서 우리의 생산성을 조금이나마 높여주는 @RequiredArgsConstructor
1 | dependencies { |
실행 환경에 따라 분리하기
우선 패키지 구조는 아래와 같이 돼있다고 가정하자.1
2
3
4
5
6
7
8
9
10
11
12- src
- main
- java
- resources
- application-core.properties
- resources-env
- local
- application.properties
- dev
- application.properties
- prod
- application.properties
먼저 local 환경을 위해서 local 디렉토리의 application.properties를 아래와 같이 수정해주자.1
2
3
4spring.profiles.active=local
spring.profiles.include=core
val=local
그 다음에 개발 서버 환경을 위해서 dev 디렉토리의 application.properties를 아래와 같이 수정해주자.1
2
3
4spring.profiles.active=dev
spring.profiles.include=core
val=dev
그 다음에 프로덕션 서버 환경을 위해서 prod 디렉토리의 application.properties를 아래와 같이 수정해주자.1
2
3
4spring.profiles.active=prod
spring.profiles.include=core
val=prod
그 다음에 이제 공통으로 쓸 application-core.properties를 정의하자.1
val2=core
spring.profiles.active=local,core 이렇게 해도 똑같은 결과가 나오는데
spring.profiles.include로 추가적으로 포함될 profile을 설정하는 게 좀 더 의미에 부합하는 것 같아서 설정했다.
그리고 spring boot 2.0의 profile은 기본적으로
- resources/config 디렉토리의 application.properties(혹은 application.yaml 파일)
- resources 디렉토리의 application.properties(혹은 application.yaml 파일)
- classpath/config 디렉토리의 application.properties(혹은 application.yaml 파일)
- classpath 디렉토리의 application.properties(혹은 application.yaml 파일)
을 찾는다.
이제 같은 directory 내에서도 다음과 같은 우선순위로 경쟁을 한다.
- application.properties(application.yaml)를 찾는다.
- application.properties(application.yaml)에서 spring.profiles.active, spring.profiles.include가 설정돼있지 않다면
기본적으로 profile에 default가 setting 되고, 아래와 같은 로그를 볼 수 있다.No active profile set, falling back to default profiles: default
- 설정된 profile에 따라서 application-{profile}.properties(application-{profile}.yaml)을 찾는다.
spring.profiles.active=local, spring.profiles.include=core
의 경우에는application-local.properties(application-local.yaml), application-core.properties(application-core.yaml)
Environment 별로 디렉토리를 쪼개 놨으니 이 디렉토리를 잘 사용하게 끔 build.gradle을 수정하자.1
2
3
4
5
6
7
8
9ext.profile = (!project.hasProperty('profile') || !profile) ? 'local' : profile
sourceSets {
main {
resources {
srcDirs "src/main/resources", "src/main/resources-env/${profile}"
}
}
}
argument로 profile을 넘기는데 없으면 local이 기본으로 profile 변수에 할당된다.
그리고 resources directory는 기본적으로 core property가 포함된 src/main/resources는 디폴트로 포함시키고,
profile에 넘긴 값에 따라서 resources 디렉토리를 설정해서 쓸 데 없는 디렉토리(application.properties 파일도)가 포함되는 걸 방지하게 만들었다.
실행 환경에 따라 코드 작성하기
이제 한 번 각 env 별로 다른 값/클래스를 쓰도록 코드를 작성해보자.
기본이 되는 인터페이스를 만들자.1
2
3
4
5
6
7
8package com.example.demo;
import org.springframework.stereotype.Service;
public interface OrderService {
void order();
}
local env 전용 서비스 구현체를 만들자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
public class LocalOrderService implements OrderService {
private String val;
private String val2;
public void order() {
log.info(val);
log.info(val2);
}
}
dev env 전용 서비스 구현체를 만들자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
public class DevOrderService implements OrderService {
private String val;
private String val2;
public void order() {
log.info(val);
log.info(val2);
}
}
prod env 전용 서비스 구현체를 만들자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
public class ProdOrderService implements OrderService {
private String val;
private String val2;
public void order() {
log.info(val);
log.info(val2);
}
}
어떤 profile에 있는 값을 쓸 것인지 @Profile로 구분할 수 있다.
이 @Profile 어노테이션을 안 쓸거라면 사실상 application.properties에서 spring.profiles.active는 없어도 된다.
간단하게 해당 서비스를 쓰는 코드를 작성해보자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.example.demo;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
public class DemoApplication {
private final OrderService orderService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
public void test() {
orderService.order();
}
}
@RequiredArgsConstructor의 이름을 풀이해보면…
- final이 붙어있으면 무조건(Required) 초기화를 해야하고,
- 생성자(Constructor)를 이용한 DI를 하게 돼서 나중에 모킹할 때도 좋다.
@PostConstruct는 아래와 같은 설명을 보면 된다.
The PostConstruct annotation is used on a method that needs to be executed
after dependency injection is done to perform any initialization.
즉 DI 이후에 실행되는 메서드라고 보면 된다.
실행 환경에 따라 실행하기
project의 specific gradle을 실행하는 Gradle Wrapper를 통해 실행해보자.
터미널을 키고 프로젝트 루트 디렉토리로 이동해서 아래 커맨드를 실행하자.1
2
3
4
5
6
7
8# local
./gradlew bootRun
# dev
./gradlew bootRun -Pprofile=dev
# prod
./gradlew bootRun -Pprofile=prod
위와 같이 실행하면 profile에 따라 아래와 같은 로그를 볼 수 있다.1
22018-07-22 18:46:00.338 INFO 37955 --- [ main] com.example.demo.ProdOrderService : prod
2018-07-22 18:46:00.339 INFO 37955 --- [ main] com.example.demo.ProdOrderService : core
IntelliJ IDEA에서는 아래와 같이 하면 된다.
test profile
1 | - test |
테스트 패키지 구조가 위와 같다고 했을 때 resources 디렉토리에 application.properties를 만들자.1
2
3
4spring.profiles.active=test
spring.profiles.include=core
val=test
그리고 ApplicationTests 클래스에 test 클래스를 사용하도록 수정하자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
public class DemoApplicationTests {
public void contextLoads() {}
}
그리고 test 용 OrderService 서비스를 구현해보자.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
public class TestOrderService implements OrderService {
private String val;
private String val2;
public void order() {
log.info(val);
log.info(val2);
}
}
contextLoads 메서드를 테스트 해보면 아래와 같이 원하는 결과가 로깅돼서 나온다.1
22018-07-22 19:45:31.941 INFO 38688 --- [ main] com.example.demo.TestOrderService : test
2018-07-22 19:45:31.941 INFO 38688 --- [ main] com.example.demo.TestOrderService : core