프로젝트 기술 스택

spring boot 2.5.4

gradle 7.1.1

mybatis 

 

지난 포스팅에서 언급했 듯이 설정 파일을 분리해서 읽는 방식에는 문제가 있었고, 이를 해결하기 위해 Spring Cloud Config를 적용하게 되었다. 

사실 이전에도 Spring Cloud Config에 대해서 알고 있었고, 지난 여름 방학동안 다른 프로젝트에 이를 적용해보려고 했었는 데 적용하는 데 실패했었다. 

당시에는 해결방안을 몰라서 적용하지 못했었고, 이번에도 그에 대한 두려움이 있어서 다른 방식을 이용하게 된 것이였다. 하지만 이번 기회에 다시 재도전하여 설정을 적용해봤다. 

 

 

Spring Cloud Config란? 

Spring Cloud Config는 외부 설정 정보를 서버에 올려두고, 클라이언트 어플리케이션에서 이를 사용할 수 있는 기능이다. 예를 들어 github의 private repository에 default, dev, prod의 설정 정보를 올려두고, 이 repository를 Cloud Config Server와 연결하면, client application에서 profile에 따라 Clould Config Server의 설정 파일을 이용할 수 있는 것이다. 당연히 서로 다른 client application에서도 같은 서버를 이용한다면, 같은 설정 정보를 이용하는 것도 가능하다. 

 

1. 개발 환경 분리 및 설정 파일 작성

개발 환경(Phase)이란?

개발 환경이란 Application이 동작하는 환경을 의미한다. 일반적으로 프로그램을 개발할 때, 우선 local에서 application을 실행하면서 디버깅이나 테스틀 진행하고, 실제 운영 환경과 비슷한 dev 환경이나, beta, rc 환경에 순차적으로 배포하면서 테스트를 진행하게 된다. 이러한 테스트를 통과한 코드를 운영 환경(prod)에서 배포하게 된다. 

개발 환경 마다 다른 설정을 적용할 수 있도록 Spring Boot에서는 Profile 기능을 제공하고 있다. 

 

각 개발환경에 대한 특징이 궁금하다면 아래의 블로그를 참고하면 좋을 것이다. 

https://ozofweird.tistory.com/entry/%EC%B0%B8%EA%B3%A0%ED%95%98%EA%B8%B0-%EC%A2%8B%EC%9D%80-%EB%82%B4%EC%9A%A9-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EB%B6%84%EB%A6%AC

 

[참고하기 좋은 내용] 개발 환경 분리

1. 개발 환경 분리 1) Local 말 그대로 개발할 때의 각자 개발자 PC 환경을 뜻한다. 이 때 중요한 점은 코드를 합칠 때의 문제가 발생하지 않도록 모든 개발자가 동일한 개발 환경을 사용해야한 다는

ozofweird.tistory.com

https://jungseob86.tistory.com/11

 

[기타] 개발 환경(Phase)이란 무엇일까?

개발 환경(Phase)란? 개발 환경( Phase )은 Application이 동작하는 환경을 의미한다. 따라서 대부분의 Framework는 각각의 개발 환경마다 설정 값을 다르게 셋팅할 수 있는 기능이 있다. 대표적으로 Spring B

jungseob86.tistory.com

 

내가 우리팀에 적용하기 위해 구상한 개발환경은 다음과 같다. 

defualt(local)

: 우리팀의 경우 개발 단계에서 나는 local DB에서 개발을 진행한 다음 서버의 DB에 통합하는 방법을 이용하고 있고, 다른 팀원의 경우 서버의 DB에서 개발을 진행하고 있다. 따라서 defualt 환경의 경우 개발자 마다 별도로 설정 파일을 작성하고, 이를 이용할 수 있도록 구성했다. 

또한 일반적으로도 개발자마다 local DB의 계정명과 비밀번호가 다르다는 점을 고려하면 Spring Cloud Config의 설정 정보를 공통적으로 이용하긴 힘들다는 것이 나의 생각이다. 

