[백엔드] 스프링 핵심 원리 기본편 정리

유승선 ·2022년 6월 22일
3

Spring 강의

목록 보기
2/12

강의를 다 보고 느낀점: 12시간이 넘는 강의를 이제야 끝냈고 첫 강의를 끝내고 코테 + 개인 일 등등 많은 일들이 겹쳐서 원래 5일~7일로 예상했던 강의 속도가 너무나도 길어졌다. 입문 프로젝트에 비교해서 굉장히 많이 스프링에 관한것들을 배운 기분이고 객체지향과 Controller, Component, Autowired, 의존관계 주입, Interface 와 Object 등등 연극과 배우의 관계를 연상하면서 많이 배웠던거 같다.

다만 후반에 갈수록 자동주입과 빈의 관한 설명에서 많이 집중력을 잃었고 강의가 길어질수록 내 흥미와 집중도가 많이 떨어져서 아쉬움에 남았다. 다음부터는 이렇게 길게 요약하는것보다는 챕터별로 따로 요약을 해서 올리는게 훨씬 나을거같다. 다음 강의는 HTTP 웹 기본지식으로서 재밌게 배울수있을거같다.

객체 지향 설계와 스프링

스프링이란?

예전부터 알고싶어했던 스프링의 기본 원리에 대해 많이 배웠던거 같다. 실제로도 스프링을 공부하기 전에 스프링 프레임워크와 스프링 부트의 차이점, 많이 들어본 DI의 원리 등등 그냥 단어만 들어본것들을 이번 강의를 통해서 좀 배운 느낌이다.


스프링 프레임워크는 내부적인 모든것을 포함하는 거대한 스프링 그 자체이다. 그리고 스프링 부트같은경우는 그런 프레임워크의 날개라고 생각할수있다. 기존에 세팅하기 힘들었던 데이터베이스 (톰캣) 외부 라이브러리 구성 등등을 손쉽게 처리해주기 위해 존재하는게 스프링 부트이다.

객체 지향 프로그래밍 (Object Oriented Programming) 은 내가 대학교 2학년때 C++을 처음 접하면서 들었던 수업이다. 자바도 굉장히 유사한 객체 지향 프로그래밍을 가지고 있어서 그런지 내가 예전에 배웠던 그 당시 기억이 많이 겹쳤다.

이 중에서 가장 중요시 생각해야하는 부분은 다형성 (Polymorphism) 이었는데 해당 강의에서는 이것을 역활(Interface) 와 구현(Implementation) 으로 구분을 하였다.

가령 예를 들어서 자동차를 비교로 했는데 꽤 적절한 비유였던거같다. 자동차의 역활을 (Interface) 그리고 그것을 상속받는 실제 자동차를 구현 (Implementation) 이라고 생각하면 편할거같다.

자바에서의 다형성으로 오바리이딩을 떠올린다고 하였는데 실제로도 수업에서 들었던 내용이고 한 클라스가 같은 인터페이스를 상속하면서도 그 내부에 기능을 유연하게 변경할수 있는것을 오바리이딩이라고 한다. https://mungto.tistory.com/320.

이런식으로 클라이언트의 요청에 따라서 다양하고 또 유연하게 서버의 구현 기능을 변경할수 있어야한다.

SOLID 좋은 객체 지향 설계의 5가지 원칙

객체 지향 프로그래밍을 이용할때 꼭 지켜야할 5가지 원칙이라고는 하지만 솔직히 시험을 보는게 아닌이상 각자 원칙에 대한 자세한 내용은 공부할때 정도만 한번씩 참고할거같다. 그렇지만 이 모든 원칙에서 강조하는것은 객체 지향의 핵심은 다형성 이고 다형성 만으로 이 모든 규칙을 지키기에 힘드니깐 스프링이 존재하는것이라고 강조하고 있다.

스프링 핵심 원리 이해1 - 예제 만들기

스프링 핵심 원리 이해 1편

시간을 아끼기 위해 내가 먼저 코딩을 하면서 따라하는것보다는 강의를 들으면서 교육 자료를 보고 이해하는걸 먼저로 해볼것이다. 그 후에 강의가 끝난다면 따로 해볼것

순수한 자바 코드로만 이용해가지고 역활과 구현을 나누고 실제 요구상황이 나올때 얼마나 자연스럽게 바꿀수 있는지를 확인하기 위해 시작하는 예제이다.

