기록하는 습관

스프링 핵심 원리 - 기본편 (2) 스프링 핵심 원리 이해 본문

개발/Spring

스프링 핵심 원리 - 기본편 (2) 스프링 핵심 원리 이해

로그뉴 2021. 6. 26. 14:09

[ 들어가며 ]

  • 이번 프로젝트는 순수 Java를 활용한 코드로, Spring 요소가 들어가지 않음.
  • DI와 DI Container가 어떤 역할을 하는지 아는 것이 핵심.
  • SOLID 관점에서 DI 적용 전/후를 비교해서 보는 것이 핵심.

 

[ 비즈니스 요구사항과 설계 ]

  • 회원
    • 회원을 가입하고 조회할 수 있다.
    • 회원은 일반과 VIP 두 가지 등급이 있다.
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있 다.)
    • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

[ 도메인 설계 ]

1. 회원 (Member)

  • 클라이언트는 '회원 서비스'를 바라보고, 회원 서비스는 '메모리 회원 저장소'를 바라본다.
  • 클래스 다이어그램은 동적 상황을 반영하지 않는다.
  • 따라서, 객체 다이어그램이 필요하다. 서버가 돌아가는 상황을 반영한 것이 객체 다이어그램이다.

 

2. 주문  (Order)

  • 주문 도메인 전체
  • 역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계

 

주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 적용한 다음 주문 객체를 생성해서 반환한다. 

메모리 회원 리포지토리와, 고정 금액 할인 정책을 구현체로 생성한다.


[ DI 적용 전 ]

MemberServiceImpl.java

  • DB가 변경되면 MemberServiceImpl 코드는 수정된다.
    • OCP 위반 : DB가 변경될 때 new 뒷 부분을 직접 변경해야 함 (이건 로미오가 줄리엣을 직접 초빙하는 것과 같음)
    • DIP 위반 : 인터페이스를 의존하지만 구현체 역시 의존한다.
      • 예) MemberServiceImpl이 MemberRepository, MemoryMemberRepository에 의존하게 되므로.

 

OrderServiceImpl.java

  • 할인 정책이 변경되면 클라이언트인 OrderServiceImpl 코드는 수정된다.
    • OCP 위반 : 할인 정책을 바꾸면 new 뒷 부분을 직접 변경해야 한다.
      • 예) new RateDiscountPolicy() -> new FixDiscountPolicy()
    • DIP 위반 : 인터페이스를 의존하지만 구현체 역시 의존한다.
      • 예) OrderServiceImpl은 인터페이스 DiscountPolicy에 의존하지만, 구현체인 FixDiscountPolicy, RateDiscountPolicy에도 의존한다. (아래에서 자세히 설명)

 

[ 그렇다면, 왜? 클라이언트 코드를 변경해야 하는걸까? ]

다이어그램을 보며 확인해보자.

우리는 DiscountPolicy 인터페이스에만 의존한다고 생각했다.

하지만, 실제 코드를 보면 FixDiscountPolicy, RateDiscountPolicy에도 의존하고 있다!

따라서, 인터페이스에만 의존하도록 설계를 바꾸면 된다.

원하는 설계

위와 같이 인터페이스에만 의존하게 코드를 변경해주면 된다. 하지만, 구현체가 없는 코드를 어떻게 실행할 수 있을까?

누군가는 클라이언트(OrderServiceImpl)에 구현 객체(DiscountPolicy)를 대신 생성하고 주입해줘야 한다.

 

 

[ DI 적용 후 ]

관심사 분리를 통해 구현체가 아닌 인터페이스만 바라보게 하고, 실제 사용되는 구현체는 생성자를 통해 의존성 주입을 해준다.

AppConfig.java

  • AppConfig는 구체 클래스를 선택한다.

 

MemberServiceImpl.java

  • DB가 변경되어도 클라이언트 코드는 수정하지 않아도 된다.

 

OrderServiceImpl.java

 


[ 정리 ]

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

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

  • AppConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값(x001)을 memberServiceImpl을 생성하면서 생성자로 전달한다.

 

Refactoring

앞서 작성했던 AppConfig에는 코드 중복(new MemoryMemberRepository())이 있고 역할에 따른 구현이 잘 안보인다.

따라서, 중복을 제거해주고 생성자를 통헤 구체들을 주입하는 코드를 작성한다.

  


[ 새로운 구조와 할인 정책 적용 ]

AppConfig의 등장으로 애플리케이션이 크게 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역으로 분리되었다!

 사용 영역과 구성 영역을 분리하고, 할인 정책을 변경하더라도(Fix -> Rate) 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.

 

만약, Fix -> Rate로 변경하고 싶다면 AppConfig에서 다음과 같이 변경해주면 된다. AppConfig는 당연히 변경되는게, 공연 기획자라고 생각하면 된다. 기획자는 공연 참여자들인 구현 객체들을 모두 알아야 하니까.

 

[ 좋은 객체 지향 설계의 5가지 원칙의 적용 ]

1. SRP(Single Responsibility Principle) : 단일 책임 원칙

한 클래스는 하나의 책임만 가져야 한다.

  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당
  • 클라이언트 객체는 실행하는 책임만 담당

 

2. DIP(Dependency Inversion Principle) : 의존 관계 역전 원칙

프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다.

  • AppConfig가 객체를 주입하면서 클라이언트가 구체화에 의존하지 않게 되었다.

 

3. OCP(Open/Closed Principle) : 개방-폐쇄의 원칙

확장에는 열려있으나 변경에는 닫혀있어야 한다.

  • AppConfig가 Fix -> Rate로 변경해서 주입해주므로 변경에 닫히게 된다.

 


[ IoC, DI, 그리고 컨테이너 ]

제어의 역전 IoC(Inversion of Control)

  • 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다.
  • AppConfig가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다.
  • 예를 들어서 OrderServiceImpl은 필요한 인터페이스들을 호출하지만 어떤 구현 객체들이 실행될지 모른다.
  • 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.

 

프레임워크 vs 라이브러리

  • 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다. (JUnit)
  • 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.

 

의존관계 주입 DI(Dependency Injection)

  • 정적인 클래스 의존관계
    • 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 
    • 정적인 의존관계는 애플리케이션을 실행하지 않아도 분석할 수 있다.
    • 문제점: 실제 어떤 객체가 주입될지는 알 수 없다.

  • 동적인 클래스 의존관계(실행 시점에 결정)
    • 플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.
    • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 클래스 의존관계를 쉽게 변경할 수 있다.

 

IoC 컨테이너, DI 컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다.
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
  • 또는 어샘블러, 오브젝트 팩토리 등으로 불리기도 한다.

 

 

Comments