Kim-Baek 개발자 이야기

[스프링 핵심원리] 16. 관심사의 분리 본문

개발/Spring

[스프링 핵심원리] 16. 관심사의 분리

김백개발자 2021. 10. 7. 12:57
김영한님의 [스프링 핵심 원리] 강의를 정리하고, 내가 생각한 내용까지 정리하는 포스팅

다시 처음으로 돌아가서 한번 생각을 해보자. 공연에서 각각 인터페이스는 배역이라고 할 수 있다. 그렇다면 이 배역을 연기하는 배우는 누가 선택을 하게 되는게 맞을까?

현재까지 작성한 코드는 로미오와 줄리엣이라는 공연에서, 로미오 역할을 하는 배우인 레오나르도 디카프리오 ( 구현체, 배우 ) 가 줄리엣 역할을 하는 여자 주인공 ( 구현체, 배우 )를 직접 캐스팅하는 것과 마찬가지인 내용이다. 디카프리오는 직접 공연도 하고, 여자 주인공도 캐스팅하는 다양한 역할을 하고 있는 셈이다.

관심사의 분리

- 배우는 자신이 맡은 배역을 수행하는 역할만 수행하는 데 집중하는 것이 맞다. 

- 디카프리오는 상대 여자 주인공이 어떤 사람이 되더라도 동일하게 공연을 할 수 있어야 한다. 

- 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 것 책임을 갖는 공연 기획자가 필요하다

- 공연 기획자를 만들어서, 배우와 공연 기획자의 책임을 분리해서 작성해보도록 하자.

AppConfig 등장

공연기획자의 역할을 하는 것이 바로 AppConfig이다. 애플리케이션의 전체 동작 방식을 구성하기 위해서, 구현 객체를 생성하고, 연결하는 책임을 갖는 별도의 설정 클래스이다.

package core.order;

import core.order.discount.FixDiscountPolicy;
import core.order.member.MemberService;
import core.order.member.MemberServiceImpl;
import core.order.member.MemoryMemberRepository;
import core.order.order.OrderService;
import core.order.order.OrderServiceImpl;

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }


}

AppConfig는 애플리케이션의 동작에 필요한 구현객체를 생성한다. 그리고 생성한 객체의 인스턴스 참조 ( 클래스 내에서 사용하는 애들 )을 생성자를 통해서 주입(연결)한다.

package core.order.member;

public class MemberServiceImpl implements MemberService {

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

MemberServiceImpl을 이렇게 수정한다. 이제 MemberServiceImpl은 MemberRepository 인터페이스만 의존하는 것을 알 수 있다. MemberServiceImpl은 생성자를 통해서 어떤 구현객체가 들어오는지는 알 수 없다. 이제 구현객체는 외부 ( AppConfig ) 를 통해서만 결정된다.

결국 MemberServiceImpl 은 의존관계에 대한 고민은 외부에 맡기고 본인의 역할인 실행에만 집중할 수 있게 되었다.

클래스 다이어그램을 보면 더 이상 구현객체를 의존하지 않게 된다. 객체의 생성과 연결은 외부인 AppConfig를 통해서 이루어지게 된 모습을 볼 수 있다.

이렇게 구현 객체를 의존하지 않고, 인터페이스만 의존하게 되어 DIP가 완성된 모습이다.

회원 객체 인스턴스 다이어그램

AppConfig는 memoryMemberRepoistory를 생성해서, 참조값을 MemberServiceImpl를 생성하면서 생성자에 전달한다.

클라이언트인 MemberServiceImpl 의 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것과 같다고 해서 이를 DI ( Dependency Injection), 의존성 주입이라고 한다.

package core.order.order;

import core.order.discount.DiscountPolicy;
import core.order.member.MemberRepository;

import core.order.member.*;

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

이렇게 주문 서비스도 회원 서비스와 동일하게 생성자를 통해서 의존성을 주입받도록 한다. 더 이상 할인 정책이나 저장에 대한 부분을 직접 결정하지 않아도 된다.

package core.order;

import core.order.member.*;
import core.order.member.MemberService;
import core.order.order.Order;
import core.order.order.OrderService;

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

이제 실행할 때, AppConfig를 통해서 객체를 생성하게 된다. 실행해보면 이전과 동일하게 결과가 정상 출력 되는 것을 알 수 있다. 

정리해보면 AppConfig를 통해서 관심사를 확실하게 분리했다. 각자 역할은 자신이 맡은 실행만 하면 되고, 전체 구성은 외부의 기획자인 AppConfig가 책임을 지게 되는 것이다.

https://github.com/bgc8214/spring-core/tree/step6

 


반응형
Comments