내 생각에 이해하기 편하게 먼저 이런 설계를 쓰고 이해하는게 중요한거같다. 이런 요구사항이 있고 설계가 있다는것을 알아야지 추후에 코딩할때도 편한거같다. 설계 내용을 보면은 "미확정" 이라는 부분이 있는데 지향 설계 방법을 터득하기 위해 일부러 적어놓은것으로 보이며 인터페이스와 구현체간에 얼마나 자연스러운 연계가 되는지 확인하기 위해서인거같다.

지금 이 포스트에는 적어두지 않았지만 프로젝트를 시작하기에 앞서 start.spring.io 로 들어간 후에 Gradle 선택후 build 해주었다. 포인트는 순수 자바코드로서의 역량을 보여주기 위해 어떠한 외부 Package 도 포함 안시켰다는 점

도메인 다이어그램을 하나씩 업로드 해보겠다.

회원 도메인 설계

회원 클래스 다이어그램

여기서 주목해야 할 점은 멤버 서비스를 인터페이스로 두었단거고 구현체 이름을 MemberServiceImp 라고 적었는데 한 인터페이스 아래에 구현체가 하나면은 저렇게 이름을 짓는게 좋다했다. 그리고 저장소 인터페이스 같은경우는 아직 어떤 DB를 사용할지 모르기때문에 저렇게 구현 클래스가 Memory Repo 와 DB Repo가 다르다.

회원 도메인 개발

회원 등급을 위한 선택 enum 은 static 과 비슷한것이라고 했다. C++에 struct 정도가 되지않을까 싶다.


회원 엔티티, GetterSetter 을 통한 설정은 항상 쓰이는거같고 가장 먼서 설정해둔 private 변수를 이용해서 저렇게 길게 나열이된 GetterSetter이 자동으로 만들어진다. Member() 컨스트럭터를 통한 객체 설정도 잊지말자.


회원 저장소 인터페이스다. MemberRepository 에 save(Member member) 와 Member findByID() 로 해주었다.

회원 저장소 구현체를 앞서서 설명했던 그림과 같이 만들어주었다. 인터페이스를 implements 한 구현체. 유심히 봐야할것은 @Override 라고 해서 인터페이스에 method 를 전부 임포트 해주었고 여기 데이터 베이스에서는 다르게 쓰일수있게 Map과 save, 그리고 findByID 방법을 전부 맞게 써주었다.


회원 서비스 인터페이스다. 정말로 간단히 멤버를 조인해주는 역활을 수행해주고 있고 findMember 을 썼을때는 멤버를 리턴해주는 method 를 품고있다.

구현체를 만들어주었고 서비스 구현체는 역활이 리포지토리에 접근을 하여 멤버를 조인해주는 역활을 수행해야한다. 그렇기 때문에 이 구현체 안에다가 MemberRepository(인터페이스) 오브젝트를 만들어준다음에 new MemoryMemberRepository() 로 설정하여 아까 만들었던 멤버 리포지토리에 구현체를 설정해 주었다. save와 findMember 위에다가 @Override 를 아까와 같이 붙혀주는게 맞는거같다.


테스트 코드

해당 강의에서는 애필리케이션 로직으로 MemberApp 을 만들어서 테스트 하는걸 보여줬지만 JUnit 테스트를 활용하는게 더 올바른 방법이다


주문과 할인 도메인 설계

기본 설계 내용:


주문 도메인 협력, 역활, 책임

굉장히 중요하다고 생각하는 다이어그램이다. 클라이언트는 시작과 동시에 주문서비스에 주문생성을 요구한다 (회원 ID, 상품명, 상품가격) 그리고 주문서비스는 회원등급을 확인하기 위해 먼저 회원 저장소에 회원을 조회하고, 등급에 따라서 할인 정책에 위임한다. 마지막으로는 DB에 주문을 저장하는게 아닌 주문 결과를 return 한다.

더 정교화된 다이어그램, 보면은 역활 밑에 점선을 이어진게 구현체라고 볼수있는데 어느 역활에는 두개에 구현체가 있다. 그 이유는 아직 확정된것이 아닌 미래에 변경이 가능한 항목들이기 때문이다.

주문 도메인 클래스 다이어그램이다. 이번에도 주문서비스에는 구현체가 하나이기 때문에 Impl을 붙혀서 구현체를 만들어주었고 이 전과 같이 리포지토리에 접근 후 회원정보 확인, 후에는 DiscountPolicy 라는 인터페이스 접근후 해당 구현체에서 Discount를 할것이다.

이 다이어그램은 미래에 바뀔수있는 요구사항에 따라서 다이어그램이 바뀔수도 있다는것을 표현한것이다.


주문가 할인 도메인 개발

할인 정책 인터페이스


정액 할인 정책 구현체. 간단하다. 천원만 깎아주면 된다, 언제? 고객이 VIP 등급일때.


