본문 바로가기
Spring/스프링 핵심 원리 기본편

스프링 핵심 원리 기본편 - 주문과 할인 도메인 설계, IoC

by latissimus 2022. 8. 30.

인프런 김영한님의 스프링 핵심원리 - 기본편을 학습하고 정리한 내용입니다. 학습 목적의 정리로 모든 강의 내용을 정리하진 않습니다. 틀린 부분 지적해주시면 감사하겠습니다.

 

실제 강의에는 리팩터링 과정이나 세부과정과 좋은 설명들이 많습니다. 강의를 꼭 들어보시길 추천드립니다.

 

주문 도메인

스프링 핵심원리-기본편 강의자료

먼저 위와 같은 설계가 있다. Member객체는 번호, 이름, 등급(id, name, grade)을 가진다. 서비스에서는 들어온 주문을 바탕으로 MemberRepository에 저장된 내용에서 회원등급을 읽어오고, 해당 등급에 따른 할인 정책을 찾아서 적용시킨다.

 

여기서 주안점은 이전 글에 서술한 SOLID를 지키면서 프로그래밍 하는 것이다. 그래서 인터페이스의 다형성을 활용한다. OrderService를 추상화(인터페이스)에 의존하게 해서 기능을 추가하면 구현만 변경하도록 의도하는 것이다. 

 

SOLID를 지키면서 구현한다면, 위의 설계처럼 간단하게 저장소나 할인정책을 교체할 수 있다.

 

문제점 - SOLID 위반

하지만 실제로 구현하면 문제가 생긴다.

스프링 핵심원리-기본편 코드 : 주문 서비스 구현체

고정할인정책(FixDiscountPolicy)를 적용중이던 코드에 정률할인정책(RateDiscountPolicy)를 적용하려고 한다. 그런데 여기서 문제가 발생한다.

 

OCP 위반

RateDiscountPolicy라는 코드를 추가해야할 뿐만아니라, OrderService에 코드를 변경해야한다. 기존의 FixDiscountPolicy객체 생성을 주석처리하고 RateDiscountPolicy생성으로 교체한 것을 볼 수 있다. 인터페이스와 다형성을 활용해서 구현했음에도 불구하고, 기능을 변경하는데 기존의 클라이언트 코드(OrderService)가 변경되는 것이다.

 

DIP 위반

잘보면 DisountPolicy(추상화, 인터페이스) 뿐만 아니라 RateDiscountPolicy(구현체)에도 의존하고 있다. DIP에서 추상화에만 의존하라고 했는데, 둘 다 의존하고 있다.

스프링 핵심원리-기본편 강의자료

기대했던 의존관계는 추상화만 의존하는 것이다. 검은 색으로 표시된 부분이다. 하지만 실제로는 인터페이스와 구현체를 모두 의존하고 있다. 그렇다고 인터페이스만 의존하면, 아무런 기능이 없기때문에 동작할 수 없다. 어떻게 해결할까?

AppConfig 

이를 해결하기 위해 AppConfig(설정 파일)을 활용한다.

스프링 핵심원리-기본편 코드 : 주문 서비스 구현체

먼저 OrderService 구현체의 생성자의 인수로 저장소와 정책을 받도록 구현한다.

스프링 핵심원리-기본편 코드 : AppConfig

그 다음 설정 파일을 다음과 같이 구현한다. AppCofig(설정 파일)는 특정 Repository와 Policy객체를 생성를 생성한다. 그리고 서비스 객체들의 생성자에 생성한 참조값을 인수로 넘긴다. 생성자를 통해서 어떤 저장소를 사용할지, 어떤 할인정책을 사용할지 주입해주는 것이다.  이것을 생성자 주입이라고 한다.

스프링 핵심원리-기본편 강의자료

설정파일을 통해 위와같이 구조를 바꾸는 것이다. 객체를 생성하고 주입하는 책임을 AppConfig가 가져가면서, 다른 클래스들의 책임이 적어지고 명확해진다.

 

