4. AppConfig 등장

임정규·2025년 8월 2일

Spring

목록 보기
4/7
post-thumbnail

1. Why? AppConfig?

지금까지 스프링 프레임워크의 도움없이 회원관리, 주문관리 시스템을 만들고 있다.
주문관리-할인정책 부분에서 변경이나 확장이 있을 경우 구현체 코드를 수정해야한다는 단점이 있다.
할인정책이 고정할인에서 비율할인으로 바뀌려면 구현 객체는 다음과 같이 변경이 된다.

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    //    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

주석처리를 하고 새로운 코드를 넣어주는 변경 작업이 필요하다.
확장에는 열려있고, 변경에는 닫혀있는 OCP에 위배가 된다.
또한 주문 구현체에서 할인 역할만 의존하는 것이 아닌 할인에 대한 구현체도 직접적으로 의존하고 있다. 즉, 추상 클래스에만 의존해야하는 DIP에 위배가 된다.

즉, 목표하고자한 설계는 다음과 같지만,

구현된 것은 다음과 같다.

구현체가 다른 역할에 대한 구현체를 직접적으로 의존을 하고 있기때문에, 다른 역할의 변경내용이 있을 경우 코드 변경이 필요하다.
하지만 지금까지 작성된 코드와 설계에서 해당 문제를 해결할 수 있는 방법이 없다
구현체에서 추상클래스에 구현체를 주입하는 과정을 따로 분리하여 담당하는 것이 필요한데,
그것을 AppConfig로 해결할 수 있다.

2. AppConfig 구성

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(
                memberRepository());
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }

    private DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

추상(역할)클래스를 호출할 때, 의존시킬 구현클래스가 반환값으로 선언이되어있다.
각각 추상 클래스가 어떠한 구현 객체를 호출하는 한눈에 볼 수 있게 AppConfig를 작성하는 것이 키포인트이다.

3. 구현체 사용예시 (OrderServiceImpl)

    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy());
    }

    private DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

AppConfig에서 선언된 의존성 주입이 구현체에서 활용을 하려면 어떻게 해야할까?

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

구현체 내에 생성자를 선언해주어 객체 생성이 될 때, AppConfig에서 미리 정리하여 선언한 의존성을 주입받는다.
이렇게 구현을 하고, 메인에서 서비스를 제공할 때는 다음과 같이 진행이 된다.

public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();

        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);

        System.out.println("order = " + order);
//        System.out.println("order.calculatePrice = " + order.calculatePrice());
    }
}
  1. 주문 서비스를 호출하는 메인에서 AppConfig 객체를 불러오고
  2. OrderServiceImpl 호출할 때, 구현 클래스를 AppConfing에서 불러온다.
    OrderServiceImpl는 어떠한 구현 클래스를 사용하는 지 전혀 모르지만, AppConfig를 통해 의존성 주입이 되어 서비스가 돌아간다.

AppConfig를 통해 DIP, OCP를 만족하면서 서비스를 구현한 것이다.
여기까지 순수 Java에서 DIP, OCP를 AppConfig를 통해 해결해보았다.
다음에는 Spring 프레임워크에서는 어떻게 이 문제를 해결할 수 있게 지원해주는 지 알아보겠다.

profile
아키텍처 설계부터 고민하는 개발자

0개의 댓글