스프링의 탄생
스프링은 언제, 왜, 어떻게 나왔을까?
이를 살펴보기위해선 EJB(Enterprise Java Beans)부터 알아야 한다. EJB는 2000년 초반에 나온 기술인데, 스프링, JPA, 등등의 각 기능들이 다 합쳐진 프레임워크라고 보면 된다. 게다가 자바 표준에서 나온 기술이기에 많은 보급이 되었는데, EJB에서는 많은 장점들(컨테이너, 설정에 의한 트랜잭션 관리, 분산 기술, ORM)이 있었기에, 서버하나에 수천만원 임에도 금융권에서는 많이들 사용되었다.
하지만, 장점만 있었을까?
EJB는 일단 러닝커브가 무척이나 가파르다. 사용하기가 너무 복잡하고 어렵고 느릴뿐더러 라이프 사이클이 어떻게 돌아가는지도 알기 힘들다보니, 개발자들은 POA(Plan Old Java)라고 다시 옛날 자바로 돌아가자는 운동도 했다고 한다. 그래서, 이때 두 명의 개발자 개빈 킹(Gavin King)과 로드 존슨(Rod Johnson)이 혜성같이 나타나 오픈소스를 만들기 시작한다.
개빈 킹(Gavin King)
EJB의 엔티티빈을 사용해보고 만족하지 못한 개빈 킹이 하이버네이트(Hibernate)라는 기술을 만드는데, 개발자들은 대다수가 EJB의 엔티티빈을 사용하는 게 아니라 모두 하이버네이트를 사용하기 시작하자, 자바 표준 논의 기관에서 개빈 킹을 스카웃해서 하이버네이트를 가져와 현재 사용하는 JPA를 만든다.
그리고 이 JPA가 현재 대부분의 점유율을 가지고 있는 상황.
로드 존슨(Rod Johnson)
2002년에 EJB의 단점을 지적하며 EJB없어도 충분히 고품질의 확장 가능한 애플리케이션을 개발할 수 있음을 보여주는 코드가 동봉된 책을 출간한다.
(대략 30,000라인 이상의 기반 기술을 예제로 선보인다.)
이 예제에 지금의 스프링의 핵심 개념이 들어가있다.
(BeanFactory, ApplicationContext, POJO, 제어의 역전, 의존관계 주입등)
그리고 책이 출간되는 이 시점에서 유겐 휠러(Juergen Hoeller), Yann Caroff(얀 카로프)가 로드 존슨에게 오픈소스 프로젝트를 제안하는데, 이렇게 나온 프로젝트가 스프링 프레임워크이다.
스프링 역사(릴리즈)
- 2003년 스프링 프레임워크 1.0 출시 - XML
- 2006년 스프링 프레임워크 2.0 출시 - XML 편의 기능 지원
- 2009년 스프링 프레임워크 3.0 출시 - 자바 코드로 설정이 가능해짐.
- 2013년 스프링 프레임워크 4.0 출시 - 자바 8
- 2014년 스프링 부트 1.0 출시
- 2017년 스프링 프레임워크 5.0, 스프링 부트 2.0 출시 - 리액티브 프로그래밍 지원
- ⇒ 자바에서도 논블로킹(non-blocking) 개발이 가능해짐.
- 2020년 9월 현재 스프링 프레임워크 5.2.x, 스프링 부트 2.3.x
스프링이란?
스프링 공식 홈페이지를 방문하면 정말 많은 프로젝트로 생태계가 형성되어 있다.
우리가 흔히 아는 스프링 프레임워크뿐 아니라 우측 이미지처럼 다른 각종 데이터베이스에 접근하도록 도와주는 스프링 데이터나, 세션 기능의 사용을 돕는 스프링 세션, 보안과 관련된 스프링 시큐리티 등 굉장히 많은 스프링 프로젝트들이 있다.
그리고 이러한 기술들을 한데 뭉쳐 좀 더 쉽게 사용할 수 있도록 도와주는 스프링 부트도 있다.
우리는 결국 이런 부가적인 프로젝트도 중요하지만, 스프링 프레임워크와 이를 편리하게 사용할 수 있도록 도와주는 스프링 부트가 핵심이고 학습해야 한다.
스프링 프레임워크
는 여러 가지 기술이 합쳐져 만들어진 프레임워크이다.
그래서 해당 프레임워크 내에서 웹 기술, 데이터 접근, 기술 통합, 테스트가 다 지원이 되고 언어 또한 코틀린이나 그루비도 지원된다.
처음 스프링을 공부하면 지루할 정도로 자주 듣는 키워드들인 스프링 DI 컨테이너, AOP, 이벤트와 같은 핵심 기술이 있고, 스프링 MVC 또는 스프링 5.0에 추가된 리액티브 스택 웹 프레임워크인 스프링 WebFlux와 같은 웹 기술도 있다.
더하여 DB에 접근 기술인 트랜잭션, JDBC, ORM, XML도 지원을 한다.
그리고 스케줄링을 통한 예약기능, 이메일전송, 캐시와 같은 기술과 이를 테스트할 수 있는 스프링 기반 테스트(JUnit)도 지원한다.
스프링 부트
스프링은 정말 많은 기술을 다 지원한다. 하지만 설정이 너무나도 어렵고 배포과정 역시 번거롭다.
기존 스프링 프레임워크에서는 프로젝트를 배포하기 위해서는 Tomcat서버를 구축하고 특정 경로에 해당 프로젝트를 빌드한 war나 jar파일을 올리고 설정도 맞춰서 구동해야지만 동작을 했다. 그뿐 아니라 해당 스프링 버전에 맞는 외부 라이브러리를 사용하기 위해선 해당 외부 라이브러리가 해당 스프링 버전과 궁합이 잘 맞는지 직접 테스트하며 지정해야 하고, 모든 초기 설정들을 직접 하나하나 해줘야 했다.
그래서 나온 게 스프링 부트이다. 이는 스프링을 편리하게 사용할 수 있도록 지원하는 기술로 스프링 프레임워크를 직접 구성하고 만들 때 어려웠던 부분들(위에서 작성한 WAS구성, 외부 라이브러리와의 궁합, 설정들)을 훨씬 쉽게 지원한다.
- Tomcat 같은 웹 서버를 자체 내장해서 별도의 웹 서버를 설치하지 않아도 된다.
- 손쉬운 빌드 구성을 위한 starter 종속성을 제공한다 (ex: implementation 'org.springframework.boot:spring-boot-starter-data-jpa')
- 스프링과 서드파티(3rd parth) 외부 라이브러리 자동 구성
- 메트릭, 상태 확인, 외부 구성과 같은 프로덕션 준비 기능 제공
- 관례의 의한 간결한 설정
- xml을 통해 하나하나 설정해줘야 했던 설정들을 대부분 관례에 따른 기본설정으로 세팅해 놓고 명시적으로 설정을 변경해야 하는 경우에만 properties나 yml을 통해 변경해 줄 수 있다.
그렇다면 스프링은 무엇을 의미하는가?
스프링이라는 단어는 문맥에 따라 다르게 해석될 수 있다.
기본적으로 핵심 기술로 말하자면 스프링 DI컨테이너 기술이라고도 할 수 있고 스프링 프레임워크라고도 할 수 있다.
혹은 스프링 부트, 스프링 프레임워크를 포함한 스프링 생태계 전체를 의미할 수도 있다.
스프링을 만든 이유
항상 기술의 핵심 개념을 파악하기 위해서는 “이 기술을 왜 만들었는가? 이 기술의 핵심 컨셉은?” 이러한 점들을 파악해야 한다.
스프링의 핵심 개념들로 아래와 같을 수 있다.
- 웹 애플리케이션 만들고, DB 접근 편리하게 해주는 기술?
- 전자정부 프레임워크?
- 웹 서버도 자동으로 띄워주고?
- 클라우드, 마이크로서비스?
스프링의 진짜 핵심
스프링은 자바 언어 기반의 프레임워크이다.
서버를 구성할 때 자바진영이면 기본적으로 스프링으로 구성한다.
자바기반의 대표 프레임워크가 스프링이기 때문인데, 그렇다면 자바의 대표적인 특징이 결국 스프링의 대표 특징이 될 수 있다.
그럼 자바의 가장 큰 특징은 무엇일까? 객체 지향 언어라는 점이다.
스프링은 객체 지향 언어가 가진 특징인 유연성과 확장성을 잘 살리는 프레임워크로써 좋은 객체 지향 애플리케이션을 개발할 수 있도록 도와준다.
객체 지향 특징
- 추상화
- 캡슐화
- 상속
- 다형성
객체 지향 프로그래밍
- 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력)
- 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
유연하고, 변경이 용이?
프로그램을 유연하고 변경이 용이하게 만든다고 하는데, 이게 무슨 의미일까?
레고 블럭을 통해 성을 만든다고 하자. 이때 성의 벽돌하나를 바꾸고자 할 때 다른 블럭들과 결합만 제대로 된다면 쉽게 교체할 수 있다
이는 변경이 용이하다고 할 수 있다. 혹은 컴퓨터에서 모니터, 키보드, 마우스 등 주변기기를 변경하는 것도 포트만 동일하다면 손쉽게 바꿀 수 있다. 이처럼 컴포넌트를 쉽게 변경할 수 있는 방법이 객체 지향 프로그래밍인데, 이런 유연함을 제공해 주기 위해 우리가 알아야 하는 핵심 개념 키워드는 다형성(Polymorphism)이다.
다형성(Polymorphism)
위에서 말한 레고 블럭과 같은 의미인데, 이번에는 아래 자동차 그림과 함께 이해해 보자.
운전면허자격증을 소지한 운전자가 있다.
이 운전자는 자동차를 운전할 수 있는데, 자동차는 그림과 같이 하나만 있는 게 아니라 K3, 아반떼, 테슬라처럼 많은 종류가 있다.
그렇다면 운전자는 K3용 운전법, 아반테용 운전법, 테슬라용 운전법을 익혀야 할까?
아니다, 운전자는 자동차가 제공하는 운전기능을 이해하고 운전할 줄 알기에 운전면허증이 있다.
그리고 각각의 다른 종류의 자동차도 디자인이나 연료, 최고속도, 시트의 푹신함과 같은 속성은 달라질 수 있지만, 액셀을 밟으면 전진하고, 브레이크를 밟으면 멈추고, 핸들을 좌우로 돌리면 방향이 바뀌는 건 동일하다.
즉 자동차가 자동차역할만 제대로 수행한다면 운전자는 자동차가 어떤 종류이건 운전을 할 수 있다.
이 말은 자동차 역할이 제공해야 할 기능들만 구현한다면 얼마든지 새로운 자동차를 만들 수 있다는 확장성을 제공한다.
이는 책임주도설계(RDD)와도 연관되는 키워드이다.
결국 해당 객체는 해당 객체의 책임만 성실히 이행한다면 내부가 어떤가는 중요하지 않다.
역할과 구현을 분리하라.
역할과 구현을 분리하면 무엇이 장점이기에 굳이 분리를 해야 할까?
객체지향 프로그래밍의 장점인 유연성과 변경 및 확장이 편해지기 위해서 역할과 구현을 분리해야 하는데, 위의 예제처럼 클라이언트(ex:운전자)는 대상의 역할(인터페이스)만 알면 된다.
클라이언트는 대상의 내부가 어떻게 돌아가고 어떤 복잡한 로직을 거치는지 알 필요도 없고, 내부 구조가 변경된다 하더라도 상관없다.
그리고 구현 대상 자체를 변경(K3→아반떼)해도 영향을 받지 않는다.
자바에서의 역할과 구현 분리
자바에서는 다형성(polymorphism)을 이용한다.
- 역할: 인터페이스
- 구현: 인터페이스를 구현한 클래스, 구현 객체
객체를 설계할 때 역할과 구현을 분리하여 작성한다.
객체 설계 시 이처럼 역할(인터페이스)을 먼저 작성한 뒤 그 역할을 수행하는 구현 객체를 만든다.
객체의 협력이라는 관계부터 생각
- 혼자 있는 객체는 없다.
- 클라이언트: 요청, 서버: 응답
- 객체는 클라이언트(요청)가 될 수 있고 서버(응답)가 될 수 있다.
- 수많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다.
다형성의 본질
- 클라이언트를 변경하지 않고 서버에서 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
- 다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야 한다.
- 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
한계점
- 역할(인터페이스) 자체가 변하면, 이를 구현하는 구현체뿐 아니라 클라이언트, 서버 모두가 변경돼야 한다.
- ex: USB 인터페이스가 변경되거나, 대본 자체가 변경될 경우
- 그렇기에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.
스프링과 객체 지향
객체지향의 개념 중 다형성이 가장 중요하고 스프링에서는 이러한 다형성의 장점을 극대화한다.
제어의 역전(IoC), 의존관계 주입(DI)등 다형성을 활용해 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
좋은 객체 지향 설계의 5가지 원칙(SOLID)
클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리
- SRP: 단일 책임 원칙(single responsibility principle)
- OCP: 개방-폐쇄 원칙 (Open/closed principle)
- LSP: 리스코프 치환 원칙 (Liskov substitution principle)
- ISP: 인터페이스 분리 원칙 (Interface segregation principle)
- DIP: 의존관계 역전 원칙 (Dependency inversion principle)
SRP 단일 책임 원칙
Single responsibility principle
- 한 클래스는 하나의 책임만 가져야 한다.
- 하나의 책임이라는 것은 모호하다.
- 클 수 있고, 작을 수 있다.
- 문맥과 상황에 따라 다르다.
- 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것
- 예) UI 변경, 객체의 생성과 사용을 분리
OCP 개방-폐쇄 원칙
Open/closed principle
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다는 의미다.
하지만 확장을 하려면, 당연히 기존 코드를 변경해야 하는데 변경에는 닫혀 있어야 한다고 한다.
이런 거짓말 같은 말이? 이런 문제를 해결하기 위해 다형성을 활용해 보자
인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현한다고 하자
지금까지 배운 역할과 구현의 분리를 생각해 보자
MemberService 클라이언트가 구현 클래스를 직접 선택
- MemberRepository m = new MemoryMemberRepository(); //기존 코드
- MemberRepository m = new JdbcMemberRepository(); //변경 코드
구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.
분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다.
이 문제를 어떻게 해결해야 하나?
객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.
DIP 의존관계 역전 원칙
Dependency inversion principle
프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다.
앞에서 이야기한 역할(Role)에 의존하게 해야 한다는 것과 같다.
객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다! 구현체에 의존하게 되면 변경이 아주 어려워진다.
그런데 OCP에서 설명한 MemberService는 인터페이스에 의존하지만, 구현 클래스도 동시에 의존한다.
- MemberService 클라이언트가 구현 클래스를 직접 선택
- MemberRepository m = new MemoryMemberRepository();
- DIP 위반
객체 지향 설계와 스프링
스프링 핵심 기술 포스팅인데 어째서 자바의 객체 지향 이야기가 계속 나왔나 싶다.
앞서 객체지향 설계의 5가지 원칙에 대해 알아봤는데 이중 OCP, DIP가 다형성만으로는 위배된다는 걸 확인할 수 있었다.
이를 해결하기 위한 프레임워크가 바로 스프링이다.
스프링에서는 OCP, DIP가 가능하면서 다형성이 되도록 지원을 한다.
- DI(Dependency Injection): 의존관계, 의존성 주입
- DI 컨테이너 제공
이렇게 DI 컨테이너를 통해 의존성이 주입되면서 우리는 OCP, DIP 원칙을 지킬 수 있기에 클라이언트의 코드 변경 없이 기능 확장이 가능해진다. 결국, 스프링은 옛날에 개발자가 객체지향 설계 5가지 원칙 (SOLID)를 지키면서 개발을 하다 보니 할 일이 많아지면서 프레임워크를 만들게 되고 그게 스프링이다. (정확히는 DI 컨테이너)
정리
- 모든 설계에서는 역할과 구현을 분리하자.
- 그렇게 함으로써 코드는 유연해지고 변경에 용이해진다.
- 하지만, 인터페이스를 도입할 경우 추상화라는 비용이 발생한다.
- 그럼 모든 클래스들을 비용을 소모해 가며 추상화를 해야 할까?
- 기능을 확장할 가능성이 없는 경우 구체 클래스를 직접 사용하고 향후 필요한 상황이 오면 리팩터링으로 인터페이스를 도입해 추상화를 하는 것도 방법이다.
참고
김영한의 스프링 핵심 원리 - 기본편 강의를 듣고 정리한 내용입니다.
'Spring > 핵심 원리 기본' 카테고리의 다른 글
6. 컴포넌트 스캔 (0) | 2024.07.31 |
---|---|
5. 싱글톤 컨테이너 (0) | 2024.07.31 |
4. 스프링 컨테이너와 스프링 빈 (0) | 2024.07.30 |
3. 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (0) | 2024.07.30 |
2. 스프링 핵심 원리 이해1 - 예제 만들기 (0) | 2024.07.29 |