[Spring] 기본_스프링 핵심 원리 이해

gayoung·2022년 3월 13일
0

스프링 완전 정복

목록 보기
18/33

[ 주문서비스 예제를 활용한 스프링 핵심 원리 이해 ]

1. 주문서비스

  • 할인정책은 정액 할인정책, 정률 할인정책으로 두가지 있음

1-1.주문 도메인 협력, 역할, 책임

1-2. 주문 도메인 전체

1-3. 주문 클래스 다이어그램

1-4. 주문 서비스 구현체

public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
}
  • 역할과 구현 구분 OK
  • 문제점 : 할인정책을 변경하기 위해서는 클라이언트인 OrderServiceImpl를 수정해야한다.
    • 위처럼 자바 코드를 작성하면, OCP와 DIP 원칙에 어긋남
    • 기능을 확장하면(할인정책 또는 회원 저장소 변경) 클라이언트 코드에 영향을 주기 때문에 OCP를 위반
    • 주문 서비스 클라이언트(OrderServiceImpl)은 DiscountPolicy 인터페이스를 의존하는 것과 동시에 구현 클래스인 FixDiscountPolicy 혹은 RateDiscountPolicy에도 의존하고 있기 때문에 DIP 위반
  • 실제 현 코드의 의존관계
  • 우리가 기대한 의존관계

1-5. 인터페이스에만 의존하도록 변경한 주문 서비스 구현체

public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
}
  • 이렇게 되면 구현체가 없기 때문에 NullPointerException 에러발생 -> 누군가가 클라이언트인 OrderServiceImpl에 MemberRepository, DiscountPolicy의 구현객체를 대신 생성하고 주입해줘야함

2. 관심사 분리_AppConfig

  • 배우는 연기만, 공연구성+배우섭외하는 공연기획자 필요 -> 이 둘의 역할 분리 필요!
  • OrderServiceImpl는 MemberRepository, DiscountPolicy의 구현객체를 가지고 주문 로직 수행하는 역할
  • AppConfig는 각 인터페이스에 어떤 구현객체가 들어가야하는지 정하기

2-1. AppConfig를 이용해 클라이언트가 인터페이스에만 의존하게 만든 코드

public class AppConfig {
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
    
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        ths.discountPolicy = discountPolicy;
    }
}
public class OrderApp {

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

        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP
        Order order = orderService.createOrder(memberId, "itemA", 20000);

        System.out.println("order = " + order);
    }
}
  • 더이상 OrderServiceImpl은 구현 클래스에 의존하지 않음(생성자를 통해 어떤 구현 객체가 주입될지 알 수 없음)
  • AppConfigRateDiscountPolicy() 생성 후 OrderServiceImpldiscountPolicy에 생성+주입 진행
  • AppConfig에 의해 어떤 구현 객체가 주입되는지 확인 가능 -> 코드 변경이 AppConfig에만 발생

3. IoC, DI, 그리고 컨테이너

3-1. 제어의 역전 IoC(Inversion of Control)

  • 기존 프로그램: 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행(구현 객체가 프로그램의 제어 흐름을 스스로 조종)
  • AppConfig: 구현 객체는 자신의 로직을 실행하는 역할만 담당. 프로그램의 제어 흐름은 AppConfig가 함
  • 제어의 역전(IoC) : 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부(AppConfig)에서 관리하는 것

3-2. 의존 관계 주입 DI (Dependency Injection)

  • OrderServiceImplDiscountPolicy 인터페이스에만 의존 + 어떤 구현객체가 사용되는지 모름

  • 의존관계는 정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 함

    • [ 정적인 클래스 의존 관계 ]
      • 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있음
      • OrderServiceImpl 은 MemberRepository , DiscountPolicy 에 의존한다는 것을 알 수 있지만, 실제 어떤 객체가 OrderServiceImpl 에 주입 될지 알 수 없음
      • 클래스 다이어그램
    • [ 동적인 클래스 의존 관계 ]
      • 애플리케이션 실행시점에 외부에서 실제구현객체 생성하고 클라이언트 전달 -> 의존관계 연결
      • 객체 인스턴스 생성 + 그 참조값을 전달해 연결
      • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를쉽게 변경할 수 있음(클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있음)
      • 객체다이어그램

3-3. IoC 컨테이너, DI 컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것

4. 스프링으로 전환하기

4-1. 스프링 컨테이너 개념

  • 스프링 컨테이너 = ApplicationContext
  • 기존에는 AppConfig로 직접 객체 생성 + DI진행했지만, 이제 스프링 컨테이너 이용
    • @Configuration = 구성정보
    • @Bean이 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록 -> 스프링 컨테이너에 등록된 객체 = 스프링 빈
    • 스프링빈 이름 = @Bean 이 붙은 메서드의 명 ( memberService ,orderService )
    • 스프링 빈은 찾는 방법 : applicationContext.getBean()
    • 장점
      • 역할과 구현 클래스가 한 눈에 드러남
      • 만약 나중에 DB가 설정되거나 다른 구현체로 변경이 필요할 때 memberRepository, DiscountPolicy부분만 변경하면 됨

4-2. 코드

[ AppConfig ]

@Configuration
    public class AppConfig {
    
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

[ OrderApp ]

public class OrderApp {
    public static void main(String[] args) {
        // AppConfig appConfig = new AppConfig();
        // MemberService memberService = appConfig.memberService();
        // OrderService orderService = appConfig.orderService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

		// 이전과 동일
        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);
 }
}

0개의 댓글