Java/Spring으로 만든 웹/앱 프로젝트를 리팩토링하며 개선해나가는 과정을 공유합니다.
사용 버전
- 자바 17
- 스프링 3.2.2
- 인텔리제이
제가 생각하는 리팩토링은 코드에 근거를 쌓아가는 과정이라 생각합니다. 초기 개발 당시 별 이유 없이 다른 사람들이 쓰니까 따라 썼던 코드들을 다시 고민해 보고 개선시키는 작업입니다. 이는 단순히 코드를 정리하고 가독성을 높이는 것 이상으로, 코드가 작동하는 방식을 이해하고 문제를 파악할 수 있습니다.
환경변수 값 바인딩하기
프로젝트를 진행하다 보면 환경 변수의 값을 코드에 적용해야 하는 경우가 생깁니다. 대표적으로 AWS나 카카오 API와 같은 키 값을 예시로 들 수 있습니다. 이러한 값들은 민감한 정보이기 때문에 누구나 열람 가능한 깃허브 레포지토리에 올리면 보안상의 위험이 있으므로 실행 환경에 저장하는 것이 안전합니다.
@Service
public class TestService {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
}
환경변수의 값들을 코드에 사용하기 위해 대부분 @Value를 사용합니다. 이 방식은 편리해서 많은 분들이 선호합니다. 그러나 Bean이 인스턴스화 된 후 BeanPostProcessor에 의해 값이 주입되기 때문에 변수를 final로 선언할 수 없습니다. 운영 중에 값이 변경되면 안 되는 환경 변수의 특성상 이러한 방법은 적합하지 않습니다.
그렇다면 어떻게 사용하는 것이 좋을지 알아봅시다.
1. 생성자 주입
@Service
public class TestService {
private final String accessKey;
private final String secretKey;
public TestService(@Value("${cloud.aws.credentials.access-key}") String accessKey,
@Value("${cloud.aws.credentials.secret-key}") String secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
}
}
@Value를 생성자 주입하는 방법입니다. 생성자 주입은 필드 주입과 다르게 객체 생성 시점에서 필요한 값을 주입합니다. 이렇게 함으로써 필드를 final로 선언하여 변경을 방지할 수 있습니다. 또한 필드 주입에서 발생하는 순환 참조 오류를 사전에 방지할 수도 있습니다.
단점은 의존성이 많아질 수록 생성자의 매개변수가 증가합니다. 이는 곧 코드 가독성과 직결되고 유지보수를 어렵게 만듭니다. 특히 보일러 플레이트 코드로 작성해야 하기 때문에 매우 번거롭습니다.
2. ConfigurationProperties
@ConfigurationPropertiesScan
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
@Getter
@ConfigurationProperties("cloud.aws.s3")
@RequiredArgsConstructor
public class TestConfig {
private final String accessKey;
private final String secretKey;
}
@Service
@RequiredArgsConstructor
public class TestService {
private final TestConfig testConfig;
public void test() {
System.out.println(testConfig.getAccessKey());
System.out.println(testConfig.getSecretKey());
}
}
두 번째 방법은 ConfigurationProperties를 사용한 방법입니다.
최상위 구성 클래스에 ConfigurationPropertiesScan 어노테이션을 붙여서 사용합니다. 장점으로는 설정값을 자바 빈에 매핑하여 중앙 집중화 할 수 있고, 여러 개의 설정 값들을 참조 한 번에 사용할 수 있습니다.
단점으로는 @Value에 비해 코드 작성 시간이 오래 걸립니다. 특히 설정 값이 한 두 개일 때는 더욱 번거롭게 느껴집니다.
설정 값이 적으면 첫번째 방법을, 많으면 두 번째 방법을 사용하는 것이 좋아보입니다.
@Transactional(readOnly = true)
readOnly 속성 사용시 성능이 좋아진다는 얘기를 들은 적 있습니다. 왜 좋아질까 라는 생각 없이 무작정 쓰기만 하다가 문득 궁금해져서 찾아보게 되었습니다. 다음과 같은 장점이 있습니다.
Dirty Checking 우회
JPA의 영속성 컨텍스트(Persistence Context)가 수행하는 Dirty Checking을 사용하지 않게 합니다. 이는 세션 플러시 모드를 FlushType.MANUAL로 설정하여 EntityManager를 닫을 때 더티 체킹을 패스하는 원리입니다. 이로써 영속성 컨텍스트는 스냅샷을 보관하지 않습니다.
Replication 부하 분산
Mysql 이중화 구성(master/slave) 상태에서 readonly 속성을 사용하면 slave DB에서 데이터를 가져오기 때문에 부하 분산 효과가 있습니다.
가독성 향상
@Transactional에 readOnly = true 속성을 명시적으로 사용함으로써 해당 메서드가 읽기 전용임을 명확하게 표시하여 가독성을 향상시킵니다.
하지만 장점만 있는 것은 아닙니다.
- 트랜잭션이 읽기 전용으로 선언되더라도 해당 트랜잭션이 종료될 때까지 커넥션 리소스를 유지하게 됩니다.
- 단순 조회에 스냅샷을 유지하고, 플러시도 해줘야 하는 오버헤드가 발생합니다.
그래서 저는 단순 조회를 제외한 조회 메소드에만 붙이기로 했습니다. 리포트 기능과 같이 여러 데이터를 종합해서 리턴하는 경우에는 단점보다 장점이 크다는 생각입니다.
2부에 계속