[Spring] 관심사 분리

앞서 생성한 OrderServiceImpl 클래스를 잠깐 살펴보자. 이 클래스는 주문 서비스에만 관련된 로직만을 수행해야 하는데, 이 안에서

private DiscountPolicy discountPolicy = new FixDiscountPolicy();

구문을 통해 할인 정책 객체를 직접 생성을 하고 "FixDiscountPolicy"(구체적인 정책) 또한 사용하겠다고 직접 선택하게 된다. 

즉, 클래스의 역할만 수행하는 것에 집중이 되어야한다. 

 

이를 위해 애플리케이션의 전체적인 동작 방식을 구성하기 위해, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스를 만들어 줄 것이다. 

Appconfig

애플리케이션의 운영에 대한 전반적인 부분을 책임지는 매우 중요한 클래스이다. 

hello.core 아래에 AppConfig 클래스를 생성하자.

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

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);
    }
}

 

먼저, MemberService를 살펴보자. 

이전에는 객체를 생성을 하고 인터페이스에 어떤 것이 들어가야하는지를 MemberServiceImpl에서 직접 수행했었다. 

private final MemberRepository memberRepository = new MemoryMemberRepository();

에서와 같이 직접 MemoryMemberRepository를 사용할 것이라 지정했다. 이것을 Appconfig에서 모두 수행할 것이다.

 

MemberServiceImpl에서 구현체를 할당하는(new) 부분을 지우고 생성자를 하나 만들자. 이 생성자를 통해서 MemberRepository에 구현체가 무엇이 들어갈지 선택을 할 것이다. 

수정한 MemberServiceImpl 클래스의 코드는 다음과 같다.

package hello.core.member;

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

이 코드를 보면, MemoryMemeberRepository에 대한 내용이 없다. 오로지 MemberRepository 인터페이스만 존재한다. 즉, 추상화에만 의존, DIP를 지키는 것을 볼 수 있다. 구체적인 것에 대해서는 전혀 모르는 상태이다. 구현체는 밖(Appconfig)에서 생성해서 넣어주는 방식이 된다. 이를 생성자 주입 이라 한다. MemberServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다. 

 

이와 같이, OrderServiceImpl 클래스 또한 수정하자.

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;

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는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.

  • MemberServiceImpl
  • MemoryMemberRepository
  • OrderServiceImpl
  • FixDiscountPolicy

또한, AppConfig는 생성한 객체 인스턴스의 참조(래퍼런스)를 생성자를 통해서 주입(연결) 해준다.

  • MemberServiceImpl → MemoryMemberRepository
  • OrderServiceImpl → MemoryMemberRepository + FixDiscountPolicy

위의 내용들을 바탕으로 전체적인 클래스 다이어그램을 그려보면 다음과 같다.

객체의 생성과 연결은 AppConfig가 담당한다. 

MemberServiceImpl은 MemberRepository인 추상(인터페이스)에만 의존하면 된다. 구현 클래스를 몰라도 되는 것이다. 이로써 DIP가완성되었다. 

 

클라이언트인 memberServiceImpl의 입장에서 보면 의존관계를 마치 외부에서 주입하는 것과 같다하여 이를, DI(Dependecy Injection)이라 한다. 

 위 수정된 클래스들을 사용하는 다른 클래스들을 모두 수정해주자. 이때 AppConfig 객체를 생성하게 된다. 

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

 

테스트 코드 수정

테스트 코드도 수정해주자.

public class MemberServiceTest {

    MemberService memberService;

    @BeforeEach
    public void beforeEach(){
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }

    @Test
    void join(){
        ...
    }

}
@BeforeEach 는 각 테스트 코드 실행 전에 무조건 실행되는 코드이다. 

이번 포스팅을 통해서 AppConfig를 이용해 관심사를 확실하게 분리했다. 이제 각 클래스들은 각자의 기능을 실행만을 고민하면 된다. 

다음 포스팅에서는 AppConfig 클래스를 조금 수정(Refactoring)해보자.