설정 파일이 객체를 생성하고 주입하는 역할을 대신해주는 것이다. 이렇게 AppConfig를 활용하면 사용 영역을 변경하지 않고, 구현체의 교체가 필요할때 구성 영역만 변경하면 된다. 그러므로 클라이언트의 코드(주문 서비스 구현)도 변경이 없다.

 

SOLID 점검

SRP

설정 파일의 추가로 클래스의 책임이 좀 더 명확해 졌다. 주문을 하는 단일 책임만 가지게 되었다.

  • 설정 이전
    • 주문 서비스 (OrderServiceImpl)
      • 저장소 생성 -> Id로 Member 찾기
      • 할인정책 생성 ->  할인율 계산
  • 설정 후
    • 주문서비스 (OrderServiceImpl)
      • Id로 Member 찾기
      • 할인율 계산
    • 설정 파일 (AppConfig)
      • 저장소 생성 및 주문 서비스에 생성자 주입
      • 할인정책 생성 및 주문 서비스에 생성자 주입

DIP

추상화에 의존하고 있다. 정확이 외부에서 구현체를 선택해서 주입해주기 때문에 가능하다.

OCP

정책을 변경(FixDiscountPolicy -> RateDiscountPolicy)했지만 바뀐것은 설정 코드(AppConfig)뿐이다. 사용 영역은 변경에 닫혀있다. 물론 클라이언트 코드도 변경에 닫혀있다.

 

IoC(Inversion of Control)

스프링의 특징 중 하나인 IoC는 제어의 역전이라는 의미다. 이것을 이해하기 위해 순수 자바로 간단한 프로그래밍을 해보자.

class Hello {
    public void hello(){
        int a = 10;
        System.out.println(a);
        System.out.println("Hello world!");
    }
}

방금 만든 간단한 코드이다. 코드를 보면 내가 구현한 코드들이 제어 흐름을 스스로 조정한다. a라는 변수에 10을 넣고, 원하면 출력도 한다. 해당 메서드를 호출하면 무엇이 일어날지 알고 있다. 제어의 흐름을 내가 가지고 있는 것이다.

 

그렇다면 위에서 사용했던 AppConfig의 내용과 주문 서비스의 내용을 다시한번 확인해보자.

스프링 핵심원리-기본편 코드 : 주문 서비스 구현체, AppConfig

OrderServiceImpl은 어떤 저장소를 사용할지, 어떤 할인정책을 사용할지를 AppConfig에게 주입받는다. 즉, OrderServiceImpl는 필요한 인터페이스들을 호출해서 사용하지만, 어떤 구현 객체들이 실행될지 모른다. 심지어 AppConfig가 다른 객체를 생성해도 아무것도 모른체 주입당한다.

 

결국에는 프로그램에 대한 제어 흐름을 AppConfig가 쥐고 있는 것이다. 이런 상황을 두고 제어의 역전이라고한다. 말 그대로 제어권이 없고 외부에서 관리하는 것이다. 보통 프레임워크는 제어의 역전이 특징이다.

라이브러리와 프레임워크의 개념이 항상 헷갈리곤 한다. 보통 제어의 역전이 일어나면 프레임워크, 제어 흐름이 프로그래머에게 있으면 라이브러리이다. 대표적으로 JUnit은 프레임워크이다. 우리가 틀 안에 테스트 코드를 작성하면 JUnit이 대신 실행하고 제어한다.

 

IoC 컨테이너

AppConfig같이 객체를 생성하고 관리하면서 의존관계를 연결해주는 역할을 하는 것을 IoC컨테이너라고 한다. IoC라는 용어는 스프링에 국한되는 용어가 아니기때문에, 너무 범용적이다. 보통 의존관계 주입의 의미에서 DI컨테이너라는 용어를 쓴다고 한다.

댓글