RATSENO

[Spring]Spring boot + Spring Profile을 이용한 환경 별 자원 이용 본문

DEV/SPRING

[Spring]Spring boot + Spring Profile을 이용한 환경 별 자원 이용

RATSENO 2020. 2. 6. 13:52

이번 포스팅을 좀더 쉽게 이해하기 위해서는 간단하게 interface, spring boot, spring profile, 개발 환경의 분리에 대해서 어느정도 지식이 있어야 할것 같습니다.

 

보통 어플리케이션을 개발할때 개발된 어플리케이션을 테스트 할 환경들을 분리하여 진행합니다.

용어는 각 개발환경 마다 다르니 참고만 해주세요

 

1. DEV(개발 환경) : 가장 활발히 수정과 배포가 일어나는 환경입니다.

2. QA(테스트 환경) : 개발한 어플리케이션을 이제 QA환경에 배포하여 테스트를 진행합니다.

3. SANDBOX(샌드박스 환경) : 테스트를 마치고 이제 상용환경에 배포되기 직전의 환경입니다.

4. Live(상용 환경) : 실제 사용자들이 접근할수 있도록 상용환경에 배포합니다.

 

위의 환경들이 추가되거나 삭제되거나 용어는 다를수 있지만 , 보통 이런식으로 환경을 구분지어서 개발을 진행하게됩니다.

이제 개발을 시작하게 되면 다양한 요건들이 들어오게되며 이에 따라 비지니스 로직들이 생겨나게 됩니다.

 

DEV에서 우리는 talk()라는 비니지스 로직을 개발하여 테스트를 진행해왔습니다.

정상적인 경우라면 talk()라는 로직은 QA => SANDBOX => LIVE 까지 변경되는 부분없이 배포가 되어야합니다.

그런데 각 환경별 특이사항으로 인하여 talk()라는 비지니스 로직을 환경별로 분기를 태워야하는 경우가 발생하게 됩니다.

 

저희는 만능의 if()문을 이용하여 talk() 비지니스 로직 안에 환경 별로 분기를 태우기 시작합니다.

//예시입니다.
public String talk(){

	if(env == "DEV"){

	}else if(env == "QA"){
    
    }else if(env == "SANDBOX"){
    
    }else if(env == "LIVE"){
    
    }
	.........//
}

이런식으로 비지니스 로직에 하드코딩식으로 분기가 이루어 지게되면 개발자도 사람이기 때문에 익히 아시는

"아 맞다 깜박했다!"

가 등장하게 되며 유지 보수에 어려움을 겪게 됩니다.

 

그래서 이번 포스팅에서는 항상 보던 xxxxService, xxxxServiceImpl의 유용한 사용법과

spring profile을 이용한 환경별 자원의 이용으로 좀더 깔끔하게 개발을 진행해보겠습니다. (간단하게!)

 

툴은 sts, intellij를 둘중 편하신것으로 진행하시면 됩니다.

저는 둘다 사용법을 익히기 위해서 두가지 모드 진행하였지만 설명은 intellij 기준으로 설명드리겠습니다.(community 버전이라 슬프지만...ㅠ)

 

먼저 spring boot + maven 프로젝트로 생성합니다.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>org.example</groupId>
    <artifactId>spring-profile</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

먼저 간단하게 완성된 프로젝트 구성을 보여드리면 아래와 같습니다.

초기 Spring boot프로젝트를 생성하게 되면 resources폴더 아래 application.properties 파일에 우리는

프로젝트에서 필요한 공통적인 설정들을 작성하게 됩니다. (DB 접속 정보 등등....)

 

spring profile인 이 application.properties 파일을 이용하여 각 환경별로 필요한 설정들을 작성하여 사용합니다.

기존의 spring + maven 프로젝트일 경우 pom.xml 파일에 <profile></profile>로 설정하는것도 훨씬 간단합니다.

 

예시에서는 환경을 dev, qa 두개의 환경이 있다고 가정하여 각 환경별 properties 파일을 생성합니다.

이때 파일의 명명 규칙을 지켜서 생성해야 합니다.

naming convention: application-{profile}.properties

위의 명명 규칙대로 만들어진 properties 파일이 없을 경우 application.properties 파일이 default로 로딩됩니다.

The Environment has a set of default profiles (by default, [default]) that are used if no active profiles are set. In other words, if no profiles are explicitly activated, then properties from application-default.properties are loaded.

명명 규칙대로 프로퍼티 파일을 생성합니다.

  • application-dev.properties

  • application-qa.properties

다음으로 간단한 API를 만들기 위해 보편적으로 만드는 구조로 Controller, Service(interface), ServiceImpl(java)

로 파일들을 생성합니다.

 

먼저 Service 인터페이스를 만들어 메서드를 정의하겠습니다. 간단하게 talk()메서드를 정의합니다.

package com.example.service;

public interface TestService {
    public String talk();
}

이제 저희는 TestService 인터페이스를 구현한 구현체 TestServiceImpl를 생성할겁니다.

package com.example.service;
import org.springframework.stereotype.Service;

@Service
public class TestServiceImpl implements TestService{
    @Override
    public String talk() {
        return "Test 서비스 입니다.";
    }
}

