Spring, Apache, Java

로컬 환경과 운영 환경에 구분될 빈 등록하는 방법

cwchoiit 2024. 6. 30. 20:39
728x90
반응형
SMALL

개발을 하다보면 로컬 환경에서 사용될 빈과 운영 환경에서 사용될 빈이 달라져야 하는 경우가 더러 있다.

예를 들면, 결제 관련 빈은 로컬 환경에서 테스트를 위해 가짜 결제 빈을 등록해서 테스트만을 위해 수행되어야 하고 운영 환경에서는 실제 결제 서비스를 통한 결제가 이루어져야 한다. 이런 경우에 구분된 빈이 스프링 컨테이너에 등록되어야 하는데 이걸 환경에 따라 편리하게 나눌수가 있다.

 

@Profile 애노테이션을 활용하면 된다.

 

다음 코드를 보자.

 

PayClient

package hello.pay;

public interface PayClient {
    void pay(int money);
}

 

LocalPayClient

package hello.pay;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LocalPayClient implements PayClient {
    @Override
    public void pay(int money) {
        log.info("로컬 결제 money={}", money);
    }
}

ProdPayClient

package hello.pay;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ProdPayClient implements PayClient {
    @Override
    public void pay(int money) {
        log.info("운영 결제 money={}", money);
    }
}

 

PayClient라는 인터페이스를 하나 만들고 이를 구현하는 구현체(LocalPayClient, ProdPayClient)를 만들었다.

이 두 구현체를 빈으로 동시에 등록할 순 없다. 왜냐하면 둘 다 PayClient를 구현하는 구현체이므로. (물론 원한다면 할 수는 있다 근데 그게 지금 목적이 아니니)

 

그래서 Configuration 클래스를 하나 만들어보자.

PayConfig

package hello.pay;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Slf4j
@Configuration
public class PayConfig {

    @Bean
    @Profile("default")
    public PayClient localPayClient() {
        log.info("LocalPayClient 빈 등록");
        return new LocalPayClient();
    }

    @Bean
    @Profile("prod")
    public PayClient prodPayClient() {
        log.info("ProdPayClient 빈 등록");
        return new ProdPayClient();
    }
}

두 빈을 등록하는데 @Profile 애노테이션으로 LocalPayClient@Profile("default")일 때 등록되는 구현체다. ProdPayClient@Profile("prod")일 때 등록되는 구현체다. 이렇게 현재 프로필에 따라 빈으로 등록되는 구현체를 지정할 수 있다. 스프링 부트에서 해주는 아주 편리하고 좋은 기능이다.

 

그럼 이제 사용하는 서비스 코드를 보자.

 

OrderService

package hello.pay;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {
    private final PayClient payClient;

    public void order(int money) {
        payClient.pay(money);
    }
}

OrderServicePayClient를 주입받는다. 어떤걸 주입받을지 이 OrderService는 알지 못한다. 이것 또한 유지보수에 좋은 코드이다. OCP원칙. 주입 시점을 이후로 미루는 것.

 

그리고 이 코드를 실제로 호출해서 사용해봐야 하는데 지금은 컨트롤러나 뭐 웹 서버를 띄우는게 아니니까 ApplicationRunner를 구현해서 스프링이 띄워질때 호출되는 코드가 생기도록 해보자.

OrderRunner

package hello.pay;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
 * ApplicationRunner는 이 구현체를 스프링이 뜨는 시점에 자동으로 실행해준다.
 * */
@Component
@RequiredArgsConstructor
public class OrderRunner implements ApplicationRunner {

    private final OrderService orderService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        orderService.order(10000);
    }
}

이렇게 코드를 작성하면 스프링이 띄워질때 이 OrderRunner라는 ApplicationRunner를 구현한 구현체의 구현 메서드인 run()이 호출된다. 프로필을 아무것도 주지않고 (즉, default 프로필) 실행해보자. 실행결과는 다음과 같다.

2024-06-30T20:33:35.161+09:00  INFO 21109 --- [           main] hello.ExternalReadApplication            : No active profile set, falling back to 1 default profile: "default"
2024-06-30T20:33:35.477+09:00  INFO 21109 --- [           main] hello.pay.PayConfig                      : LocalPayClient 빈 등록
2024-06-30T20:33:35.583+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : url: local.db.com
2024-06-30T20:33:35.584+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : username: username
2024-06-30T20:33:35.584+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : password: password
2024-06-30T20:33:35.584+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : maxConnection: 1
2024-06-30T20:33:35.584+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : timeout: PT3.5S
2024-06-30T20:33:35.584+09:00  INFO 21109 --- [           main] hello.datasource.MyDataSource            : options: [CACHE, ADMIN]
2024-06-30T20:33:35.638+09:00  INFO 21109 --- [           main] hello.ExternalReadApplication            : Started ExternalReadApplication in 0.767 seconds (process running for 1.109)
2024-06-30T20:33:35.640+09:00  INFO 21109 --- [           main] hello.pay.LocalPayClient                 : 로컬 결제 money=10000

 

결과를 보면 알 수 있듯, 로컬 결제 빈이 등록되어 실행됐다. 만약 프로필을 `prod`로 주고 실행하면 다음과 같은 실행결과가 도출된다.

2024-06-30T20:38:38.289+09:00  INFO 21334 --- [           main] hello.ExternalReadApplication            : The following 1 profile is active: "prod"
2024-06-30T20:38:38.649+09:00  INFO 21334 --- [           main] hello.pay.PayConfig                      : ProdPayClient 빈 등록
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : url: local.db.com
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : username: username
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : password: password
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : maxConnection: 1
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : timeout: PT3.5S
2024-06-30T20:38:38.743+09:00  INFO 21334 --- [           main] hello.datasource.MyDataSource            : options: [CACHE, ADMIN]
2024-06-30T20:38:38.791+09:00  INFO 21334 --- [           main] hello.ExternalReadApplication            : Started ExternalReadApplication in 0.83 seconds (process running for 1.191)
2024-06-30T20:38:38.792+09:00  INFO 21334 --- [           main] hello.pay.ProdPayClient                  : 운영 결제 money=10000

 

이렇게 현재 프로필을 통해 등록되어야 하는 빈도 나눌 수가 있다.

 

728x90
반응형
LIST