Search
Duplicate

객체 지향 설계와 스프링

목차

스프링의 탄생

스프링은 언제, 왜, 어떻게 나왔을까?
이를 살펴보기위해선 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컨테이너 기술이라고도 할 수 있고 스프링 프레임워크라고도 할 수 있다.
혹은 스프링 부트, 스프링 프레임워크를 포함한 스프링 생태계 전체를 의미할수도 있다.

핵심은?

스프링은 자바 언어 기반의 프레임워크이다.

서버를 구성할때 자바진영이면 기본적으로 스프링으로 구성한다. 자바기반의 대표 프레임워크가 스프링이기 때문인데, 그렇다면 자바의 대표적인 특징이 결국 스프링의 대표 특징이 될 수 있다.
그럼 자바의 가장 큰 특징은 무엇일까? 객체 지향 언어 라는 점이다. 스프링은 객체 지향 언어가 가진 특징인 유연성과 확장성을 잘 살리는 프레임워크로써 좋은 객체 지향 애플리케이션을 개발할 수 있도록 도와준다.

좋은 객체 지향 프로그래밍이란?

객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지 를 주고받고, 데이터를 처리할 수 있다. (협력) 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프 트웨어 개발에 많이 사용된다. -위키(https://ko.wikipedia.org/wiki/객체_지향_프로그래밍)
위키에 나온 객체 지향 프로그래밍의 정의를 정리하자면 프로그램은 각각의 명령어를 순차적으로 수행하는 절차지향을 벗어나 객체 하나하나가 서로 메세지를 주고받으며 로직을 수행하는 협력형태라 볼 수 있다.
그리고 이런 방식은 프로그램을 유연하고 변경이 용이하게 만든다고 하는데, 이게 무슨 의미일까?
레고 블럭을 통해 성을 만든다고 하자. 이때 성의 벽돌하나를 바꾸고자할때 다른 블럭들과 결합만 제대로 된다면 쉽게 교체할 수 있다 이는 변경이 용이하다고 할 수 있다. 혹은 컴퓨터에서 모니터, 키보드, 마우스 등 주변기기를 변경하는것도 포트만 동일하다면 손쉽게 바꿀 수 있다. 이처럼 컴포넌트를 쉽게 변경할 수 있는 방법이 객체 지향 프로그래밍인데, 이런 유연함을 제공해주기 위해 우리가 알아야하는 핵심 개념 키워드는

다형성(Polymorphism)이다.

위에서 말한 레고 블럭과 같은의미인데, 이번에는 아래 자동차 그림과 함께 이해해보자.
스프링-핵심-원리-기본편 좋은 객체 지향 프로그래밍이란? 편
운전면허자격증을 소지한 운전자가 있다. 이 운전자는 자동차를 운전할 수 있는데, 자동차는 그림과 같이 하나만 있는게아니라 K3, 아반떼, 테슬라처럼 많은 종류가 있다. 그렇다면 운전자는 K3용 운전법, 아반테용 운전법, 테슬라용 운전법을 익혀야할까?
아니다, 운전자는 자동차가 제공하는 운전기능을 이해하고 운전할줄 알기에 운전면허증이 있다.
그리고 각각의 다른 종류의 자동차도 디자인이나 연료, 최고속도, 시트의 푹신함과같은 속성은 달라질 수 있지만, 엑셀을 밟으면 전진하고, 브레이크를 밟으면 멈추고, 핸들을 좌우로 돌리면 방향이 바뀌는건 동일하다. 즉 자동차가 자동차역할만 제대로 수행한다면 운전자는 자동차가 어떤 종류이건 운전을 할 수 있다.
이 말은 자동차 역할이 제공해야할 기능들만 구현한다면 얼마든지 새로운 자동차를 만들수 있다는 확장성을 제공한다. 이는 책임주도설계(RDD)와도 연관되는 키워드이다. 결국 해당 객체는 해당 객체의 책임만 성실히 이행한다면 내부가 어떤가는 중요하지 않다.

역할과 구현을 분리하라.

역할과 구현을 분리하면 무엇이 장점이기에 굳이 분리를 해야할까?
객체지향 프로그래밍의 장점인 유연성과 변경및 확장이 편해지기 위해서 역할과 구현을 분리해야하는데, 위의 예제처럼 클라이언트(ex:운전자)는 대상의 역할(인터페이스)만 알면된다. 클라이언트는 대상의 내부가 어떻게 돌아가고 어떤 복잡한 로직을 거치는지 알 필요도 없고, 내부 구조가 변경된다하더라도 상관 없다.
그리고 구현 대상 자체를 변경(K3→아반떼)해도 영향을 받지 않는다.

자바에서는 어떻게 역할과 구현을 분리하는가

자바에서는 다형성(polymorphism)을 이용한다.
역할: 인터페이스
구현: 인터페이스를 구현한 클래스, 구현 객체
객체를 설계할 때 역할과 구현을 분리하여 작성한다.
객체 설계시 이처럼 역할(인터페이스)를 먼저 작성한 뒤 그 역할을 수행하는 구현 객체를 만든다.

다형성의 본질

클라이언트를 변경하지않고 서버에서 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야한다.
클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

한계점

역할(인터페이스) 자체가 변하면, 이를 구현하는 구현체 뿐아니라 클라이언트, 서버 모두가 변경되야 한다.
ex: USB 인터페이스가 변경되거나, 대본 자체가 변경될 경우
그렇기에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.

스프링의 객체 지향

객체지향의 개념중 다형성이 가장 중요하고 스프링에서는 이러한 다형성의 장점을 극대화한다.
제어의 역전(IoC), 의존관계 주입(DI)등 다형성을 활용해 역할과 구현을 편리하게 다룰 수 있도록 지원한다.

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

위 링크를 통해 5가지 원칙을 모두 이해했다면, 이제 이런 고민이 생길 수 있다.
OCP를 지키다보면 Service 클라이언트에서 구현클래스를 직접 선택하는데 그러 DIP위반아닌가?
그렇다 분명 DIP 의존관계 역전 원칙에서는 인터페이스에 의존하고 구현체에 의존하지 말라고했는데, OCP쪽을 보면 직접 구현클래스를 대입해주면서 구현체에 의존성을 가지는 것을 볼 수 있다.
분명 객체지향의 핵심은 다형성(Polimorphism)이라 하였다.
그런데 생각보다 다형성 만으로는 부품을 쉽게 갈아치울수 없고, 구현 객체 변경시 클라이언트 코드도 같이 변경되는 것을 볼 수 있다. 즉, 다형성만으로는 OCP, DIP원칙을 위배할 수밖에 없다.
그럼 어떻게 해야할까? 다음 섹션에서 이에 대해 알아보자.

객체 지향 설계와 스프링

스프링 핵심 기술 포스팅인데 어째서 자바의 객체 지향 이야기가 계속나왔나 싶다.
바로 위 섹션에서 객체지향 설계의 5가지 원칙중 OCP, DIP가 다형성만으로는 위배된다고 했는데, 이를 해결하기 위한 프레임워크가 바로 스프링이다.
스프링에서는 OCP, DIP가 가능하면서 다형성이 되도록 지원을한다.
DI(Dependency Injection): 의존관계, 의존성 주입
DI 컨테이너 제공
이렇게 DI 컨테이너를 통해 의존성이 주입되면서 우리는 OCP, DIP 원칙을 지킬수 있기에
클라이언트의 코드 변경 없이 기능 확장이 가능해진다.
결국, 스프링은 옛날에 개발자가 객체지향 설계 5가지 원칙 (SOLID)를 지키면서 개발을 하다보니 할 일이 많아지면서 프레임워크를 만들게되고 그게 스프링이다. (정확히는 DI 컨테이너)

정리

모든 설계에서는 역할과 구현을 분리하자.
그렇게 함으로써 코드는 유연해지고 변경에 용이해진다.
하지만, 인터페이스를 도입할 경우 추상화라는 비용이 발생한다.
그럼 모든 클래스들을 비용을 소모해가며 추상화를 해야할까?
기능을 확장할 가능성이 없는 경우 구체 클래스를 직접 사용하고 향후 필요한 상황이 오면 리팩터링으로 인터페이스를 도입해 추상화를 하는 것도 방법이다.

다음 챕터