주문 엔티티이다. 이 주문 객체를 자세히 보면 Order 이라는 method 가 있고 여러 다른 method 가 포함이 되있는데 위에 다이그램을 충실히 따른 method 들이다.


주문 서비스 인터페이스다. 정말로 간단하게 오더만 만들면된다.


주문 서비스 구현체이다. 위에 다이그렘에서 나왔던데로 우리는 리포지토리를 접근하고 할인 정책 구현체를 접근하는게 필요하다. 여기서 주시해야 할점은 항상 구현체를 부르기 전에 구현체를 그대로 부르는게 아닌 인터페이스 이름을 적은다음에 구현체를 부르는거같다. 이건 내가 자바 클래스 생태계랑 익숙치 않아서 조금 헷갈린거같기도하다.

createOrder 함수에 해당값들을 입력하게 되면은 각자 필요한 구현체를 이용해서 맞는값을 리턴하는게 참 신기한거같다. 인터페이스와 구현체의 차이점을 잘 몰랐는데 이렇게 보니 이해가 더 쉽고 다이어그렘의 중요성을 조금 더 깨달은거같다.

테스트케이스를 적는것으로 마무리. 이번에도 JUnit 테스트를 진행했다.


스프링 핵심 원리 이해2 - 객체 지향 원리 적용

스프링 핵심 원리 이해 2편

새로운 할인 정책 개발

이번에도 다이어그램을 이용해서 이해를 도울것이다. 이 전에 사용했던 FixDiscountPolicy 객체를 뒤이어, 할인 정책이 바뀐다는 시나리오를 생각해서 RateDiscountPolicy 라는 객체를 새로 만들어주었다.


새로운 할인 정책 적용과 문제점

우리는 기존에 OrderService 인터페이스를 상속받은 OrderServiceImple 이라는 구현체를 사용하고 있었다. 그리고 이 구현체 안에서 할인 정책을 선택하고 그랬었는데 새로운 정책이 나타남에 따라서 위에 코드처럼 RateDiscountPolicy 로 구현체를 지정해주었다.

그러나

이런 방법은 문제가 많다. 왜냐면 이 전에 배웠던 SOLID 방식에서 두가지나 위반하고 있기를 때문이다.

가장 먼저 DIP 를 위반하고 있다, 왜 냐면은 클래스 의존관계를 봤을때 지금 클래스는 인터페이스 뿐만 아니라 구현체에도 의존하고 있기때문이다. 한마디로, DiscountPolicy 인터페이스에만 의존해야하지만 지금은 FixDiscountPolicy 와 RateDiscountPolicy 둘 다 의존하고있다.

두번째로 OCP 를 위반하고 있다. 지금 처럼 할인 정책을 바꾸는 확장 기능에서 클라이언트 코드에 영향을 주고 있다. 예를 들어, 직접 OrderServiceImple 에 들어가서 Policy 를 직접 바꿔주고 있다.


해결 방법

해결 방법은 정말 간단하다, 위에처럼 DIP 와 OCP를 위반하는 행위가 아닌, 오로지 클래스가 인터페이스에만 의존할수있도록 변경해주면 되는것이다.

이것이 가장 이상적인 그림이고 이것을 코드로 옮기게 되면 이런 모습이 나오게된다.

하지만 현재 이 방법으로는 구현체가 없기 때문에 당연히 에러가 나올수밖에 없고 이 문제를 근본적으로 해결하기 위해서는 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야한다.


관심사의 분리

모든 강의를 통틀어서 이 부분에서 가장 긴 강좌시간이 나왔었고 알려주는 강사님 조차 너무나도 중요한 부분이라고 강조를 했었다.

애플리케이션의 관심사를 분리하는 부분에 대해서 설명을 해줄때 강조 했던 하나는 구현체가 절대로 다른 구현체를 선택하는 일은 없어야 하고 어떤 구현체를 선택할지는 별도의 클라이언트가 정하는것 이라고 했다.

그 예를 들어 역활극이라는 예를 들어서 절대로 배우가 다른 배우를 선택하는 책임을 가져서는 안되고 배우는 자신의 역활 (인터페이스) 에만 집중을 하고 그에 맞는 배우를 선택하는것은 공연 기획자라고 하였다. 꽤 적절한 비유같았다.

AppConfig 등자

이 강의에서 첫번째로 등장하는 가장 중요한 부분이 아닐까 싶다. AppConfig 란 애플리케이션의 전체 동작 방식을 구성하기 위해, 구현 객체를 생성 하고, 연결 하는 책임을 가진 별도의 클래스라고 한다.

