해당 글은 인프런의 김영한님의 강의인 '스프링 핵심 원리 - 기본편'을 공부하며 작성한 글입니다.
스프링의 이전에는 EJB(Enterprise Java Bean)이라는 기술이 있었다. 이 기술은 이론적으로는 참 좋았지만 높은 가격, 어려운 사용 방법, 느린 속도 등의 단점으로 인하여 개발자들이 고통받고 있었다.
이때, 오픈소스인 Hibernate가 개발되며 이 기술이 대세가 되었다. 이 기술을 자바 진영에서 표준 인터페이스화 시켜서 만든 것이 현재의 JPA이다.
EJB에 지친 개발자 Rod Johnson이 책 한 권을 발간하는데 이 책을 기반으로 오픈소스 프로젝트를 제안받아 프레임워크를 개발하게 되는데, EJB의 겨울을 넘어 새로운 시작이라는 뜻으로 Spring으로 짓게 된다.
스프링의 핵심 개념은 Java 언어 기반의 프레임워크다.
그렇다면 자바는 어떤 언어인가?
자바는 객체 지향 언어이다.
그렇다면 객체 지향은 어떤 것인가?
객체 지향 프로그래밍은 기존의 절차적 프로그래밍 방식이 아닌 프로그램을 실세계에 대입하여 수많은 객체(Object)라는 기본 단위로 나누어서 프로그래밍 하는 것이다.
객체 지향의 특징으로는 다음과 같은 특징이 있다.
위에서 언급한 것과 같이 객체 지향 프로그래밍은 프로그램을 '객체'단위로 나누어서 객체들의 모임으로 파악하는 것이다.
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들 수 있다는 장점이 있다.
그렇다면 유연하고 변경이 용이하게 만든다는 것이 무엇일까?
그것이 바로 객체 지향의 꽃인 다형성(polymorphism)이다.
다형성을 이해하기 위하여 편의상 역할과 구현으로 구분할 수 있다. 역할은 인터페이스를 의미하며, 구현은 인터페이스를 구현한 객체이다.
운전자와 자동차의 역할로 나눠보자.
자동차는 어떤 것으로 변경되어도 운전자는 영향 없이 운전을 할 수 있어야 한다.
이렇게 구조를 짜면 구조가 유연해지며 변경이 용이하다.
우리가 TV 리모컨을 사용하지만 리모컨이 어떤 구조로 작동하는지 몰라도 되며, 리모컨의 내부 구조가 변경되어서 성능이 향상되었어도 우리가 사용하는데 영향이 없으며, 리모컨을 다른 리모컨으로 바뀌어도 사용할 수 있다.
그러므로 좋은 객체 지향 프로그래밍은 자바 언어가 제공하는 다형성을 활용하여 역할과 구현을 명확하게 분리하고, 객체를 설계할 때 역할(인터페이스)를 먼저 부여하고, 그 역할을 수행하는 구현 객체를 만드는 것이다.
다시 스프링으로 넘어와서...
스프링은 다형성을 극대화하여 이용할 수 있도록 제어의 역전(IoC), 의존관계 주입(DI)으로 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
좋은 객체 지향 설계의 5가지 원칙을 정리한 것이 SOLID이다.
Single(단일) Responsibility(책임) Principle
하나의 클래스는 하나의 책임만 가져야 한다.
변경이 잇을때 파급 기준이 적다면 단일책임원칙을 잘 따른것으로 본다.
Open(개방) Close(폐쇄) Principle
확장(새로운 기능 추가)에는 열려 있으나 변경에는 닫혀 있어야 한다.
기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화 하도록 프로그램을 작성해야 하는 설계 기법이다.
예를 들어, MemberService 연결 DB를 In-memory DB에서 JDBC DB로 변경한다고 한다면
아래와 같이 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현해도 클라이언트인 서비스 코드를 변경해야만 한다.
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
그러므로 객체를 생성하고 연관관계를 맺어주는 별도의 설정자가 필요하게 된다.
Liskov(리스코프) Substitution(치환) Principle
다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다.
이 원칙을 위반한다면 클라이언트는 인터페이스를 믿고 사용할 수 없어진다. (리모컨의 볼륨 증가 버튼을 눌렀는데 다음 채널로 넘어간다면 우리는 리모컨의 인터페이스를 믿을 수 없다)
Interface(인터페이스) Segregation(분리) Principle
큰 덩어리의 범용 인터페이스보다 특정 클라이언트를 위한 작은 인터페이스 여러개가 낫다.
인터페이스가 변해도 영향을 주지않으며 인터페이스가 명확해지고 대체가능성이 높아진다.
Dependency(의존관계) Inversion(역전) Principle
추상화(역할)에 의존하고 구체화(구현)에 의존하면 안된다.
즉, 운전자(클라이언트)는 자동차라는 추상적인 역할에 의존해야 하지, 구체화인 BMW에 의존하면 안된다.
위의 OCP 문제점과 동일하게 아래의 코드를 보면
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
MemberService는 MemberRepository라는 구체화에도 의존하고, MemoryMemberRepository라는 구현체에도 의존하고 있다.
다형성 만으로는 SOLID 원칙을 모두 지킬 수 없다.
다시 스프링으로 돌아와서 우리는 왜 스프링을 사용하는가?
순수 자바 코드로만 좋은 객체 지향적으로 SOLID 원칙을 모두 지키며 코드를 짜게 된다면, 할일이 너무 많아지게 된다.
그래서 프레임워크로 만들어서 사용하게 된다면 개발자들은 훨씬 더 편하게 좋은 객체 지향 코드를 사용 할 수 있게 된다.
그래서 스프링 프레임워크를 사용하게 된 것이며, 스프링은 다형성만으로는 해결할 수 없었던 OCP, DIP 원칙을 DI 컨테이너라는 기술로 해결한다.
이제, 다음 섹션 부터는 스프링이 왜 만들어졌는지를 알아본다.