ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 핵심 원리 (1) - 예제 순수 자바 to 스프링 컨테이너
    Web/Spring 2023. 8. 16. 18:49

    - 객체 지향 프로그래밍의 원리에 맞게 회원 및 주문 서비스를 만들어보자

    - 객체 지향 설계에서 협력 - 역할 - 책임의 관계는 매우 중요하다.

       역할: 객체가 수행할 책임들을 의미하고, 다형적인 특징을 보여준다.

       책임: 객체가 수행해야할 행위 공용인터페이스 

     

    - 객체지향-

    다형적 특징은 역할과 구현을 분리함으로써 달성할 수 있다.

    캡슐화: - 데이터 숨김, 내부 구현 숨김,메시지를 통한 데이터 접근 -> 객체 자율성 부여 

                 - 오류의 범위 캡슐 내부화 -> 유지보수에 유리 

    타입: 객체를 나누는 기준 -> 할 수 있는 책임(행위)에 따라 객체 분리(classify) 

    클래스: 객체를 생성할 수 있도록 언어적 차원에서 지원된 정적 객체 생성 방법 

     

    - 객체지향적 설계에서 가장 중요한 것은 협력관계를 통해 적절히 책임을 분할하고, 객체에 할당하는 것 

    - 어떤 메시지를 주고받을 지 결정 -> 객체의 책임 도출 

                                                          -> 개념적인 인터페이스 (리턴타입,파라미터,메서드명 등 설정) 

                                                          ->  인터페이스 구현 (상속,인터페이스 구현)

     

    * 메시지 설정은 클라이언트(컨트롤러,메인메서드 등) 중심으로 생각하자 

     * 이러한 객체 지향의 특징을 통해 갹체 사용하는 클라이언트 코드 수정 없이 구현을 변경,쉬운 확장을 할 수 있게되며,   

        유지보수에 이점이 있다.

     

    - 의존 -

    의존이란 클래스에 필드, 파라미터 등에서 특정 클래스를 사용하는 것 (Has a)

    public class ClassA{
    
    	private ClassB clb = new ClassB();
        
        public void met01(ClassC c){
        	c.met02();
        }
    }

     > 객체지향설계의 OCP(개방폐쇄)원칙을 지키기 위해서는 강한의존관계는 지양해야한다.

         - 확장,유지보수를 위해 

     

    1. 객체 지향적 설계와 다형성의 한계 

     

    1. 회원도메인 

     

    요구사항: 1. 회원 가입, 조회 

                     2. 회원 등급 구분 

                     3. 회원 데이터 자체 DB는 정해지지 않음(변경가능성 높다 -> 역할과 구현을 분리)

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

    - 회원가입 및 조회 협력

       > 클라이언트 : 회원 서비스에 회원가입 혹은 조회 메시지 

       > 회원 서비스: 회원 가입 및 조회 책임, 회원 저장소에 가입 회원 저장 및 저장된 회원 데이터 조회 요청

       > 회원 저장소: DB에 회원 저장 및 조회 책임 

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    - 메시지를 통해 결정된 책임에 따라 공용 인터페이스를 뽑고, 역할과 구현 분리  

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    - 동적 객체의 흐름 

     

    2. 회원 도메인 구현

     

    2.1 인터페이스 

    public interface MemberRepository {
        void save(Member member);
        Member findByid(Long memberId);
    }
    
    public interface MemberService {
        void join(Member member);
        Member findMember(Long memberId);
    
    }

    - 저장 및 조회를 위한 인터페이스 뽑자 

     

    2.2 구현

    public class MemberServcieimpl implements MemberService{
        private final MemberRepository memberRe = new MemoryMemberRepository();
      //..
    }
    public class MemoryMemberRepository implements MemberRepository{
    
        private static Map<Long,Member> store=new HashMap<>();
        //..
        }

    - 회원 서비스는 회원가입 및 조회를 위해 DB작업이 필요하므로 MemberRepository와 의존관계가 필요하다.

    - MemberRepository의 콘크리트 클래스는 데이터를 저장할 수 있는 DB를 내부적으로 가지고 있다.

    public class Member {
        private Long id;
        private String name;
        private Grade grade;
        //..
        
        }

    - 저장될 객체인 멤버는 위와 같은 필드를 가지고 있다.

     public static void main(String[] args) {
            MemberService memberS = new MemberServcieimpl();
            Member member1 = new Member(1L, "member1", Grade.VIP);
            memberS.join(member1);
            Member member = memberS.findMember(1L);
            //..
            }

    - 클라이언트는 위와 같이 멤버를 생성해서 서비스를 통해 회원가입 혹은 조회를 한다.

     

    ----문제점---------------------------------------------

    * 역할과 구현을 분리 객체지향의 원리를 잘 지켰다.

       위 협력관계는 추상화된 역할을 바탕으로 재사용할 수 있다(구현 객체만 갈아끼우면 쉽게 확장할 수 있다)

       아직 남은 문제점은 

    public class MemberServcieimpl implements MemberService{
        private final MemberRepository memberRe = new MemoryMemberRepository();

    - 위와 같이 멤버서비스 구현체가 Repository 추상객체(인터페이스 타입)와 콘크리트 객체 두 가지 모두에 의존 

       > DIP원칙 위배, 추후 DB수정시 MemberServiceimpl의 코드도 수정해야함 (OCP위배) 

     

     

    3. 주문 할인 도메인 설계 

     

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

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    회원등급에 따른 할인을 적용한 주문 데이터 생성 협력 

     

     - 클라이언트: 주문 서비스에게 주문 생성 요청

     - 주문 서비스: 요청에 따라 주문 생성 및 결과 반환 역할 -> 회원저장소에 회원 조회 요청

                                                                                              -> 할인 정책에 할인 적용 가능 요청

     - 회원저장소: 요청에 따른 회원 조회 책임, 할인 정책: 요청에 따라 회원이 할인 적용 가능한지, 할인 금액 얼마인지 결정 책임

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

    - 역할과 구현 분리 

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

    - 세부 구현에 따라 객체 다이어그램에 할인 정책을 정률 할인 정책으로 변경할 수도 있다.

    - 잘 설계된 객체지향프로그래밍의 이점으로 주문 서비스 구현체 변경없이 할인을 변경할 수 있다.

     (재사용 가능한 협력관계)

     

    4. 주문 도메인 구현

     

    4.1 인터페이스 

    public interface OrderService {
        Order createOrder(Long memberId,String itemName,int itemPrice);
    }

    4.2 구현체 

    public class OrderServiceimpl implements OrderService{
        private final MemberRepository memberRepository =new MemoryMemberRepository();
        private final DiscountPlicy disp = new FixDiscountPlicy();
        //..
        }

    - 클라이언트 요청에 따라 주문객체를 생성하기 위해서는 

       ->  멤버 id를 통해 주문한 회원을 조회해야하므로 -> memberRepository와 의존

       ->  조회한 멤버가 할인 적용이 가능한 멤버인지 확인하여, 할인 금액을 계산해야함 -> 할인 정책 객체와 의존

     

     public class Order{
     private Long memberId;
        private String itemName;
        private int itemPrice;
        private int discountPrice;
    
        public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
            this.memberId = memberId;
            this.itemName = itemName;
            this.itemPrice = itemPrice;
            this.discountPrice = discountPrice;
        }
        public int caculatePrice(){
           
        }
        
        //..
        }

    - order객체는 위와 같은 필드를 가지고 있으며, 생성시 전달받은 할인 금액을 통해 최종 주문 금액 계산할 수 있음

     

     public static void main(String[] args) {
            MemberService memberService = new MemberServcieimpl();
            OrderService orderService=new OrderServiceimpl();
    
            //멤버 생성 - 등록
            Long memberId =1L;
            Member memberA = new Member(memberId, "memberA", Grade.VIP);
            memberService.join(memberA);
    
            //주문 생성
            Order itemA = orderService.createOrder(memberId, "itemA", 10000);
            System.out.println("itemA = " + itemA);
            System.out.println(itemA.caculatePrice());
        }

    - 클라이언트는 위와 같이 주문을 생성할 수 있음 

     

    * 주문 서비스도 회원 서비스와 동일한 문제점이 있다.

    * 객체 지향 설계에 잘 맞게 구현 했지만, DIP와 OCP를 지키기 위해서는 다형성만으로는 한계가 있다.

     

     

    2. 구성 영역과 사용영역 분리

     

    1. 구성영역을 통한 문제점 해결 (AppConfig)

     

    - DIP의 문제점 (MemberService, OrderService) 추상타입이 아닌 콘크리트 타입에도 의존하는 문제점을 고쳐야한다.

    - 또한 현 코드는 기능을 확장하려면, 클라이언트 코드를 수정해야한다.

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

       - 현재 위와 같은 의존관계에서 할인 정책의 클라이언트인 OrderServiceimpl와 콘크리트 의존관계를 깨야한다.

       - 이를 위해서는 OrderServiceimpl대신에  구현 객체를 주입해줘야한다.

     

     1.1 **관심사의 분리 

     

       - 어플리케이션을 개발하는 것은 협력에 따라 적절하게 역할을 설정하고 책임을 분배하는 것이라 했다.

       - 연극과 빗대본다면, 배역을 맡은 배우는 대본에 충실할 뿐 그 외에 책임은 없다.

       - 배역에 실제 배우를 결정하거나 나머지 여러 일은 공연 기획자의 일이다.

       - 서비스 객체는 로직에 맞는 서비스를 실행할 뿐, 실제 의존객체를 결정할 필요가 없다.

       - 애플리케이션에도 연극과 같이 실제 배역을 설정해줄 기획자와 같은 객체가 필요하다.

     

    1.2 Appconfig 

      

       - 애플리케이션 전체 동작 방식 구성을 위해 존재

       - 구현 객체를 생성하고, 연결하는 책임을 별도로 가지는 설정 클래스이다.

     

    public class Appconfig{
    
    	public MemberService memberService(){
        	return new MemberServiceImpl(new MemoryMemberRepository());
        }
        
        //..
    }

     - 위처럼 Appconfig를 통해 MemberService를 생성하게 되면, 구현객체와 구현객체가 내부적으로 사용할 의존관계까지 주입할 수 있다.  

    public class MemberServiceImpl implements MemberService{
    
     private final MemberRepository memberRepository;
    
    public MemberServiceImpl(MemberRepository memberRepository) {
     this.memberRepository = memberRepository;
    }
    //..
    }

    - 이를 바탕으로 MemberServiceImpl은 MemberRepository 인터페이스에만 의존한다.

    - MemberServiceImpl입장에서는 생성자로 주입받기 때문에 실제 어떤 구현 객체가 주입될지는 알 수 없다.

    - 이는 AppConfig의 역할이다. 의존관계 고민은 외부에 맞기고 실행에만 집중 

     

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     - AppConfig를 통해 DIP가 만족되었다.

     - 역할과 책임에 따라 클래스가 적절히 분리되었다.

     

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

      - 위와 같은 새로운 구조에서 할인 정책은 변경하는 것은 매우 쉽다.

    출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

       - 구성 영역만 수정한다면, 사용 영역의 코드는 전혀 수정할 필요 없이 변경 가능하다.

       - 위와 같은 구조는 SRP, DIP, OCP에 잘 맞는다.

     

     

    2. IoC,DI,컨테이너

     

     제어의 역전 IoC : - 서비스 구현 객체가 필요 객체 생성,연결,실행 -> 프로그램 흐름 제어

                                   - AppConfig와 같은 구성 객체 존재시 사용 영역 객체들은 로직 실행만 담당

                                   - 프로그램 제어 흐름을  AppConfig가 가져감 

                                   - 위와 같이 각 서비스 구현 객체가 흐름을 직접 제어하는 것이 아니라, 외부에서 관리하는 것을

                                      제어의  역전이라고 한다.

      

    프레임워크 vs 라이브러리: 제어의 역전 유무 차이로 구분 가능

     

    의존관계 주입 DI : - 런타임에 외부에서 실제 구현 객체 생성하고 클라이언트에 전달해서 

                                     서버와 클라이언트 실제 의존관계가 연결 되는 것을 의미한다.

                                   - 의존관계를 주입하면, 클라이언트 코드를 변경하지 않고, 클라이언트가

                                      호출하는 대상의 타입 인스턴스를 변경할 수 있다.

                                   - 정적 클래스 의존관계 변경하지 않고, 동적 객체 인스턴스 의존관계 쉽게 변경 가능하다.

     

    * 정적 클래스 의존관계와, 실행 시점 동적(인스턴스) 의존관계는 분리해서 생각하자.

     

    정적 클래스 의존관계: import 코드만으로 쉽게 판단 가능, 실행 하지 않고 확인 가능 (ex 클래스 다이어그램)

    동적 객체 인스턴스 의존관계: 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스 연결된 의존관계

    (ex 객체 다이어그램)

     

     

    2.1 IoC컨테이너, DI 컨테이너 

    - AppConfig처럼 객체 생성 관리하고, 의존관계 연결해주는 것을 IoC 혹은 DI컨테이너라 한다.

     

    3. 스프링으로 전환

     

    3.1 스프링 컨테이너에 Bean등록하기

     

    - AppConfig에 메서드들을 스프링 컨테이너에 등록할 것이다.  

    @Configuration
    public class AppConfig {
    
     @Bean
     public MemberService memberService() {
     return new MemberServiceImpl(memberRepository());
     }
     //..
    }

    - AppConfig가 설정을 구성한다는 뜻으로 @Configuration을 붙여준다.

    - 각 메서드에 @Bean 어노테이션을 붙여준다 -> 스프링 컨테이너에 빈으로 등록 

     

    7.2 스프링 컨테이너 사용 

     

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

     

    - ApplicationContext를 스프링 컨테이너라고한다.

    - AppConfig를 직접 생성해서 DI했지만, 위 코드는 스프링 컨테이너를 사용한 것이다.

    - 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정 정보로 사용했다. 

    - @Bean이 적용된 모든 메서드를 호출해서 반환 값을 스프링 컨테이너에 등록한다.

        이렇게 컨테이너에 등록된 객체를 스프링 빈이라 한다.

    - 스프링 빈은 @Bean이 붙은 메서드 명을 스프링 빈의 이름으로 사용한다.

    - 스프링 컨테이너를 통해 필요한 스프링 빈(객체)를 getBean메서드를 통해 찾는다. 

    - 이제 직접 자바코드로 했던 것을 스프링 컨테이너에 객체를 스프링 빈으로 등록하여 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경 되었다.

     

    -> 다형성만으로는 DIP와 OCP를 만족시킬 수 없다.

         -> 따라서 의존관계를 주입 받는 방식으로 이를 해결하고자 하였다. (AppConfig, 구성과 사용 분리)

         -> AppConfig와 같은 역할이 발전하여 이를 담당하는 IoC컨테이너-> 스프링 컨테이너가 등장

         -> 즉 스프링 컨테이너는 기존 자바 코드로 할 수 없었던, DIP와 OCP를 만족시킴과 동시에 여러 편의 기능을 제공하기 위해 등장 (여기선 DIP랑 OCP 측면에서만 봤음)

     

     

     

    참고자료: 스프링 핵심 원리 - 김영한 

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

    www.inflearn.com

     

Designed by Tistory.