스프링의 정의
- 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
- 애플리케이션 프레임워크는 애플리케이션 개발을 빠르고 효율적으로 할 수 있도록 애플리케이션의 바탕이 되는 틀과 공통 프로그래밍 모델, 기술 API등을 제공해준다.
- 복잡한 엔터프라이즈급 개발을 할 때 훌륭한 개발자들이 다른 개발자들을 위해 기본틀을 제공하고, 공통적인 프로그래밍 모델을 제시해주며, 잘 만들어진 API를 제공함.
- 오픈소스의 장점을 충분히 취하면서 동시에 오픈소스 제품의 단점과 한계를 극복하고 있는 전문적이고 성공적인 오픈소스 소프트웨어
애플리케이션 프레임워크
특정 계층이나 기술, 업무 분야에 국한되지 않고 애플리케이션의 전 영역을 포괄하는 범용적인 프레임워크를 말한다.
* 스프링의 기원은 J2EE 기술서적에 딸린 예제 코드이다. 스프링을 처음 만든 사람은 Rod Johnson이라는 자바 개발자. Rod Johnson은 2003년에 "Expert One-on-One J2EE Design and Development"라는 책을 출간했다. 이 책에서 강조하는 중요한 전략의 하나는 "항상 프레임워크 기반으로 접근하라"이다. 당연히 책의 예제 애플리케이션도 프레임워크를 먼저 만들고 나서, 프레임워크를 이용하는 코드를 만드는 방식으로 작성됐다. 바로 이 예제에 포함된 프레임워크가 스프링 프레임워크의 기원이다.
이 책의 내용과 예제로 제공된 프레임워크에 매료된 개발자들이 책의 독자들이 토론하는 출판사 포럼에 모이기 시작했고, 그것을 발전시켜서 지속적으로 개발하자는 의견들과 함께 저자인 Rod Johnson도 참여하면서 정식적으로 스프링 프레임워크라는 이름의 오픈소스 프로젝트가 시작돼서 오늘날에 이른 것이다.
단지 여러 계층의 다양한 기술을 그저 한데 모아뒀기 때문에 애플리케이션 프레임워크라고 불리는 것은 아니다. 애플리케이션의 전 영역을 관통하는 일관된 프로그래밍 모델과 핵심 기술을 바탕으로 해서 각 분야의 특성에 맞는 필요를 채워주고 있기 때문에, 애플리케이션을 빠르고 효율적으로 개발할 수가 있다. 바로 이것이 스프링이 애플리케이션 프레임워크라고 불리는 이유이다.
경량급(lightweight)
스프링 자체가 아주 가볍다거나 작은 규모의 코드로 이뤄졌다는 뜻은 아니다. 그것은 불필요하게 무겁지 않다는 의미이다.
기술 수준이 가볍다거나, 스프링이 유치하고 용도가 제한적이라는 의미는 결코 아니다. 고성능이면서 내구성도 좋은 스포츠카가 그저 덩치만 크고 성능은 떨어지는 차에 비해 오히려 중량은 가볍고 차체도 작다는 것과 마찬가지 개념이라 생각해도 좋을 것 같다.
만들어진 코드가 지원하는 기술 수준은 비슷하더라도 그것을 훨씬 빠르고 간편하게 작성하게 해줌으로써 생산성과 품질 면에서 유리하다는 것이 바로 경량급이라는 말로 표현되는 스프링의 특징이다.
자바 엔터프라이즈 개발을 편하게
스프링은 근본적인 부분에서 엔터프라이즈 개발의 복잡함을 제거해내고 진정으로 개발을 편하게 해주는 해결책을 제시한다.
이 문구는 EJB가 처음 등장했을 때도 사용됐다. EJB 버전 1.0의 스펙문서를 살펴보면 EJB의 목표를 다음과 같이 이야기하고 있다.
EJB를 사용하면 애플리케이션 작성을 편하게할 수 있다. 로우레벨의 트랜잭션이나 상태 관리, 멀티 스레딩, 리소스 풀링과 같은 복잡한 로우레벨의 API 따위를 이해하지 못하더라도 아무런 문제 없이 애플리케이션을 개발할 수 있다.
- Enterprise JavaBeans 1.0 Specification, Chapter 2 Goals
이 목표에서 볼 수 있듯이 편리한 애플리케이션 개발이란, 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않으면서도 애플리케이션의 핵심인 사용자의 요구사항, 즉 비즈니스 로직을 빠르고 효과적으로 구현하는 것을 말한다.
EJB는 이 과정에서 다른 차원의 더 큰 복잡함을 애플리케이션 개발에 끌고 들어오는 실수를 저질렀다. 스프링도 당시 EJB의 잘못된 접근 방법에 대한 대안을 모색하는 중에 등장한 것이다.
따라서 스프링은 EJB가 궁극적으로 이루고자 했던 이 목적을 제대로 실현하게 해주는 프레임워크이다.
오픈소스
오픈소스의 장점은 공개된 커뮤니티 공간 안에서 투명한 방식으로 다양한 참여를 통해 개발되기 때문에 매우 빠르고 유연한 개발이 가능하다는 것이다. 오픈소스 제품의 사용자는 소스코드를 다운받아서 품질과 기능을 얼마든지 검증하고 분석해볼 수 있다. 발견한 버그를 신고하거나 기능 개선을 제안했다면 그것이 어떻게 처리되는지도 지켜볼 수 있다. 개발 중인 경우에도 소스코드까지 투명하게 공개되기 때문에 다양한 현장에 있는 사용자의 피드백이 그만큼 빨리 전달되고 반영된다. 인기 있는 오픈소스 제품이라면 베타 버전임에도 전 세계의 수많은 개발자가 자발적으로 다운받아서 사용해보고 다양한 방식으로 피드백을 주기도 한다.
여러가지 장점이 있기 때문에 오픈소스 개발 모델은 이제 비영리 개발그룹에서만이 아니라 상용 제품을 만들고 영리를 추구하는 일반 기업에서도 적극적으로 이용한다. 대형 소프트웨어 개발업체가 자신이 만든 제품의 일부 또는 전체 소스코드를 오픈소스 커뮤니티에 기증하거나 기업 웹사이트 등을 통해 공개하는 일도 적지 않게 일어나고 있다.
하지만 오픈소스 개발 모델에는 단점도 있다. 가장 큰 취약점은 지속적이고 안정적인 개발이 계속될지가 불확실하다는 것이다. 상당수의 오픈소스 제품은 핵심 개발자의 여가시간을 이용해 일종의 취미활동으로 만들어진다. 그런데 개발자의 개인적인 사정으로 인해 개발을 더 진행할 수 없거나 개발자가 중간에 교체되거나, 개발팀에 불화가 생겨서 개발을 정상적으로 진행하기가 힘들 때도 종종 있다. 어떤 때는 단순한 버그 하나가 수정되기까지 몇 년씩 걸리거나 개발자들이 다 떠나서 프로젝트 자체가 사장되는 최악의 상황까지도 갈 수 있다. 개발 프로젝트라는 게 대부분 그렇긴 하지만, 오픈소스 프로젝트는 특히 개발자 개개인에게 극히 의존적이다.
스프링 개발자는 이런 오픈소스의 문제점과 한계를 잘 알고 있었다. 그래서 오픈소스 개발이라는 방법을 선택하기는 했지만 프레임워크 사용자에게 지속적인 신뢰를 줄 수 있도록 개발을 책임지고 진행할 수 있는 전문 기업을 만들었다.
스프링을 개발하고 있는 스프링소스는 스프링의 창시자인 Rod Johnson을 비롯해 스프링이 오픈소스화되는 데 가장 큰 역할을 한 Juergen Hoeller 와 자바 엔터프라이즈 세계에서 손꼽히는 최상급 개발자들이 주축이 돼서 만든 회사다. 이 회사는 스프링에 대한 전문적인 기술지원과 컨설팅 그리고 스프링을 기반으로 개발된 시스템을 안정적으로 운용할 수 있도록 돕는 상용 제품을 제공함으로써 수익을 얻고, 한편으로는 오픈소스 프로젝트로서 스프링이 효율적으로 개발되도록 지원하고 있다.
스프링은 오픈소스의 장점을 충분히 취하면서 동시에 오픈소스 제품의 단점과 한계를 잘 극복하고 있는, 전문적이고 성공적인 오픈소스 소프트웨어라고 할 수 있다.
스프링의 목적
스프링의 목적은 무엇인가? 스프링이 만들어진 이유는 무엇이고, 스프링을 통해 궁극적으로 이루려고 하는 것은 무엇인가? 그것은 정의를 통해 살펴봤듯이 '경량급 프레임워크인 스프링을 활용해서 엔터프라이즈 애플리케이션 개발을 편하게' 하는 것이다. 그렇다면 굳이 스프링을 사용해서 엔터프라이즈 개발을 편하게 하려는 이유는 뭘까? 원래 엔터프라이즈 개발이란 편하지 않기 때문이다.
1) 엔터프라이즈 개발의 복잡함
2000년대 초반 각종 자바 컨퍼런스에서 자주 논의됐던 주제는 '왜 자바 엔터프라이즈(JavaEE) 프로젝트는 실패하는가?' 였다. 당시 IT 리서치기업의 조사에 따르면 80% 이상의 자바 엔터프라이즈 프로젝트가 실패한다고 했다. 그중 가장 대표적인 원인이 '엔터프라이즈 시스템 개발이 너무 복잡해져서'였다.
복잡함의 근본적인 이유
기술적인 제약조건과 요구사항이 늘어가기 때문이다.
엔터프라이즈 시스템이란 서버에서 동작하며 기업과 조직의 업무를 처리해주는 시스템을 말한다. 엔터프라이즈 시스템은 많은 사용자의 요청을 동시에 처리해야 하기 때문에 서버의 자원을 효율적으로 공유하고 분배해서 사용할 수 있어야 한다. 또한 중요한 기업의 핵심 정보를 처리하거나 미션 크리티컬한 금융, 원자력, 항공, 국방 등의 시스템을 다루기도 하기 때문에 보안과 안정성, 확장성 면에서도 뛰어나야 한다. 따라서 뛰어난 성능과 서비스의 안정성이 요구되고 그런 점을 고려한 개발 기술이 필요하다. 즉, 엔터프라이즈 시스템을 개발하는 데는 순수한 비즈니스 로직을 구현하는 것 외에도 기술적으로 고려할 사항이 많다는 뜻이다.
또 웹을 통한 사용자 인터페이스 뿐만 아니라, 타 시스템과의 자동화된 연계와 웹 이외의 클라이언트와의 접속을 위한 리모팅 기술도 요구된다. 기업의 시스템이 복잡함에 따라 다중 데이터베이스를 하나의 트랜잭션으로 묶어서 사용하는 분산 트랜잭션의 지원도 필요하다. 문제는 이러한 엔터프라이즈 시스템의 기술적인 요구사항은 단순히 고가의 애플리케이션 서버(WAS)나 툴을 사용한다고 충족될 수 있는게 아니라는 점이다. 따라서 이런 종류의 기술적인 문제를 고려하면서 애플리케이션을 개발해야 하는 부담을 안게 된다.
엔터프라이즈 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함이 증가하기 때문이다.
원래 기업 업무란 그 자체로 복잡한데다 다양한 예외상황도 많고 처리해야 하는 정보의 규모도 상당하다. 엔터프라이즈 시스템이 관여하는 업무의 비율이 급격하게 커지고 있으니 당연히 애플리케이션 개발도 힘들고 복잡해져 가는 것이다.
더 큰 문제는 2000년 전후로 전 세계에 불어 닥친 경제위기가 기업의 체질을 크게 바꿨다는 사실이다. 수시로 업무 프로세스를 변경하고 조종하는 것을 상시화할 만큼 변화의 속도가 빨라졌다. 결국 이를 뒷받침해줘야하는 엔터프라이즈 시스템의 변경을 요구할 수밖에 없다. 버그나 오류가 있어서가 아니라, 기능 요구사항과 업무 정책 등이 바뀌기 때문에 애플리케이션을 자주 수정해줘야 하는 시대가 된 것이다. 그만큼 이전과 다르게 시스템 개발과 유지보수, 추가 개발 등의 작업에 대한 부담은 커지고 그에 따른 개발의 난이도는 더욱 증가한 것이다.
복잡함을 가중시키는 원인
비즈니스 로직의 복잡함과 기술적인 복잡함, 하나씩 놓고 봐도 만만치 않은데, 그 두가지를 한 번에 다뤄야 하니 복잡함이 몇 배로 가중되는 것이다.
일반적으로 사람은 성격이 다른 두 가지 종류의 일을 동시에 생각하고 처리하는데 매우 취약하다. 그럼에도 전통적인 자바 엔터프라이즈 개발 기법은 대부분 비즈니스 로직으 ㅣ복잡한 구현 코드와 엔터프라이즈 서비스를 이용하는 기술적인 코드가 자꾸 혼재될 수 밖에 없는 방식이었다. 결국 개발자가 동시에 그 두가지를 모두 신경 써서 개발해야 하는 과도한 부담을 줬고, 그에 따라 전체적인 복잡함은 몇 배로 가중됐다.
2) 복잡함을 해결하려는 도전
제거될 수 없는 근본적인 복잡함
근본적으로 엔터프라이즈 개발에 나타나는 복잡함의 원인은 제거 대상이 아니다. 대신 그 복잡함을 효과적으로 상대할 수 있는 전략과 기법이 필요하다. 문제는 비즈니스 로직의 복잡함을 효과적으로 다루기 위한 방법과 기술적인 복잡함을 효과적으로 처리하는데 적용되는 방법이 다르다는 점이다. 따라서 두 가지 복잡함이 코드에 한데 어우러져 나타나는 전통적인 개발 방식에서는 효과적으로 복잡함을 다루기가 힘들다.
따라서 가장 먼저 할 일은 성격이 다른 이 두 가지 복잡함을 분리해내는 것이다.
실패한 해결책 : EJB
EJB는 기술적인 복잡함을 애플리케이션의 핵심 로직에서 일부분 분리하는데 성공하긴 했다. 선언적 트랜잭션이나 선언적 보안, 컨테이너를 통한 리모팅 기술의 적용, 컴포넌트 단위의 배치, JNDI를 통한 서비스 검색 지원, 서비스 오브젝트의 풀링, 컴포넌트 생명주기 관리 등은 EJB의 목표를 어느 정도 충족시켰다. 반면에 EJB 환경에서 동작하기 위해 특정 인터페이스를 구현하고, 특정 클래스를 상속하고, 서버에 종속적인 서비스를 통해서만 접근하고 사용이 가능하게 만드는 등의 EJB 개발 방식은 잘못된 선택이었다. 애플리케이션 로직을 담은 핵심 코드에서 일부 기술적인 코드가 제거된 건 사실이지만, 오히려 EJB라는 환경과 스펙에 종속되는 코드로 만들어져야 하는 더 큰 부담을 안게 됐다.
EJB는 결국 일부 기술적인 복잡함을 덜어주려는 시도를 하다가 오히려 더 큰 복잡함을 추가하는 실수를 범했다. 가장 치명적인 건, EJB라는 틀 안에서 자바 코드를 만들게 강제함으로써 자바 언어가 원래 갖고 있던 장점마저 잃어버렸다는 사실이다. EJB의 특정 클래스를 상속하게 함으로써 더 이상 상속구조를 적용하지 못하게 만들거나, 다형성 적용을 근본적으로 제한한다거나 하는 것들이다. EJB는 결국 객체지향적인 특성은 잃어버린 밋밋한 서비스 스크립트서 코드로 변질돼갔다. 별다른 장점은 없는데다 개발 방식은 너무 불편했기 때문에 개발자에게 점점 외면당하는 신세가 돼버렸다.
비침투적인 방식을 통한 효과적인 해결책: 스프링
스프링은 EJB의 실패를 교훈으로 삼아서 출발했다. EJB처럼 어떤 기술을 적용했을 때 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우를 침투적인(invasive) 기술이라고 한다. 물론 꼭 필요한 기능을 사용해야 하기 때문에 특정 기술의 API를 이용하게 되는 건 어쩔 수 없다. 그런데 꼭 필요한 기능을 사용하는 것도 아니면서 단지 어떤 기술을 바탕으로 만들어진다고 해서 특정 클래스나 인터페이스, API등의 코드에 마구 등장한다면 그것은 침투적인 기술이 되며 복잡함을 가중시키는 원인이 된다.
반면에 비침투적인(non-invasive) 기술은 기술의 적용 사실이 코드에 직접 반영되지 않는다는 특징이 있다. 어딘가에는 기술의 적용에 따라 필요한 작업을 해줘야 하겠지만, 애플리케이션 코드 여기저기에 불쑥 등장하거나, 코드의 설계와 구현 방식을 제한하지 않는다는 게 비침투적인 기술의 특징이다.
스프링이 성공할 수 있었던 비결은 비침투적인 기술이라는 전략을 택했기 때문이다.
스프링을 통해 성격이 다른 복잡함들을 깔끔하게 분리해줬기 때문에 각각을 효과적으로 상대할 수 있는 기반이 마련됐다. 동시에 스프링이 코드에 불필요하게 등장해서 부가적인 복잡함을 가져오지도 않았다.
3) 복잡함을 상대하는 스프링의 전략
스프링의 기본적인 전략은 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것이다.
기술적 복잡함을 상대하는 전략
기술에 대한 접근 방식이 일관성이 없고, 특정 환경에 종속적이다.
환경이 바뀌고, 서버가 바뀌고, 적용되는 조건이 바뀌면 적용하는 기술이 달라지고 그에 따라 코드도 바뀐다는 건 심각한 문제다.
이렇게 일관성 없는 기술과 서버환경의 변화에 대한 스프링의 공략 방법은 바로 서비스 추상화다. 기술적인 복잡함은 일단 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부 기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책이다.
스프링이 제공하는 템플릿/콜백 패턴은 판에 박힌 반복적인 작업 흐름과 API 사용 코드를 제거해 준다. 이를 통해 기술을 사용하는 코드도 최적화된 핵심 로직에만 집중하도록 도와준다.
기술적인 처리를 담당하는 코드가 성격이 다른 코드에 섞여서 등장한다.
책임에 따라 계층을 구분하고 그 사이에 서로의 기술과 특성에 의존적인 인터페이스나 예외처리 등을 최대한 제거한다고 할지라도 근본적인 엔터프라이즈 서비스를 적용하는 한 이런 문제는 쉽게 해결할 수 없다. 이런 기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 스프링의 접근 방법은 바로 AOP이다.
AOP는 최후까지 애플리케이션 로직을 담당하는 코드에 남아 있는 기술 관련 코드를 깔끔하게 분리해서 별도의 모듈로 관리하게 해주는 강력한 기술이다.
AOP는 기술을 다루는 코드로 인한 복잡함이 기술 그 자체 이상으로 불필요하게 증대되지 않도록 도와주는 가장 강력한 수단이다.
비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략
기술적인 코드, 침투적인 기술이 가져온 불필요한 흔적 등을 제거하고 나면 순수하게 애플리케이션의 주요 기능과 비즈니스 로직을 담은 코드만 독립적으로 존재하게 된다. 이 중에서 기술적인 부분과 느슨하게나마 연관되는 데이터 처리 코드나 웹이나 리모트 인터페이스 코드 등을 제외하면 비즈니스 로직 코드를 다루는 코드가 남는다. 비즈니스 로직을 담은 코드는 애플리케이션에서 가장 중요한 핵심이 되는 부분이다. 또한 업무의 변화에 따라 자주 변경되거나 수정되는 부분이기도 하다. 따라서 대체로 복잡하다.
비즈니스 로직은 가장 중요하게 다뤄져야 하고 가장 많이 신경 써야 한다.
자바는 객체지향 언어의 장점을 잘 살려서 설계된 언어이다. 객체지향 프로그래밍 기법과 언어가 주는 장점인 유연한 설계가 가능하고 재사용성이 높다는 점을 잘 활용하면 자주 바뀌고 조건이 까다로운 비즈니스 로직을 효과적으로 구현해낼 수 있다. 객체지향 분석과 설계(OOAD)를 통해서 작성된 모델을 코드로 구현하고 지속적으로 발전시킬 수도 있다.
결국 비즈니스 로직의 복잡함을 상대하는 전략은 자바라는 객체지향 기술 그 자체다. 스프링은 단지 객체지향 언어의 장점을 제대로 살리지 못하게 방해했던 요소를 제거하도록 도와줄 뿐이다.
핵심도구 : 객체지향과 DI
기술과 비즈니스 로직의 복잡함을 해결하는 데 스프링이 공통적으로 사용하는 도구가 객체지향(OO)이다.
스프링의 모토는 결국 "기본으로 돌아가자"이다. 자바의 기본인 객체지향에 충실한 설계가 가능하도록 단순한 오브젝트로 개발할 수 있고, 객체지향 설계 기법을 잘 적용할 수 있는 구조를 만들기 위해 DI 같은 유용한 기술을 편하게 적용하도록 도와주는 것이 스프링의 기본 전략이다.
여기서 바뀔 수 있는 것은 무엇일까? 여기서 성격이 다르고, 변경의 이유가 다른 기능은 무엇일까? 그리고 그런 후보를 찾을 수 있다면 DI를 적용해서 오브젝트를 분리하고, 인터페이스를 도입하고, DI로 관계를 연결해줄 것이다. 결국 DI는 좋은 오브젝트 설계의 결과물이기도 하지만, 반대로 DI를 열심히 적용하다 보면 객체지향 설계의 원칙을 잘 따르고 그 장점을 살린 설계가 나올 수도 있다.
기술적인 복잡함을 해결하는 문제나 기술적인 복잡함이 비즈니스 로직에 침범하지 못하도록 분리하는 경우에도 DI가 바탕이 된 여러 가지 기법이 활용된다. 반면에 비즈니스 로직 자체의 복잡함을 해결하려면 DI보다는 객체지향 설계 기법이 더 중요하다.
결국 모든 스프링의 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사용할 수 있도록 돕는 것이라고 볼 수 있다. 스프링은 단지 거들 뿐이다. 현장의 업무를 잘 지원하고 유연하게 대응할 수 있는 뛰어난 애플리케이션을 만드는 것은 객체지향을 잘 활용해서 복잡한 문제를 풀어나갈 줄 아는 개발자의 능력에 달려 있다는 사실을 잊지 말아야 한다. 스프링만 잘 공부하면 자바 언어 자체나 객체지향 설계와 개발 실력 따윈 별로 신경 쓰지 않아도 복잡한 엔터프라이즈 시스템 개발을 잘 할 수 있을 거라고 생각하면 오산이다.
POJO 프로그래밍
스프링의 핵심 개발자들이 함께 쓴 "Professional Spring Framework"라는 책이 있다. 이 책에서 스프링 핵심 개발자들은 "스프링의 정수(essence)는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것"이라고 했다. '분리됐지만 반드시 필요한 엔터프라이즈 서비스 기술을 POJO 방식으로 개발된 애플리케이션 핵심 로직을 담은 코드에 제공한다.'는 것이 스프링의 가장 강력한 특징과 목표이다.
1) 스프링의 핵심:POJO
스프링의 주요 기술인 IoC/DI, AOP와 PSA(Portable Service Abstraction)는 애플리케이션을 POJO로 개발할 수 있게 해주는 가능기술(enabling technology)라고 불린다.
2) POJO란 무엇인가?
POJO는 Plain Old Java Object의 첫 글자를 따서 만든 약자이다. 이 단어는 Martin Fowler가 2000년에 컨퍼런스 발표를 준비하다가 만들어낸 용어라고 한다. Martin Fowler는 당시 인기를 끌고 있던 EJB처럼 복잡하고 제한이 많은 기술을 사용하는 것보다는 자바의 단순한 오브젝트를 이용해 애플리케이션의 비즈니스 로직을 구현하는 편이 낫다고 생각했다. 그럼에도 왜 개발자는 자바의 단순한 오브젝트를 사용하길 꺼리는지 궁금했다. 그 이유를 찾아보니 평범한 자바오브젝트에는 EJB와 같은 그럴싸한 이름이 없기 때문이었다. 그래서 뭔가 있어 보이도록 만든 이름이 바로 POJO였다. 같은 설명이지만 그냥 "간단히 자바 오브젝트를 사용하는데요"라고 말하는 것보다 "POJO 방식의 기술을 사용합니다."라고 하면 왠지 더 세련되고 첨단기술을 쓰는 것처럼 느껴진다는 심리를 이용한 것이다. 이 시도는 기대 이상으로 성공적이었다.
3) POJO의 조건
특정 규약(contract)에 종속되지 않는다.
POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 한다. 별다른 가치를 주지도 못하는 규약 따위에 종속되지 않아야 하고, 객체지향 설계의 자유로운 적용이 가능한 오브젝트여야만 POJO라고 불릴 수 있다.
특정 환경에 종속되지 않는다.
진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다. 그런 POJO에 애플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO 프로그래밍이라고 할 수 있다.
4) POJO의 장점
POJO가 될 수 있는 조건이 그대로 POJO의 장점이 된다.
특정한 기술과 환경에 종속되지 않는 오브젝트는 그만큼 깔끔한 코드가 될 수 있다.
또 POJO로 개발된 코드는 자동화된 테스트에 매우 유리하다.
객체지향적인 설계를 자유롭게 적용할 수 있다는 것도 큰 장점이다.
5) POJO 프레임워크
스프링은 POJO를 이용한 엔터프라이즈 애플리케이션 개발을 목적으로 하는 프레임워크라고 했다. POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크를 POJO 프레임워크라고 한다. 스프링 프레임워크와 하이버네이트를 대표적인 POJO 프레임워크로 꼽을 수 있다.
스프링의 기술
스프링에는 POJO 프로그래밍을 손쉽게 할 수 있도록 지원하는 세 가지 가능기술(enabling technology)을 제공한다. IoC/DI, AOP, PSA가 그 기술이다.
스프링의 기술들은 스프링 프레임워크가 만들어진 진정한 목표인 POJO 기반의 엔터프라이즈 개발을 편리하게 해주는 도구일 뿐이다.
1) 제어의 역전(IoC) / 의존관계 주입(DI)
왜 두 개의 오브젝트를 분리해서 만들고, 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해 외부에서 지정하는 것일까? 이렇게 DI 방식으로 하는 것이 그렇지 않은 경우, 즉 직접 자신이 사용할 오브젝트를 new 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?
가장 간단한 답변은 '유연한 확장이 가능하게 하기 위해서'라고 할 수 있다. DI는 개방 폐쇄 원칙(OCP)이라는 객체지향 설계 원칙으로 잘 설명될 수 있다. 유연한 확장이라는 장점은 OCP의 '확장에는 열려있다'에 해당한다. DI는 역시 OCP의 '변경에는 닫혀 있다.'라는 말로도 설명이 가능하다. 폐쇄 관점으로 볼 때 장점은 '재사용이 가능하다'라고 볼 수 있다.
A->B라는 의존관계를 갖는 오브젝트 구조라고 생각해보자. 여기서 확장은 B가 자유롭게 변경될 수 있음을 의미한다. 이는 B가 변경돼도 A는 아무런 영향을 받지 않고 그대로 유지 가능하다는 뜻이기도 하다. B 관점에서는 유연한 확장이고 A 관점으로 보자면 변경 없이 재사용이 가능하다고 볼 수 있는 것이다. B가 B1, B2, B3로 구현 방법을 바뀌어도 된다고 볼 수도 있고, B1, B2, B3 처럼 의존 대상이 바뀌어도 A는 그대로 재사용이 가능하다고 볼 수도 있다.
DI의 활용 방법
핵심기능의 변경
DI의 가장 대표적인 적용 방법은 바로 의존 대상의 구현을 바꾸는 것이다. 디자인 패턴의 전략 패턴이 대표적인 예다. A->B 구조에서 A의 기능 일부를 B에게 위임한다고 했을 때 B의 구현 방식을 필요에 따라 통째로 B1, B2, B3로 바꾸는 것이다.
예를 들어보면 서비스 오브젝트가 사용하는 DAO가 있다고 할 때, DAO의 구현을 JDBC로 했다가, 그것을 JPA, 하이버네이트, JDO, iBatis 등으로 변경하는 것을 생각할 수 있다. 구현 방식을 통째로 바꾸는 것이다.
핵심기능의 동적인 변경
일반적인 DI를 이용한 변경 방법과 달리, 동적으로 매번 다르게 변경할 수도 있다. DI도 기본적으로는 런타임 시에 동적으로 의존 오브젝트를 연결해주는 것이긴 하지만, 일단 DI 되고 나면 그 후로는 바뀌지 않는다. 즉 동적인 방식으로 연결되지만 한번 DI되면 바뀌지 않는 정적인 관계를 맺어주는 것이다.
하지만 DI를 잘 활용하면 애플리케이션이 동작하는 중간에 그 의존 대상을 다이내믹하게 변경할 수 있다.
예를 들면 사용자의 등급에 따라서 다른 DataSource를 사용하게 만들 수도 있다. DAO는 DataSource에 의존한다. DAO->DataSource 관계가 만들어진다. 그런데 이를 DAO 하나가 여러 개의 DataSource에 의존하게 만들 수도 있다. 그리고 현재 접속한 사용자의 등급에 따라서 그때그때 다른 DataSource를 DAO가 사용하게 할 수도 있다. VIP 사용자는 좀 더 속도가 빠른 DB를 이용하게 해서 빠른 처리 속도를 보장해주려고 할 때 적용할 수 있는 기법이다. 물론 DAO를 따로 만들 필요는 없다. 대신 매우 지능적인 방식으로 동작하는 DI 덕분에 선택적으로 사용할 DataSource를 바꿔주는 기법이 가능하다.
동적인 방식으로 핵심 기능을 변경하는 건, 기술적으로 보자면 다이내믹 라우팅 프록시나 프록시 오브젝트 기법을 활용한 것이다. 그런 기법을 적용할 수 있었던 이유는 역시 DI가 있기 때문이다. DI 없이는 불가능하다. DI의 원칙은 여전히 지켜지므로 확장과 재사용이라는 장점은 손상되지 않고 오히려 더 가치를 드러낸다.
부가기능의 추가
핵심기능은 그대로 둔 채 부가기능을 추가하는 것이다. 데코레이터 패턴을 생각해보면 된다. 인터페이스를 두고 사용하게 하고, 실제 사용할 오브젝트는 외부에서 주입하는 DI를 적용해두면 데코레이터 패턴을 쉽게 적용할 수 있다. 그래서 핵심기능과 클라이언트 코드에는 전혀 영향을 주지 않으면서 부가적인 기능을 얼마든지 추가할 수 있다.
인터페이스의 변경
때로는 사용하려고 하는 오브젝트가 가진 인터페이스가 클라이언트와 호환되지 않는 경우가 있다. 또는 여러 종류의 인터페이스를 가졌지만 사실은 비슷한 기능을 담당하는 오브젝트를 바꿔가면서 사용하고 싶을 때도 있다. 이렇게 클라이언트가 사용하는 인터페이스와 실제 오브젝트 사이에 인터페이스가 일치하지 않는 경우에도 DI가 유용하다.
A가 C 오브젝트를 사용하려 한다고 해보자. 하지만 A는 원래 B 인터페이스를 사용하도록 만들어져 있고 C는 B 인터페이스를 구현하지 않았다. 이 때 A가 DI를 통해 B의 구현 오브젝트를 받도록 만들어져 있다면 B 인터페이스를 구현했으면서 내부에서 C를 호출해주는 기능을 가진 어댑터 오브젝트를 만들어 A에 DI 해주면 된다. 220V 전기를 원하는 9V, 6V, 12V 식으로 바꿔주는 어댑터처럼, 인터페이스가 다른 오브젝트를 클라이언트가 사용하는 인터페이스로 바꿔주는 기능을 이용하면 되는 것이다. A->B(C로 위임)->C 처럼 구성된다. 여전히 A는 DI 덕분에 자신의 코드를 수정하지 않아도 된다. 이처럼 인터페이스가 일치하지 않는 호출이 필요한 경우에도 DI는 유용하다. 디자인 패턴에서 말하는 오브젝트 방식의 어댑터 패턴의 응용이라고 볼 수 있다.
이를 좀 더 일반화해서 아예 인터페이스가 다른 다양한 구현을 같은 방식으로 사용하도록, 중간에 인터페이스 어댑터 역할을 해주는 레이어를 하나 추가하는 방법도 있다. 스프링의 대표적인 기술로도 분류되는 일관성 있는 서비스 추상화(PSA)가 그런 방법이다. PSA는 클라이언트가 일관성 있게 사용할 수 있는 인터페이스를 정의해주고 DI를 통해 어댑터 역할을 하는 오브젝트를 이용하게 해준다. 이를 통해서 다른 인터페이스를 가진 로우레벨의 기술을 변경하거나 확장해가면서 사용할 수 있는 것이다.
프록시
프록시 패턴의 전형적인 응용 방법도 있다. 필요한 시점에서 실제 사용할 오브젝트를 초기화하고 리소스를 준비하게 해주는 지연된 로딩(lazy loading)을 적용하려면 프록시가 필요하다. 원격 오브젝트를 호출할 때 마치 로컬에 존재하는 오브젝트처럼 사용할 수 있게 해주는 원격 프록시를 적용하려고 할 때도 프록시가 필요하다. 두 가지 방법 모두 DI를 필요로 한다. 스프링은 EJB 원격 호출을 포함해서 웹 서비스, REST 호출, HTTP 방식의 호출 등 다양한 리모팅 기술을 지원한다.
템플릿과 콜백
반복적으로 등장하지만 항상 고정적인 작업 흐름과 그 사이에서 자주 바뀌는 부분을 분리해서 템플릿과 콜백으로 만들고 이를 DI 원리를 응용해 적용하면 지저분하게 매번 만들어야 하는 코드를 간결하게 만들 수 있다.
콜백을 얼마든지 만들어서 사용할 수 있다는 건 개방을 통한 유연한 확장성을 보여주는 것이며, 템플릿은 한 번 만들어두면 계속 재사용할 수 있다는 건 기능의 확장에도 변하지 않는다는 OCP의 폐쇄 원칙에 가장 잘 들어맞는 것이다.
싱글톤과 오브젝트 스코프
DI를 프레임워크로 이용한다는 건 DI 대상 오브젝트를 컨테이너가 관리한다는 의미이다. 오브젝트의 생성부터 관계설정, 이용, 소멸에 이르기까지의 모든 과정을 DI 컨테이너가 주관하기 때문에 그 오브젝트의 스코프를 자유롭게 제어할 수 있다.
가장 기본이 되는 스코프는 역시 싱글톤이다. 하나 또는 소수의 오브젝트가 수많은 클라이언트를 상대로 고성능 서비스를 제공하는 방식은 엔터프라이즈 개발에서 매우 중요하다. 상태를 갖지 않도록 만든 오브젝트가 동시에 여러 스레드의 요청을 처리하는 이런 방식을 적용하려면, 만들어지는 오브젝트의 개수를 제어하는 일이 매우 중요하다. 스프링의 DI는 기본적으로 싱글톤으로 오브젝트를 만들어서 사용하게 한다. 컨테이너가 알아서 싱글톤을 만들고 관리하기 때문에 클래스 자체는 싱글톤을 고려하지 않고 자유롭게 설계해도 된다는 장점이 있다.
테스트
다른 오브젝트와의 사이에서 일어나는 일을 테스트를 위해 조작할 수 있도록 만든다. 그래야만 테스트 대상인 오브젝트의 기능에 충실하게 테스트가 가능하다.
의존 오브젝트를 대신해서 스텁 또는 목 오브젝트 같은 테스트 대역을 활용해야 한다. 이때도 DI는 중요한 역할을 한다. DI를 위해 만든 수정자 메소드를 사용하면 테스트 코드 안에서 수동으로 목 오브젝트를 주입할 수 있다. 또는 테스트용으로 설정을 별도로 만드는 방법도 있다. DI 없이는 이런 테스트 기법을 적용하기란 불가능하다.
2) 애스펙트 지향 프로그래밍(AOP)
객체지향 기술은 매우 성공적인 프로그래밍 방식임에 분명하다. 하지만 한편으로는 점점 복잡해져 가는 애플리케이션의 요구조건과 기술적인 난해함을 모두 해결하는데 한계가 있다. AOP는 바로 이러한 객체지향 기술의 한계와 단점을 극복하도록 도와주는 보조적인 프로그래밍 기술이다. AOP를 사용하면 그 결과로 OOP를 더욱 OOP답게 만들 수 있다.
AOP의 적용 기법
스프링과 같이 다이내믹 프록시를 사용하는 방법이다.
이 방법은 기존 코드에 영향을 주지 않고 부가기능을 적용하게 해주는 데코레이터 패턴을 응용한 것이다. 자바의 객체지향 패턴을 활용한 방법이기 때문에 만들기 쉽고 적용하기 간편하다. 대신 부가기능을 부여할 수 있는 곳은 메소드의 호출이 일어나는 지점 뿐이라는 제약이 있다. 인터페이스와 DI를 활용하는 데코레이터 패턴이 기반원리이기 때문이다. 부가기능을 구현한 코드나 기능을 적용할 대상을 찾는 방법 모두 평범한 자바 클래스로 만들면 된다. 스프링의 기본적인 AOP 구현 방법은 다이내믹 프록시를 이용한 프록시 AOP 방식이다. 엔터프라이즈 개발에서 필요로 하는 AOP는 대부분이 이 프록시 방식의 AOP면 된다.
자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법이다.
AspectJ라는 유명한 오픈소스 AOP 툴이 있다. 프록시 방식의 AOP에서는 불가능한 다양한 조인 포인트를 제공한다. 메소드 호출뿐 아니라 인스턴스 생성, 필드 액세스, 특정 호출 경로를 가진 메소드 호출 등에도 부가기능을 제공할 수 있다. 이런 고급 AOP 기능을 적용하려면 자바 언어와 JDK의 지원만으로는 불가능하다. 그 대신 별도의 AOP 컴파일러를 이용한 빌드 과정을 거치거나, 클래스가 메모리로 로딩될 때 그 바이트 코드를 조작하는 위빙과 같은 별도의 방법을 이용해야 한다. 그만큼 사용하기 까다롭고 번잡하지만 경우에 따라서는 프록시 방식의 AOP로는 할 수 없는 작업을 위해 AspectJ를 사용해야 한다.
AOP의 적용 단계
AOP 적용 1단계: 미리 준비된 AOP 이용
일단 처음에는 스프링이 미리 만들어서 제공하는 AOP 기능을 그대로 가져다 적용하는 것으로 시작한다. 스프링이 직접 제공하는 대표적인 AOP는 바로 트랜잭션이다. DB를 사용하는 애플리케이션이라면 트랜잭션이 필요할 테니 이 트랜잭션 적용을 스프링 AOP 도입의 첫 번째 단계로 이용한다. AOP 설정을 통해서 트랜잭션이 어떻게 많은 오브젝트에 투명하게 적용되는지 관찰해보고, AOP의 특성과 동작원리를 이해해보자.
스프링에는 트랜잭션만큼 자주 사용되진 않지만 특정 아키텍처를 선택했을 때 사용할 수 있도록 @Configurable 애노테이션을 이용해서 도메인 오브젝트에 DI를 자동적용해주는 AOP가 있다.
AOP 적용 2단계 : 전담팀을 통해 정책 AOP 적용
아직까지는 개발자 개개인이 AOP 기능을 직접 이용하게 해서는 안된다. 대신 애플리케이션 전체적으로 이용가능한 것을 소수의 AOP 담당자 관리하에 적용해볼 수 있다. 대표적으로 비즈니스 로직을 가진 오브젝트에 대한 보안, 특정 계층의 오브젝트 이용 전후의 작업 기록을 남기는 로깅, 데이터 추적을 위한 트레이싱, 특정 구간의 실시간 성능 모니터링과 같은 정책적으로 적용할 만한 기능에 AOP를 이용하는 것이다.
AOP 적용 3단계 : AOP의 자유로운 이용
첫 번째와 두 번째 단계를 거쳐서 AOP에 어느 정도 친숙해지고, 그 장단점과 응용 전략, 위험성 등을 어느 정도 이해했다면 이제는 개발자 스스로가 AOP를 활용할 수 있는 단계로 넘어갈 수 있다. 이제는 개발자가 구현하는 기능에 적용하면 유용한 세부적인 AOP를 이용할 수 있다.
3) 포터블 서비스 추상화(PSA)
세 번째 가능 기술은 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있게 해주는 PSA(Portable Service Abstraction)다. POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않아야 한다. 스프링은 JavaEE를 기본 플랫폼으로 하는 자바 엔터프라이즈 개발에 주로 사용된다. 따라서 다양한 JavaEE를 기본 플랫폼으로 하는 자바 엔터프라이즈 개발에 주로 사용된다. 따라서 다양한 JavaEE 기술에 의존적일 수밖에 없다. 특정 환경과 기술에 종속적이지 않다는 게 그런 기술을 사용하지 않는다는 뜻은 아니다.다만 POJO 코드가 그런 기술에 직접 노출되어 만들어지지 않는다는 말이다. 이를 위해 스프링이 제공하는 대표적인 기술이 바로 일관성 있는 서비스 추상화 기술이다.
스프링은 엔터프라이즈 개발에 사용되는 다양한 기술에 대한 서비스 추상화 기능을 제공한다. 어떤 것은 AOP나 템플릿/콜백 패턴과 결합돼서 사용되기 때문에 직접적으로 서비스를 이용할 필요가 없다. 대신 설정을 통해 어떤 종류의 기술을 사용할지 지정해줘야 한다.
트랜잭션 서비스 추상화는 코드를 이용해 트랜잭션을 제어하지 않는다면 직접 이용할 이유가 없다. 트랜잭션은 대부분 AOP를 이용해 적용하기 때문에 직접 코드를 만들지 않기 때문이다. 대신 설정에서는 스프링의 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 구현한 구체적인 서비스 클래스를 빈으로 등록해줘야 한다. JTA를 이용해 트랜잭션을 적용하고 싶다면 JtaTransactionManager를 빈으로 등록하고 JTA 환경에 대한 설정을 프로퍼티로 넣어주면 된다.
서비스 추상화를 위해 필요한 기술은 DI 뿐이다. 결국 DI 응용 방법의 한 가지이므로 DI를 적극 활용해서 개발한다면 서비스 추상화는 자연스럽게 만들어 쓸 수 있다. 서비스 추상화는 단지 구체적인 기술에 종속되지 않게 하기 위해서만 사용되는 것은 아니다. 테스트가 어렵게 만들어진 API나 설정을 통해 주요 기능을 외부에서 제어하게 만들고 싶을 때도 이용할 수 있다.
'Program > Spring Framework' 카테고리의 다른 글
Spring 스프링 2일차 - beanFactory 생성 (0) | 2015.07.28 |
---|---|
Spring 스프링 1일차 - 컨테이너란? (0) | 2015.07.27 |
Spring 스프링 1일차 - AOP (Aspect Oriented Programming)란? (0) | 2015.07.27 |
Spring 스프링 1일차 - IoC(Inversion of Control - 제어의 역전)란? (0) | 2015.07.27 |
Spring 스프링 1일차 - MVC 모델(패턴)이란? (0) | 2015.07.27 |