본문 바로가기

개발중/Spring

Spring 환경에서 프로파일 별 효율적 코드 관리 전략

728x90
반응형

 

안녕하세요, 여러분!

오늘은 Spring 환경에서 프로파일(Profile)을 활용하여 코드를 효율적으로 관리하는 전략에 대해 이야기하려 합니다.

 

특히, 코드 유틸리티 클래스를 활용하여 다양한 환경(로컬, 프로덕션 등)에 맞게 유연하게 설정을 관리하는 방법을 중점적으로 다룰 것입니다.

 

공통적인 내용의 중앙화

개발 과정에서 중복되는 코드 관리는 큰 골칫거리가 될 수 있습니다.

이를 해결하기 위해 CodeUtils라는 추상 클래스를 정의했습니다.

이 클래스는 공통적으로 사용되는 코드 목록(initCodeList)을 정의하고, 실제 코드를 조회하는 getCode 메소드를 추상 메소드로 선언함으로써, 구체적인 구현을 상속받는 클래스에 위임합니다.

 

@Component
public abstract class CodeUtils {

	/**코드 초기화 **/
    abstract void init();

	/**코드 조회 **/
    public abstract Map<String, CodeDto> getCode(CodeType codeType);

    public final List<CodeObj> initCodeList = new ArrayList<>() {{

        /** a 코드 정보 **/
        add(new CodeObj("a", "aa", AA, 1));

        /** b 코드 정보 **/
        add(new CodeObj("b", "bb", BB, 1));

        /** c 분류 정보 **/
        add(new CodeObj("c", "cc", CC, 1)); 
    }};

    @Data
    public class CodeObj {
        public CodeObj(String group, String type, CodeType codeType, int sysCode ) {
            this.codeType = codeType;
            this.group = group;
            this.type = type;
            this.sysCode = sysCode;
        }

        String group, type;
        CodeType codeType;
        int sysCode;
    }
}

 

 

프로파일에 따른 구현 분리

Spring의 프로파일 기능을 이용해, 로컬과 프로덕션 환경에서 각기 다른 방식으로 코드 정보를 초기화합니다. 

로컬 환경(LocalCodeUtils)에서는 API 호출을 통해 코드를 직접 가져오고, 프로덕션 환경(RedisCodeUtils)에서는 Redis를 사용해 코드 정보를 관리합니다. 

 

 

이렇게 함으로써, 각 환경에 최적화된 성능과 관리 효율성을 달성할 수 있습니다.

 

@Component
@Profile("local")
public class LocalCodeUtils extends CodeUtils {
    // 로컬 환경에 맞는 구현
}

@Component
@Profile("!local")
public class RedisCodeUtils extends CodeUtils {
    // 프로덕션 환경에 맞는 구현
}

 

 

투명한 코드 조회

이 구조의 가장 큰 장점은 최종 사용자가 내부 로직을 몰라도 코드를 조회할 수 있다는 것입니다. 

사용자는 단순히 CodeUtils 인터페이스를 통해 필요한 코드를 요청하면, 현재 환경에 맞는 구현체가 자동으로 처리해줍니다. 

이렇게 하면 코드를 사용하는 측면에서는 복잡한 내부 구현을 신경 쓸 필요가 없어집니다.

 

구현 코드


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

 
@Component
@Profile("local")
public class LocalCodeUtils extends CodeUtils {

    public static final Map<CodeType, List<CodeDto>> codeMap = new HashMap<>();
    private SoobinApi soobinApi;

    @Autowired
    public LocalCodeUtils( SoobinApi soobinApi ){
        this.soobinApi = soobinApi;
    }

    @PostConstruct
    @Override
    void init() {
        for( CodeObj x : initCodeList ) {
            codeMap.put( x.getCodeType(), soobinApi.getCode(x.group, x.type, x.sysCode) );
        }
    } 

    @Override
    public Map<String, CodeDto> getCode(CodeType codeType) {
        return codeMap.get(codeType).stream().collect(Collectors.toMap(CodeDto::getId, x -> x ));
    }
}

 


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
 
@Component
@Profile("!local")
public class RedisCodeUtils extends CodeUtils {

    private RedisTemplate<String, Object> redisTemplate;
    private SoobinApi soobinApi;

    @Autowired
    public RedisCodeUtils(RedisTemplate<String, Object> redisTemplate, SoobinApi soobinApi) {
        this.redisTemplate = redisTemplate;
        this.soobinApi = soobinApi;
    }


    @PostConstruct
    @Override
    void init() {
        for( CodeObj x : initCodeList ) {
            redisTemplate.opsForHash().putAll(
                    x.getCodeType().name()
                    , soobinApi.getCode(x.group, x.type, x.sysCode)
                            .stream()
                            .collect(Collectors.toMap(CodeDto::getId, this::convertToJson))
            );
        }
    } 

    @Override
    public Map<String, CodeDto> getCode(CodeType codeType) {

        HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
        Map<String, String> rawMap = hashOps.entries(codeType.name());
        Map<String, CodeDto> result = new HashMap<>();

        for (Map.Entry<String, String> entry : rawMap.entrySet()) {
            String key = entry.getKey();
            String jsonValue = entry.getValue();
            CodeDto codeDto = convertFromJson(jsonValue);
            if (codeDto != null) {
                result.put(key, codeDto);
            }
        }
        return result;
    }

    /**
     * 직렬화
     */
    private String convertToJson(CodeDto codeDto) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(codeDto);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 역직렬화
     */
    private CodeDto convertFromJson(String jsonValue) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.readValue(jsonValue, CodeDto.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

 

 

결론

이번 포스트에서는 Spring의 프로파일을 활용하여 다양한 환경에 맞춰 코드를 관리하는 전략을 살펴보았습니다. 

이 접근 방식은 코드 관리의 복잡성을 줄이고, 유지 보수를 용이하게 하는 데 크게 기여합니다. 

또한, 개발자가 환경에 구애받지 않고 코드를 쉽게 조회하고 사용할 수 있게 해줍니다.

Spring을 사용하는 프로젝트에서 이런 전략을 적용해보시길 권장드리며, 이를 통해 더욱 효율적이고 깔끔한 코드 베이스를 유지할 수 있기를 바랍니다. 

 

여러분의 개발 경험이 한층 더 발전하는 계기가 되길 바랍니다. 

 

감사합니다!

728x90
반응형