결국은 "설정 정보의 민감한 정보 숨기기 1"에서 사용했던 설정 정보를 2가지로 나누는 방식을 유지할 것이다. 

그러나, DB 외의 다른 설정 정보 (예를 틀어 jwt 암호화 키, Open API의 키 값과 비밀번호 등 )은 Spring Cloud Config의 설정을 이용할 수 있도록 구성했다. 

 

wholeinone-defualt.yml

(이때 각 환경별 설정 정보의 파일 이름은 {어플리케이션 이름}-{환경 이름}.yml 형식으로 작성한다. )

config-name: default
searchapi:
  naverid: {client 키 값}
  naversecret: {secret 값}

jwt:
  secret:
    user_info_password_key: {암호화 키}
    token_validity_in_seconds: {만료 시간}

앞서 말했 듯이 DB 정보 외의 설정 정보를 작성한다. 

 

dev

: 운영환경과 유사하게 만든 개발 서버, 서버의 DB를 이용한다. 

 

wholeinone-dev.yml

config-name: dev
spring:
  datasource:
    hikari:
      driver-class-name : {driver}
      jdbc-url : {url}
      user-name : (계정명)
      password : {비밀번호}

searchapi:
  naverid: {client 키}
  naversecret: {secret}

jwt:
  secret:
    user_info_password_key:{암호화 키}
    token_validity_in_seconds: {만료 시간}

참고로 localhost를 기준으로 jdbc-url를 작성하는 방법은 다음과 같다.  

aws의 RDS를 이용하는 경우 localhost대신 RDS의 endpoint를 작성하면된다. 

jdbc-url: jdbc:mysql://localhost:3306/{DB이름}?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&useSSL=false

allowPublicKeyRetrieval=true: mysql 8.0이후로 외부에서 mysql db 접속시 필수로 필요 (기본 false)

useUnicode=true: unicode로 전송한다.

characterEncoding=utf8: utf8로 인코딩한다. 

useSSL=false: ssl 접속을 사용하지 않는다 (기본 값 true)

이외에도 다양한 옵션을 추가할 수 있다. 

 

prod 

: 원래라면 운영 환경이지만, 우선은 front 분들이 이용하는 환경이라고 생각하고 구성했다. dev와 마찬가지로 개발 서버를 이용하지만, 오류 발생시 slack을 통해 오류 메시지를 출력한다는 점이 다르다. 또한 서버에서 배포시 사용하는 환경이다.

(slack 연동 방법은 추후 소개)

wholeinone-prod.yml

config-name: prod
spring:
  datasource:
    hikari:
      driver-class-name : {driver}
      jdbc-url : {url}
      username : {계정명}
      password : {비밀번호}

searchapi:
  naverid: {client 키}
  naversecret: {secret}

jwt:
  secret:
    user_info_password_key: {client 키}
    token_validity_in_seconds: {만료시간}

logging:
  slack:
    webhook-uri: {url}
  config: classpath:logback-spring.xml

jdbc-url에 서버의 DB 연결정보를 적는다. 

 

이렇게 작성한 wholeinone-default.yml, wholeinone-dev.yml, wholeinone-prod.yml 파일을 깋허브 private 저장소에 올린다. 나는 wholeinone-config라는 이름의 저장소에 올렸다. 

 

2. Clould Config Server Application 구현

의존성 추가(bulid.gradle)