(이것은 리팩터링된 버전이다)

원리는 자세히 보면은 또 굉장히 간단하다 인터페이스의 이름으로 된 public method 를 만들고 return 부분에서 객체를 만들어 준 후에 parameter 안에다가 원하는 구현체를 넣어주면 되는것이다. 그 후에는 레퍼런스를 생성자를 통해서 주입 해준다.

그 결과, 실제로 MemberServiceImpl 은 인터페이스에만 의존중이고 그 어떤 구현체에도 의존하고 있지않다. 그리고 처음에 MemberServiceImpl(MemberRepository memberRepository) 를 부른 순간 AppConfig 에서 레퍼런스를 직접 만들어줘서 똑같은 기능을 수행할수있지만 의존관계가 완전히 형성이 됐다.

여기서 핵심중 하나인 DI의 정의가 나오는데 의존관계 주입 또는 의존성 주입이라고 한다. 왜냐면은 appConfig 객체가 대신 리포지토리를 생성해주고 그 참조값을 생성자로 전달해주고 있기때문이다.

오더서비스 구현체도 똑같은 역활을 수행하고있다.

보면은 AppConfig 를 실행해줄때 이 전에 했던 테스트케이스와 동일하지만 Appconfig.memberService() 라는 method 를 호출함으로서 코드가 더 간결해진것을 볼수있다.


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

Appconfig 를 쓰면서 가장 많이 달라진점이라고 생각한다. 사용영역하고 구성의 영역이 명확히 달라졌다. 구성을 하는 역활을 AppConfig 가 담당함으로서 사용영역은 그 역활만 수행하면 되게 바뀌었다.

할인 정책을 바꾸고 싶다면 정말 간단하게 return 값만 바꿔주면 된다. 가장 큰 이점은 우리가 전처럼 직접적으로 OrderServiceImpl 을 포함해 사용역역의 코드를 안바꿔도 되고 공연의 기획자로 AppConfig 를 생각하면 편하다.


여기서부터는 이론적인 내용이 많았던 강의 부분이였고 이론을 굉장히 추스린 부분만 캡쳐해서 올려본다


스프링으로 전환하기

  • ApplicationContext 를 스프링 컨테이너라 한다
  • 기존에는 개발자가 AppConfig 를 조작하여 직접 객체를 생성하고 DI를 했지만, 이제부터 스프링 컨테이너를 통해서 사용한다.


스프링 컨테이너와 스프링 빈

스프링 컨테이너 생성되는 과정

전 챕터에서 ApplicationContext 를 스프링 컨테이너라고 불리는것을 배웠고 이번 섹션에서는 컨테이너가 생성되는 과정을 배웠다.

  • ApplicationContext의 기본 정의

  • 1번 과정, 스프링 컨테이너 안에 AppConfig 의 정보를 넣는순간 컨테이너가 생성이 된다. 컨테이너는 구성 정보를 AppConfig 로 부터 받는다.

  • 그림에서 잘 안보일수있지만, AppConfig.class 안에 있는 정보를 통해서 빈이 등록이 된다. 물론 이과정은 @Bean 이 적힌 클래스 정보만 들어가게 된다.

  • 의존관계 (DI) 를 하기 전 준비상태

  • 스프링 컨테이너안에 있는 객체들은 서로의 클래스에 의존하는 관계를 유지하고 있고 컨테이너는 이러한 관계를 주입 시켜준다.

  • 후에 챕터에서는 빈을 조회하는 방법과 여러 디테일한 상위 클래스에 대해 설명했다.


싱글톤 컨테이너

웹 애플리케이션과 싱글톤

웹 애플리케이션은 기본적으로 많은 고객이 동시에 요청을 한다. 그러나 지금 유지하고 있는 코드처럼 요청이 들어올때마다 객채를 새로 만들게 되면 메모리 낭비가 너무 심하므로, 1개를 생성하고 공유하도록 설계 해야하는데 이것을 싱글톤 패턴 이라고 부른다.

싱글톤 컨테이너

  • 객체 인스턴스를 1개만 생성 (싱글톤) 해서 관리하는 방법이 지금껏 스프링 빈으로 우리가 사용하던 기술이었다.

  • 스프링 컨테이너가 이렇게 싱글톤 객체를 생성하고 관리하는기능을 싱글톤 레지스트리라고 한다.

  • @Bean 이 붙은 method 를 불러도 이미 의존관계가 DI 된 상태면은 객체 하나로 모두 한번만 호출이 된다.

  • AppConfig 에는 @Configuration 이 붙어있는데 @Bean 을 대신해서 쓰면은 싱글톤이 유지가 안된다. 간단히 설명하면 상위 클래스에 의해서 AppConfig 또한 싱글톤이 유지된다고 했다. 설정 정보는 항상 @Configuration 을 사용하자