이제 TestController를 생성하여 TestSerice를 호출하는 API를 만들겠습니다.

package com.example.controller;

import com.example.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Autowired
    private TestService testService;


    @GetMapping(value = "/test")
    public String test(){
        return testService.talk();
    }
}

지금 개발한 내용들이 이제 DEV => QA 환경에 배포가 되어서 테스트를 진행하려고 가정하였습니다.

그런데 각 환경별로

DEV 일때는 "/test"를 호출 시  "Dev 서비스 입니다."

QA 일때는 "/test"를 호출 시 "Qa 서비스 입니다."

라는 응답이 내려오도록 해야된다는 요건이 추가 되었습니다.

 

그래서 개발자는 TestServiceImpl에 하드 코딩식으로 if()문으로 분기를 태우자는 생각을 하게됩니다.

@Service
public class TestServiceImpl implements TestService{
    //예시입니다 실제 동작하는 소스가 아닙니다.
    @Override
    public String talk() {
        if(env === "dev"){
           return dev...; 
        }else if(env == "qa"){
            return qa.....;
        }
    }
}

이렇게 하드 코딩식으로 분기를 추가하게 될경우 환경이 추가가 되거나 제거가 되는 여러가지 상황속에서

개발자는 실수를 하게 되고, 큰 문제를 발생하게 되는 확률이 높아지게 됩니다.

 

여기서 interface를 좀더 활용하게 되면 이 문제를 해결할 수 있습니다.

각 환경별로 TestService interface를 구현하는 구현체를 만들겠습니다.

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class DevServiceImpl implements TestService{
    @Override
    public String talk() {
        return "Dev 서비스 입니다.";
    }
}
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class QaServiceImpl implements TestService{

    @Override
    public String talk() {
        return "Qa 서비스 입니다.";
    }
}

구현체의 이름에서 알수있듯이, 각각 DEV, QA 환경에서 사용되도록 Service를 분리하였습니다.

다음으로 

  • application-dev.properties

  • application-qa.properties

각각의 파일에 해당환경에서 사용할 서비스의 이름을 정의합니다.

-application-dev.properties

test.serviceImpl.name=devServiceImpl

-application-qa.properties

test.serviceImpl.name=qaServiceImpl

Controller를 수정합니다.

여기서 수정되는 부분은 TestService 인터페이스 타입으로 자동 주입되는 부분 한 줄 뿐입니다.

package com.example.controller;

import com.example.service.TestService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestController {

    @Resource(name = "${test.serviceImpl.name}")
    //현재 active된 profile의 properties파일에 정의된 serviceImpl의 이름으로 주입
    private TestService testService;


    @GetMapping(value = "/test")
    public String test(){
        return testService.talk();
    }
}

 

위의 소스에서 알수있듯이 현재 실행되고있는 어플리케이션의 환경이 dev 일 경우

spring profile이 해당 환경에 맞는 application-dev.properties 파일에 있는 설정 정보를 가져오게 되며

비지니스 로직에 하드코딩식의 수정없이 각각의 환경에 맞는 serviceImpl을 주입해주게 됩니다.

 

이제 실제로 확인해 보겠습니다. profile을 선택하여 application을 기동하는 방법은 여러가지 있습니다.

저는 spring boot main 클래스에서 프로퍼티를 선택하는 방법으로 진행하겠습니다.

 

먼저 dev환경 일 경우 입니다.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringProfileApplication {
    public static void main(String[] args) {

        System.setProperty("spring.profiles.default", "dev");

        SpringApplication.run(SpringProfileApplication.class, args);
    }
}

 

어플리케이션을 run 후 localhost:8080/test를 호출해보면

2020-02-06 14:01:11.593 INFO 33460 --- [ main] com.example.SpringProfileApplication : No active profile set, falling back to default profiles: dev

run 시 해당 로그로 dev properties 파일이 선택된것을 확인할 수 있고

응답값을 확인할수 있으며

 

다음으로 qa환경으로 변경 후 동일하게 진행하게 되면

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringProfileApplication {
    public static void main(String[] args) {

        System.setProperty("spring.profiles.default", "qa");

        SpringApplication.run(SpringProfileApplication.class, args);
    }
}
2020-02-06 14:04:08.855 INFO 34516 --- [ main] com.example.SpringProfileApplication : No active profile set, falling back to default profiles: qa

위와 같이 qa 프로퍼티가 선택되어 환경별로 다르게 응답값을 확인할 수 있습니다.

 

profile을 선택하는 방법은 여러가지 방법들이 있습니다.

이번 포스팅에서는 최대한 간단하게 확인하게 위해서

애플리케이션 단의 main 클래서에서 설정하였지만,

좀 더 심화된 방법이 있기에 확인해 보시는게 좋습니다.!

 

부족한 포스팅 봐주셔서 감사합니다.

언제나 잘못된 부분 지적은 환영입니다.

 

참고링크 : https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

          https://velog.io/@max9106/Spring-Boot-%ED%94%84%EB%A1%9C%ED%8C%8C%EC%9D%BC-z6k69jc6u0

Comments