Spring @ConfigurationProperties 분석

배경

현재 개발하고 있는 것은 ‘서비스의 정산’에 대한 서비스이다. 그런데 정산이라는 것이 결제가 있다면 항상 들어가야 하는 미들웨어와 같은 성격을 지니고 있다.그러다보니 서비스의 설정 정보들이 각 프로젝트에 혼재해있어서 정리를 해보려고 한다.

 

현재 상황

전체적으로 정산에 대한 공통 부분을 사용하기 위해 settlement-common이라는 모듈을 사용하고 있다. 각 서비스에서는 settlement-common에 대한 의존성을 연결하여 사용한다. 그런데 정산에서 사용하는 property 정보들이 모두 하위 서비스에서 정의하여 사용하고 있다. 예를 들면 erp.url은 하위 서비스의 application.yml에 정의되어 있다.

 

하고 싶은 것

보통의 경우에는 common 공통의 설정을 로딩해서 사용하고, 일부의 케이스에 대해서만 하위 서비스에서 설정을 overriding하고 싶었다. application.yml에 다른 설정과 함께 섞여 있는 것도 제거하고 싶었다.

 

@ConfigurationProperties 사용

아래처럼 Property정보를 Bean으로 등록해서 사용하는 것이 @Value를 사용하는 것보다 나을 거란 생각을 했다.
@Component가 없어도 Bean으로 사용할 수 있는데 굳이 쓴 이유는, IDE에서 Bean Mapping이 안된다는 waring이 발생하는 게 싫어서이다. 그리고 @ConfigurationProperties는 setter를 이용해서 값을 주입하는 것도 약간은(?) 신기한 부분이다. 최근 스프링의 추세가 reflection을 통한 강제 주입이라서.

 

파일분리 (application.yml -> settlement.yml)

애사당초 얘기했던 것처럼, 하위 프로젝트의 application.yml에서 모든 것을 정의해서 사용하면, 어떤 property를 어떤 모듈에서 사용하는지 파악하기 힘들고, 이후에 리팩토링할 때에도 장애물이 된다. 그래서 @ConfigurationPropertieslocations attribute를 활용하여 파일을 분리하였다.

 

locations attribute deprecated

그런데 locations attribute가 deprecate되었다. 심지어 최신 버전의 springboot에서는 아예 제거되었다. 1 그래서 별로 권장하지 않는다고는 하지만, @PropertySource를 활용하기로 했다.

 

spring.active.profile 기능 활용

아래처럼 springEL을 활용해서 각 profile별로 필요한 설정을 import할 수 있다. 다만 같은 값의 overriding의 경우 선언한 순서에 영향을 받는다. 2 특히 아래와 같은 경우에는 @PropertySource에 선언된 Array 중에 나중에 선언된 것이 override된다.

 

문제 발생, yml 파싱 불가

위처럼 로직을 짜고 테스트를 돌려보면, NPE가 발생한다.
이유인즉슨, Bean이 제대로 setting이 되지 않았기 때문인데, 디버거를 통해 확인했더니 yml 파일을 인식하지 못한다. 3 그래서 yml 파일을 사용할 수 있도록 Factory 클래스를 추가 구현하였다.

 

yml 파싱은 잘되는데, profile은 어떻게…

application.yml처럼 Array 형식으로 정의하고 profile별로 property를 꺼내 쓰고 싶은데, 위의 YamlPropertySourceFactory를 보면, profile을 null로 넘겼다. 해당 profile을 넣어주면 제대로 파싱될 것 같은데, Factory 파일이 bean도 아니라서 Environment를 사용할 수 없고 어떻게 해야할지는 좀더 생각해봐야 할 것 같다.

 

하위 서비스에서 설정 완료

하위 서비스에서는 아래처럼 추가 설정을 overriding해서 사용할 수 있다.

 

또 다른 문제, 파일이 없을 경우

그런데 settlement-common은 하위 프로젝트들의 모든 spring.profiles를 알 수 없다. 그래서 정의되지 않은 profile이 들어올 경우, 리소스를 읽을 수 없다면서 settlement-${spring.profiles.active}.yml 구문에서 오류가 발생한다. 그래서 아래처럼 @PropertySource를 분리하고, ignoreResourceNotFound 속성을 추가해서 처리하였다.

그런데 계속 오류가 발생한다. 디버거를 통해서 확인하였더니, YamlPropertySourceFactory내에서 YamlPropertySourceLoader를 사용하고 있는데, 여기에서 리소스가 없는 경우에 대해 기존의 경우와 다르게 return을 하고 있다. 결국 NPE가 발생한다. (기본 factory를 사용하는 경우에는 문제없음)

 

결론

yml 포맷이 훨씬 구조화된 형식이라 properties 파일보다 권장되기도 하는데, 사용하려니 현실적인 벽에 많이 부딪혀서 properties 파일을 우선 사용하는 것으로 선회하였다. 끝까지 고쳐보지 못하고, 실패를 결론으로 기록하려니 좀 아쉽긴 한데, 시간도 시간인지라… 나중에는 다시 볼 일이 생기지 않을까 싶은 생각으로 일단 여기까지 기록해둔다.

 

추가1

resource 파일은 classpath로 복사될 때, 같은 이름이면 우선순위에 따라 overwrite된다. 즉, application.properties 또는 application.yml 파일이 settlement-common에 있는데, 하위 모듈에서 동일한 파일이 있는 경우에는 내용에 상관없이 하위 모듈의 것을 사용하게 된다. 심지어 해당 property가 없는 경우라 할지라도 common의 것을 사용할 수 없다. override가 아니라 파일이 overwrite되는 것에 유의!

추가2

파일이 다르고 키가 같은 property의 경우에는 해당 값을 우선순위에 따라 override한다.

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다