컴포넌트 스캔

컴포넌트 스캔과 의존관계 자동 주입

  • 코드로 컴포넌트 스캔을 통해서 의존관계 자동 주입을 할수있다.

  • 컴포넌트 스캔은 이름 그대로 @Component 애노테이션을 클래스에 붙히면 된다. 그렇다면 애노테이션이 붙은 클래스 스캔 후 빈으로 등록한다. (@Configuration 에는 @Component 에노테이션이 자동으로 붙어있다)

  • 유심하게 볼 점들은 클래스에 @Component 가 붙어있고 @Bean을 통해 직접 설정 정보를 작성한거에 비해서 이제는 의존관계 주입도 클래스 안에서 해결해야한다.

  • @Autowired 는 의존관계를 자동으로 주입해준다.

  • @Autowired 사용 예시

  • 컴포넌트 스캔 과정이다. @Bean 을 썼던거와 굉장히 유사하고 비슷한 원리로 컨테이너는 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.

  • @AutoWired 에 대한 설명이 조금 이해안된다 생각했는데 DI를 위한 과정이라고 생각하고 있고 @AutoWired를 써주는 순간 컨테이너에서 해당 타입에 맞는 빈을 찾은후 주입해준다고 한다.

  • 파라미터가 많아도 동시에 자동으로 주입해준다.

탐색 위치와 기본 스캔 대상

  • 컴포넌트 스캔을 자바 클래스 전부를 포함해서 진행한다면 시간이 오래 걸리므로 이렇게 위치 지정을 하는것도 좋은 방법이라 한다.

  • 혹여라도 나중에 이 구조가 궁금하다면 찾아볼수 있을거같다.

  • 그 외에도 @ComponentScan 은 필터 등을 사용해서 대상을 추가로 지정하고 제외할수있다 하지만 생략하겠다.


의존관계 자동 주입

다양한 의존관계 주입 방법

의존관계 주입에는 다양한 방법이 존재하고 있다한다. 가장 대표적인 주입방법으로는,

  1. 생성자 주입
  2. 수정자 주입 (setter 주입)
  3. 필드주입

하지만 강의에 내용 끝에서는 요약본으로 생성사 주입이 가장 많이 선택된다고 했고 권장한다고 하였다.

생성자 주입이 권장되는 이유중 하나는 순수한 자바 언어의 특징을 잘 살리는 강점이 있기때문이라기도 했다.

조회 빈이 2개이상 - 문제

@Autowired 는 DI 방식으로 의존관게를 주입시키는데 사용된다. 그러나 이 방법은 기본적으로 타입 (Type) 으로 빈을 조회하는데 같은 타입의 빈이 2개 이상일때는 문제가 발생한다 한다.

예를들어, DiscountPolicy 의 하위타입인 FixDiscountPolicy 와 RateDiscountPolicy 가 둘다 스프링 빈으로 선택되고 의존관계를 자동주입 실행하면,

이런식의 오류가 나온다.

그리고 이런 오류를 고치기 위한 방법으로는

@Autowired 필드 명,
@Qualifier
@Primary

있다고한다.

필드명을 주입시킬때 보면은 이름을 변경시켰다. DiscountPolicy discountPolicy -> rateDiscountPolicy 이렇게 하면은 정상 주입되는걸 볼수있다.

Qualifer 주입방식은 비슷하지만 구분을 할때 굉장히 편리한 기능같았다.

정리:

  1. Qualifier 끼리 매칭
  2. 빈 이름 매칭
  3. NoSuchBeanDefinitionException 같은 예외 발생

@Primiary 같은 경우는 우선순위를 정해주는 좋은 방식이다

@Qualifer 와 @Primary 의 좋은 차이점이다.


누락된 주제들:

스프링 컨테이너에서의 XML 활용, BeanDefinition 의 정의, 빈 생명주기 콜백, 그리고 빈 스코프.

누락을 했던 이유는 첫번째로는 내가 아무리 영상을 봐도 이해를 잘 못해서였거나 내 판단하에 그렇게 중요하게 배우지 않아도 될거같은 생각이 들어서 집중력 저하로 인해 영상을 빠르게 생략했던거같다.

꽤 많이 심화된 주제 같았고 앞으로 JPA 와 함께 영상 복습을 돌아온다면 다시 배우고 싶다.

profile
성장하는 사람

0개의 댓글