들어가며
살다보면 실수를 할 때가 있다.
rm -rf ./
를 하려다가 rm -rf /
를 한다던지
where
절 없는 update를 날려본다던지
테스트 DB
조작하려다 운영 DB
에 갈겨본다던지
product application properties (...ddl-auto: create
)
전역한 남자들이 술자리에서 군대 얘기하다보면 내 군생활이 더 힘들었다고 하는데
저 중에 하나라도 달성했다고 하는 개발자가 있다고하면 심심한 위로를 전해줬으면 한다.
나는 이걸 개발자 히든 퀘스트라고 말하고 다닌다.
(히든 업적을 전부 깨고 나면 Delete King
칭호를 얻을 수 있다.)
한번 쯤 겪어보면 좋다.
엔터 한방이 얼마나 신중해지는지..
방어기재가 패시브로 동작한다.
(애초에 권한이 없어서 이런일이 발생하기 어렵기도 하다. 썰을 풀고 싶다면 한번쯤은 ㅎㅎ..)
JPA hibernate.ddl-auto
아무튼 jpa를 사용해보신 분들은 알겠지만 hibernate 프로퍼티 중에 ddl을 생성해주는 놈이 있다.
이놈 또한 아주 지독한 놈이다. 상상 그 이상이다.
일단 이걸 스프링 컨테이너를 부트스트래핑 하는 과정에서 이 옵션에 대한 검증을 진행하고 싶다.
즉 프로퍼티를 읽어서 실제 ddl-auto의 value 값을 가지고 generate ddl을 하기 전에 검증이 완료되었으면 했다.
Environment Abstraction
스프링의 동작 방식을 살짝만 집고 넘어가면 autoconfigure를 통해 environment를 구성한다.
우리가 사용하는
은 가장 후순위이며 컨디션(값의 유무)에 따라 디폴트를 물던 한다.
(높은 우선순위의 프로퍼티를 덮어 쓸 수 없다.)
시스템 변수, 환경변수 등등 우선순위는 있으니까 궁금하면 찾아보시라!
어떻게 해볼까
일단 위에서 말한대로 부트스트래핑 되는 과정에서 ddl-auto의 값을 검증하고싶다.
JPA에 대한 인프라스트럭처 빈이 자동 구성이 되고 나서 검증하면 무슨소용인가;
우선 해볼 수 있는 것들을 생각해본다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
if(args.length > 0 && args[0].equals("prod")) {
System.setProperty("spring.jpa.hibernate.ddl-auto", "none");
System.setProperty("spring.jpa.properties.hibernate.hbm2ddl.auto", "none");
}
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.addInitializers(applicationContext -> {
String[] activeProfiles = applicationContext.getEnvironment().getActiveProfiles();
for (String activeProfile : activeProfiles) {
if (activeProfile.equals("prod")) {
Map<String, Object> replaceDdlAutoProperty = Map.of(
"spring.jpa.hibernate.ddl-auto", "none",
"spring.jpa.properties.hibernate.hbm2ddl.auto", "none"
);
applicationContext.getEnvironment()
.getPropertySources()
.addFirst(new MapPropertySource("HighestPrecedenceProperties", replaceDdlAutoProperty));
}
}
});
ApplicationContext applicationContext = application.run(args);
}
}
예제를 만들어봤지만 둘의 코드는 크게 차이가 없다. 어느정도 스프링에게 위임한 것이다.
일단 둘 다 마음에 들지 않는다. 왜냐면 저 메서드가 호출되는 시점에서는 외부 설정 파일을 읽기 전이다.
PropertySourceLoader가 동작하기 전의 시점이기 때문에 지금 내가 무슨 값을 설정했는지에 대한 검증을 저 단계에서는 비효율적이다.
그러면?
스프링은 많은 후 처리기(Post Processor)를 제공해주는데, Envirionment에 대한 포스트 프로세서 인터페이스가 존재한다. 실제 PropertySourceLoader가 동작할 때 파싱하면 좋겠지만 그 작업은 배꼽이 더 큰 것같다.
이런 저런 핑계로 포스트 프로세서를 활용했다.
final class HibernatePropertiesValidator implements EnvironmentPostProcessor {
private static final String PRODUCTION_PROFILE_NAME = "prod";
private static final String SPRING_JPA_HIBERNATE_DDL_AUTO_PROPERTY = "spring.jpa.hibernate.ddl-auto";
private static final String SPRING_JPA_PROPERTIES_HIBERNATE_HBM2DDL_AUTO_PROPERTY = "spring.jpa.properties.hibernate.hbm2ddl.auto";
private static final List<String> VALIDATION_PROPERTY_NAMES = Arrays.asList(
SPRING_JPA_HIBERNATE_DDL_AUTO_PROPERTY,
SPRING_JPA_PROPERTIES_HIBERNATE_HBM2DDL_AUTO_PROPERTY
);
@Override
public void postProcessEnvironment(
final ConfigurableEnvironment environment,
final SpringApplication application
) {
boolean isProdProfileActive = Arrays
.asList(environment.getActiveProfiles())
.contains(PRODUCTION_PROFILE_NAME);
if (isProdProfileActive) {
validate(environment);
}
}
private void validate(
final ConfigurableEnvironment environment
) {
for (final String propertyName : VALIDATION_PROPERTY_NAMES) {
final String propertyValue = environment.getProperty(propertyName);
if (checkInvalidPropertyValue(propertyValue)) {
throw new IllegalArgumentException(
String.format("In production profile, '%s': '%s' must be set to 'none'. Other values are disallowed."
, propertyName
, propertyValue
)
);
}
}
}
private boolean checkInvalidPropertyValue(
final String propertyValue
) {
// none or validate
return propertyValue != null && (!"none".equals(propertyValue));
}
}
EnvirionmentPostProcessor
를 구현하였다.
이건 위의 코드와 다른 점이 있다면
부트스트래핑하는 포스트 프로세서의 구현 클래스를 META-INF/spring.factories에 적어줘야한다.
// ../resources/META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=com.example.demo.HibernatePropertiesValidator
이렇게 하고
profile을 prod로 선언한 상태로 애플리케이션을 기동해보면
코드에서 한번 더 막아 줄 수 있다.
꼭 이것 뿐 아니라 특정 프로퍼티에 대한 검증 같은 것도 할 수 있겠죠?
마치며
사실 이렇게 중요한 구성 정보를 그 누구도 확인하지 않고 매너리즘에 빠져 운영서버에 올라가면 안된다. (고해성사)
구성 정보가 바뀌면 면밀한 검토(리뷰)가 필요하며, 코드로 방지하는건 완벽할 수 없고 코드로 해결하려 하면 안된다고 생각'은' 항상 합니다.
위에서 말했지만 한번 쯤 날려먹어보는 것도 좋다. (우리는 성인이니까, 책임은 본인이)
하지만 칭호는 희귀할 수록 가치가 있기 때문에 다른 개발자들이 깨지 않길 바라며 이 글을 마칩니다..
좋은 인사이트 감사합니다!! 😋😋