dependencies {
   implementation 'org.springframework.cloud:spring-cloud-config-server'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

그리고 본인의 Server 프로젝트에서 main 함수가 있는 클래스에 @EnableConfigServer를 추가한다. 

아래는 예시

@EnableConfigServer
@SpringBootApplication
public class WholeinoneConfigServerApplication {
   public static void main(String[] args) {
      SpringApplication.run(WholeinoneConfigServerApplication.class, args);
   }
}

application.yml

server에서 활용할 설정정보가 저장된 깃허브 계정과 연결을 위한 설정을 작성한다. 

spring:
  application:
    name: configserver
  cloud:
    config:
      server:
        git:
          uri: https://github.com/{깃허브 계정}/{설정 정보를 올린 깃허브 저장소}
          username: {깃허브 계정}
          password: {깃허브 계정의 키값}
        default-label: master //main이 되는 브랜치 (개인마다 다를 수 있으니 확인 필요)

server:
  port: 8082 // Server가 실행될 포트

여기서 password는 깃허브의 personal access token을 입력하면 되는데 발급 방법은 다음과 같다. 

https://record-of-coding.tistory.com/24

 

Github의 access token 발급하기 (원격으로 깃허브 로그인하기)

Settings > Developer Settings > Personal access tokens Github의 우측 상단의 프로필 사진을 클릭하고 Settings 클릭 Settings 페이지의 좌측 사이드바에서 Developer Settings 클릭 Developer settings에서..

record-of-coding.tistory.com

 

서버를 실행하고,

http://localhost:8082/wholeinone/defualt 등으로 get 요청을 보냈을 때 wholeinone-default.yml의 값이 정상적으로 출력된다면 config server가 정상적으로 작동하고 있는 것이다. 

({어플리케이션 이름}-{환경 이름}.yml 형식으로 설정 정보의 파일명을 작성하게 되는 데, http://localhost:8082/{어플리케이션 이름}/{환경 이름}과 같은 url로 get요청을 보내면 된다. )

 

3. Client Application 구현

설정정보를 이용하는 스프링 프로젝트에 spring cloud config의 의존성을 추가한다. 

build.gradle

{
   //Config Server Client
   implementation 'org.springframework.cloud:spring-cloud-starter-config'
}

dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

스프링 프로젝트에서 client의 설정 정보를 읽기 전에 server의 설정정보를 읽어와야 하는데, 이를 위해서 bootstrap.yml을 작성한다. bootstrap.yml은 스트링 부트 프로젝트가 시작할 때 application.yml 보다 먼저 로드된다. 

그런데 bootstrap.yml을 자동으로 읽어오는 기능이 최신의 spring cloud에서 빠지게 되어 의존성을 추가해줘야 한다. 

(사실 전에 구현에 실패했던 이유가 이 의존성을 추가해야 한다는 것을 몰라서였다..)

   implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'

bootstrap.yml

spring:
  cloud:
    config:
      uri: {spring cloud server가 실행되고 있는 uri}:{포트}
      name: {프로젝트 이름}

 

설정 정보 읽기 

로컬의 설정 정보를 읽는 방법과 spring cloud config를 사용하는 방법 2가지를 이용하기 때문에 설정 정보를 읽는 방법도 약간 복잡하게 구성하게 되었다. 

 

설정 정보를 읽는 클래스를 3가지로 구성하게 되었는 데 각 클래스의 기능은 다음과 같다. 

DefaultGlobalPropertyConfig

: defualt 프로필에서 local의 설정 정보를 통해 개발자들마다 서로 다른 설정 정보를 구성하고 읽어 올 수 있다. 

GlobalPropertyConfig

:  dev, prod 프로필에서 사용하는 서버의 DB 정보를 읽어온다. 

SecretPropertyConfig

: default, dev, prod 에서 공통으로 사용하는 secret 값을 설정 정보에서 읽어온다 (예를 들어 jwt 암호화 키 등)

 

DefaultGlobalPropertyConfig

전체 코드는 "설정 정보의 민감한 정보 숨기기 1"과 동일한데, Profile 설정과 RefreshScope 어노테이션만 추가한다.

@Profile("default")는 default 프로필에서 이 빈을 사용하겠다는 의미이다.

@RefreshScope은 서버의 설정 정보에 변경이 생겼을 때 변경 정보를 갱신할 수 있다. 

@Configuration
@Profile("default")
@RefreshScope
@PropertySources({
        @PropertySource( value = "classpath:/properties/config.yaml", ignoreResourceNotFound = true )//db 설정 파일 경로
//        @PropertySource( value = "file:${user.home}/env/dev-config.properties", ignoreResourceNotFound = true) // 배포시 배포 환경의 디렉토리 주소
})

@Getter
public class DefaultGlobalPropertyConfig {
    @Value("${driver-class-name}")
    private String driverClassName;

    @Value("${jdbc-url}")
    private String url;

    @Value("${username}")
    private String username;

    @Value("${password}")
    private String password;
}

 

GlobalPropertyConfig

!default는 default가 아닌 profile에서 이 빈을 이용하겠다는 의미이다. 

@Configuration
@Profile("!default")
@RefreshScope
@Getter
public class GlobalPropertyConfig {
    @Value("${spring.datasource.hikari.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.hikari.jdbc-url}")
    private String url;

    @Value("${spring.datasource.hikari.username}")
    private String username;

    @Value("${spring.datasource.hikari.password}")
    private String password;

}

 

SecretPropertyConfig

모든 프로필에서 공통적으로 사용할 설정 정보를 주입받는다. 

@Configuration
@Getter
@RefreshScope
public class SecretPropertyConfig {
    @Value("${jwt.secret.user_info_password_key}")
    private String userInfoPasswordKey;

    @Value("${jwt.secret.token_validity_in_seconds}")
    private String tokenValidityInSeconds;

    @Value("${searchapi.naverid}")
    private String naverId;

    @Value("${searchapi.naversecret}")
    private String naverSecret;
}

 

문제는 DBConfig 인데, 

원래는 하나의 DBConfig에서 프로필마다 다른 클래스를 이용하도록 구현하고 싶었는 데, 방법을 모르겠어서 그냥 DBConfig도 2가지를 만들게 되었다. 

Default 프로필에서 사용하는 DB 설정

@Configuration
@EnableTransactionManagement
@Profile("default")
public class DefaultDBConfig {
    @Autowired
    DefaultGlobalPropertyConfig defaultGlobalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "dev.spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(defaultGlobalPropertyConfig.getUrl())
                .driverClassName(defaultGlobalPropertyConfig.getDriverClassName())
                .username(defaultGlobalPropertyConfig.getUsername())
                .password(defaultGlobalPropertyConfig.getPassword())
                .build();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "dev.mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

dev, prod에서 사용하는 DB설정 

@Profile("!default")
@Configuration
@EnableTransactionManagement
public class DBConfig {
    @Autowired
    GlobalPropertyConfig globalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(globalPropertyConfig.getUrl())
                .driverClassName(globalPropertyConfig.getDriverClassName())
                .username(globalPropertyConfig.getUsername())
                .password(globalPropertyConfig.getPassword())
                .build();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

 

문제점

앞서 잠깐 언급했듯이 설정 정보를 private 저장소에 올렸음에도 http://localhost:8082/{어플리케이션 이름}/{환경명} 으로 get 요청을 보내면 설정 정보가 그대로 노출되게 된다. 

따라서 이러한 문제점을 해결하기 위해 spring clould config에서는 암호화 기능을 제공한다. 

설정 정보를 암호화하는 방법은 다음에 소개하도록 한다. 

 

후기 

지난 포스팅에서 설정 파일을 2개로 나누는 경우 생기는 문제(1. 설정 방법이 다소 복잡함, 2. 설정 정보의 변경이 어렵고 배포 시 파일 동기화가 불편함)가 있어서 이와 같은 방법을 적용하게 되었는 데, 적어도 설정 정보의 변경 및 동기화가 어렵다는 문제를 해결할 수 있었다.

다만, 개발자마다 다른 설정 정보를 사용할 수 있도록 설계하면서 설정 방법이 더 복잡하게 된 것같다...

만약에 다음에 다른 프로젝트를 하게 된다면 spring cloud config만 이용하여 설정 방법을 간단하게 구성하게 될 것같다. 

그럼에도 지난번에 적용에 실패했던 방법을 이번에 시도하여 설공했다는 것이 개인적으로 의미가 있다. 

 

 

참고 블로그

Spring Cloud Config

https://wonit.tistory.com/502

 

[Spring Cloud Config] Application의 설정 정보 (application.yml) 를 중앙에서 관리하기 (by native repository)

해당 글은 Spring Cloud Netflix Eureka 와 Spring Cloud Gateway 의 Built-in Route로 Predicates와 Filter 조작하기)에 의존하는 글입니다. 실습 환경을 따라하시려면 Eureka와 Gateway 글에 나온 실습을 따라하..

wonit.tistory.com

https://madplay.github.io/post/introduction-to-spring-cloud-config

 

Spring Cloud Config: 소개와 예제

스프링 설정이 바뀌었을 때 빌드, 배포없이 갱신하려면 어떻게 해야할까? 스프링의 설정 파일들을 어떻게 외부로 분리시킬 수 있을까?

madplay.github.io

https://pjh3749.tistory.com/276

 

Spring Cloud Config를 활용하여 설정값(properties), 비밀번호 숨겨서 배포하기

Spring Cloud Config Server 구축하기 Spring Cloud Config 를 사용하여 비밀번호나 민감한 key들을 숨겨서 관리하는 방법을 알아보겠습니다. 전체적인 구상도는 다음과 같습니다. 깃허브 private 저장소에 yml

pjh3749.tistory.com

 

'프로젝트 > 스크린 골프장' 카테고리의 다른 글

전시회 후기  (0) 2022.06.16
[Spring Boot] 설정 파일의 민감한 정보 숨기기 1  (0) 2022.01.15
DB 및 전체 프로젝트 구조  (0) 2022.01.09
UI 프레임워크  (0) 2022.01.09
경쟁 앱 분석  (0) 2022.01.09

Settings > Developer Settings > Personal access tokens

Github의 우측 상단의 프로필 사진을 클릭하고 Settings 클릭

 

Settings 페이지의 좌측 사이드바에서 Developer Settings 클릭

Developer settings에서 Personal access token 클릭 후 Generate new token 클릭

token의 이름, 유효 기간, 권한 등을 선택할 수 있다. 

그리고 Generate token을 클릭하면 다음과 같이 token값이 나타나는 데 이 값을 복사하여 잘 저장해두길 바란다

재발급만 가능하고 같은 token을 다시 볼 수 없다

원격으로 깃허브 로그인이 필요한 경우 이 값을 이용하여 로그인할 수 있다. 

'기타 프로그래밍 팁' 카테고리의 다른 글

Gradle & Maven 라이브러리 찾기  (2) 2022.01.17

프로젝트 기술 스택 및 버전

spring boot 2.5.4

gradle 7.1.1

mybatis 

 

프로젝트 초기 구조

DB password, id, jwt 토큰 암호화 키 등 설정파일의 민감한 정보는 당연하지만 github에 공개적으로 올리면 악용될 여지가 있어, 이러한 민감한 정보는 숨기는 것이 중요하다. 

이러한 민감한 정보를 숨기는 방법에는 여러가지가 있지만, 이번에 적용했던 방법은 설정파일을 2개로 나누는 것이다.

 

처음에 이용했던 방식은 다음과 같다. 

  1. 설정파일을 2개로 분리한다.
  2. 민감한 정보가 들어 있는 설정 파일의 경우 gitignore를 통해서 git에 올리지 않는다. 
  3. @PropertySource를 통해 2개의 설정 파일을 읽는다. 

설정 파일 분리

application.yml : 공통 설정

이 프로젝트는 mybatis를 이용하고 있어, 이에 대한 설정을 추가했다. 

(참고로 현재 프로젝트에서는 mybatis외에도 logback에 대한 설정도 되어 있지만 생략했다)

# MySQL
spring:
  datasource:
    hikari:
      connection-test-query : SELECT NOW() FROM dual
      
# mybatis
mybatis:
  configuration:
    cache-enabled : false
    jdbc-type-for-null : NULL
    map-underscore-to-camel-case : true

config.yml : DB 설정 정보

DB 연결 정보, id, password 등 민감한 정보를 담는다.

그리고 여기서 주의할 점이 username으로 적으면 여기서 설정된 계정명이 아니라 컴퓨터의 유저 이름을 읽게 된다. 따라서 username이 아니라 user-name 등으로 바꾸는 것을 권장한다. 

 

내가 의도했던 것은 username과 password를 통해 팀원과 같은 계정을 사용하기를 원했는 데,

이와 같은 문제떄문에 팀원분이 DB를 연결하는 데 애를 먹으셨다. 

이 글을 읽는 다른 분들은 꼭  username이 아니라 다른 이름으로 설정하시길 바란다. 

# MySQL
spring:
  datasource:
    hikari:
      driver-class-name : com.mysql.cj.jdbc.Driver
      jdbc-url: {url}
      user-name : {계정명}
      password : {비밀번호}

 

설정파일 읽기

@Configuration 어노테이션을 통해 Bean을 생성하고, 

@PropertySources 를 통해 설정파일을 읽는다. 

classpath는 resources 폴더까지를 의미하므로 그 아래부터의 설정 파일의 경로를 적으면 Spring에서 설정파일을 읽을 수 있다. 

그리고 @Value를 통해 값을 주입한다. 

@Configuration
@PropertySources({
        @PropertySource( value = "classpath:/properties/config.yaml", ignoreResourceNotFound = true )//db 설정 파일 경로
//        @PropertySource( value = "file:${user.home}/env/dev-config.properties", ignoreResourceNotFound = true) // 배포시 배포 환경의 디렉토리 주소
})

@Getter
public class DefaultGlobalPropertyConfig {
    @Value("${driver-class-name}")
    private String driverClassName;

    @Value("${jdbc-url}")
    private String url;

    @Value("${user-name}")
    private String username;

    @Value("${password}")
    private String password;
}

 

설정정보 이용하기

@Configuration
@EnableTransactionManagement
public class DefaultDBConfig {
    //설정파일을 읽은 클래스를 생성한다.
    @Autowired
    DefaultGlobalPropertyConfig defaultGlobalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "dev.spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(defaultGlobalPropertyConfig.getUrl())
                .driverClassName(defaultGlobalPropertyConfig.getDriverClassName())
                .username(defaultGlobalPropertyConfig.getUsername())
                .password(defaultGlobalPropertyConfig.getPassword())
                .build();
    }

	// 커스텀한 DataSource를 이용해서 MyBatis와 DataSource 연결
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "dev.mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

 

문제점

그런데 이와 같이 설정을 했는 데 발생했던 문제는 다음과 같다. 

 

1. 다른 팀원이 DB 설정 정보를 작성하기 어려움 

설정파일을 하나 만들고 임의의 폴더 위치에 둔 다음, @PropertySource에 폴더의 주소를 적기만 하면되기 때문에 설정하기 쉬울 것이라고 생각했었는데, 나름 쉽게 설명드렸음에도 다른 팀원 분이 DB 설정을 하기 힘들어 하셨다... 

git ignore로 인해 파일이 공유되지 않다보니 상대방 입장에서는 이해하기 힘들다는 것을 고려하지 못했다. 

이는 아예 설정이 완료된 프로젝트 전체(config.yml 포함)을 따로 전달해드려서 해결하긴 했지만, 처음 의도대로 username이 개인 PC의 username으로 설정되는 바람에 한 번 더 어려움을 겪어야 했다. 

이 문제도 root 계정에서 개인 PC의 username의 계정을 하나 만드는 것으로 해결했었다. 

 

2. 배포시 설정파일 관리의 어려움

API를 서버에 올릴 때, jenkins와 github web hook를 이용해서 배포를 했었다. 

그런데 git ignore로 인해 설정파일이 빠지게 되면서 빌드 시에 에러가 발생했다. 

이는 약간의 꼼수로 서버에 동일한 설정파일을 만들어두고, jenkins에서 빌드 시에 복사해서 사용하도록 했었다. 

그러나 이렇게 관리를 하면 매번 새로운 설정 정보가 생길 경우, 직접 server에 있는 파일을 수정해야해서 매우 번거로웠다. 

 

이와 같은 문제를 해결하기 위해 spring cloud config 를 적용하게 되었다. 

 

프로젝트 깃허브

https://github.com/nh0317/KW-Whole_In_One-Backend

 

GitHub - nh0317/KW-Whole_In_One-Backend

Contribute to nh0317/KW-Whole_In_One-Backend development by creating an account on GitHub.

github.com

 

참고했던 블로그

https://4urdev.tistory.com/48

 

Spring Boot - (6) 민감 정보 숨기기(DB id, password, etc.)

GitHub 소스 위치 : https://github.com/Simplify-study/SpringBootSample.git 여기까지 개발하고 나서 GitHub에 소스 코드를 공유하려고 보니, Database에 대한 접속 정보가 그대로 노출되어 있습니다. applicat..

4urdev.tistory.com

기존 RNN의 경우 계산식과 모델

 

기울기 소멸 문제

RNN을 N번 연결한 경우 

위와 같이 tanh를 너무 여러번 곱하게 되고, 이로 인해 가중치 값이 너무 빠르게 0이 되는 기울기 소멸 문제가 발생한다. 

-> LSTM과 GRU 이용

 

LSTM

Gate를 사용해서 데이터를 가공한다. 

빨간색 데이터만 살리고 싶은 경우,

neural이 중요도를 계산하여 0~1사이값으로 나타난다. (gate coefficient)

그리고 이를 원래 데이터와 곱하면 중요한 데이터만 남고 다른 데이터는 거의 0이 된다. 

-> gate coefficient를 어떻게 계산할 것인가?

 

Gate Coefficient

$g_t\cdot C_t$ 값을 통해 어떤 정보를 남기고 어떤 정보를 없앨지 선택할 수 있다. 

LSTM은 Hidden State 뿐만 아니라 Cell State에서도 정보를 받는다. 

 

LSTM의 동작 과정

$f_t$: 0~1사이의 벡터를 만든다. 

이전 스텝에서 남길건 남기고 현재 단계에서 $C_t$를 만든다.

이전 스텝에서 $f_t$에 의해 남길 값은 남기고 없앨 값은 없앤다. 

이번 스텝에서 $i_t$에 의해 남길 값은 남기고 없앨 값은 없앤다. 

중요한 정보를 판단하는 것도 학습한다. 

-> cell state 업데이트 

 

output으로 내보낼 것만 가공하여 내보낸다. 

그래서 어떻게 기울기 소멸 문제를 해결하는 가?

t시점에서 cell state에 기록된 정보가 만약 지워지지 않고 t+n에서 활용되었을 때 중간에 non-linear activation function을 거치지 않고 t+n 시점까지 흘러오기 때문에 기울기 소멸 문제를 상당히 해결할 수 있다. 

standard RNN의 경우 time을 t+n까지 가려면 무조건 tanh를 통과해야 했는 데, LSTM의 경우 tanh를 통과하지 않고도 t+n까지 흘러 올 수 있다는 것이 다르다. 

 

GRU 

LSTM의 게이트가 너무 많아서 이를 줄이기 위해서 고안됨

 

$h_{t-1}$과 $h_t$를 이용해 무엇을 지울지 계산한다. 

임시 $h_t$와 $h_{t-1}$의 가중치를 둔다. 

그래서 어떻게 기울기 소멸 문제를 해결하는 가?

$h_{t-1}$ 라인이 tanh를 거치지 않고 전달될 수 있다.

-> 기울기 소멸 문제를 해결 가능 

 

결국 tanh를 거치지 않고 값을 전달할 수 있는 channel을 만들어 기울기 소멸 문제를 해결할 수 있다. 

+ Recent posts