스프링은 기본적으로 IoC와 DI를 위한 컨테이너로서 동작하지만 그렇다고 "스프링은 단지 IoC/DI 프레임워크다" 라고는 말할 수 없다. 스프링은 단순히 IoC/DI를 편하게 적용하도록 돕는 단계를 넘어서 엔터프라이즈 애플리케이션 개발의 전 영역에 걸쳐 다양한 종류의 기술에 관여한다. 그렇다면 스프링이란 무엇이고, 만들어진 이유와 존재 목적, 추구하는 가치는 무엇일까?
8.1. 스프링의 정의
자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
정의를 봐도 스프링이 무엇인지 감이 바로 오지는 않을 것이다. 하지만 이 정의에는 스프링의 중요한 특징이 잘 담겨있다.
- 애플리케이션 프레임워크
일반적으로 라이브러리나 프레임워크는 특정 업무 분야나 한 가지 기술에 특화된 목표를 가지고 만들어진다.
예를들어, 관계형 DB와 자바 오브젝트를 매핑해주는 ORM 기술을 제공하는 프레임워크 등이 있다. 이러한 프레임워크들은 특정 계층에서 주로 동작하는 한 가지 기술 분야에 집중된다.
스프링 프레임워크는 이와 다르게 '애플리케이션 프레임워크' 라는 특징을 갖고 있다.
애플리케이션 프레임워크는 특정 계층이나 기술, 업무 분야에 국한되지 않고 애플리케이션의 전 영역을 포괄하는 범용적인 프레임워크를 말한다.
스프링의 기원은 J2EE 기술서적에 딸린 예제 코드이다. 이 기술서적에서 강조한 중요한 전략 중 하나는 "항상 프레임워크 기반으로 접근하라" 라는 것이었다. 이를 바탕으로 예제코드도 작성되었고, 일부 개발자들과 책의 저자가 이 예제 코드를 정식으로 스프링 프레임워크라는 오픈소스 프로젝트가 시작돼서 오늘날에 이른 것이다.
단지 여러 계층의 다양한 기술을 그저 한데 모아뒀기 때문에 애플리케이션 프레임워크라고 불리는 것은 아니다.애플리케이션의 전 영역을 관통하는 일관된 프로그래밍 모델과 핵심 기술을 바탕으로 해서 각 분야의 특성에 맞는 필요를 채워주고 있기 때문에, 애플리케이션을 빠르고 효과적으로 개발할 수 있다. 바로 이것이 스프링이 애플리케이션 프레임워크라고 불리는 이유다.
스프링을 MVC 프레임워크 또는 JDBC/ORM 지원 프레임워크라고 생각하는 것은 스프링이 다루는 일부 영역만 봤기 때문이다. 또한, 스프링을 IoC/DI 프레임워크나 AOP 툴이라고 보는 이유는 스프링 핵심 기술에만 주목했기 때문이다. 스프링의 1차 존재 목적은, 핵심 기술에 담긴 프로그래밍 모델을 일관되게 적용해주는 '애플리케이션 프레임워크' 인 것임을 기억해두자.
- 경량급
스프링 정의의 다음 항목은 경량급(lightweight)이다. 그렇다고 코드가 별로 없다는건 아니다. 20여개의 모듈과 수십만 라인에 달하는 '기본 코드'를 가진 아주 무겁고 방대한 규모의 프레임워크이다.
그럼에도 스프링이 가볍다고 하는 이유는 무엇일까? 그것은 "불필요하게 무겁지 않다"는 것이다. 그 당시의 비교대상군과 비교했을때 를 말하는 것이다. 그 당시에는 EJB(Enterprise JavaBeans)어마어마한 규모의 코드와 고가의 느리고 무거운 자바 서버를 사용했다고 한다. 그것에 비해서 스프링이 가볍다는 것이다. - 자바 엔터프라이즈 개발을 편하게
EJB에서도 이와 정확히 일치하는 말을 한다.
EJB를 사용하면 애플리케이션 작성을 편하게 할 수 있다. 로우레벨의 트랜재견이나 상태 관리, 멀티스레딩, 리소스 폴링과 같은 복잡한 로우레벨의 API를 이해하지 못하더라도 아무런 문제 없이 애플리케이션을 개발할 수 있다. - Enterprise JavaBeans 1.0 Specification, Chapter2 Goals
EJB는 이를 달성하지 못하였고, 이후에 출시된 스프링 프레임워크가 EJB가 궁극적으로 이루고자 했던 이 목적을 제대로 실현하게 해주었다.
스프링은 엔터프라이즈 개발의 기술적인 복잡함과 그에 따른 수고를 제거해준다. 여기서 제거한다는 건 그런 기술적인 필요를 무시한다는 의미는 아니다. 엔터프라이즈 개발에서 필연적으로 요구되는 기술적인 요구를 충족하면서도 개발을 복잡하게 만들지 않는다는 점이 스프링의 뛰어난 면이다.
- 오픈소스
스프링을 지금까지 오픈소스 프로젝트 방식으로 개발돼왔다. 이렇게 개발 과정에 많은 사람이 자유롭게 참여하는 방식인 오픈소스 방식이 스프링이 가진 장점이다. 물론 공식적인 개발 은 SpringSoure 부서가 담당한다. 하지만 개발에 영향을 줄 수 있는 의견제시, 패치제공, 버그신고, 토론 등은 전부 가능하다.
8.2. 스프링의 목적
스프링을 제대로 사용하는 건 생각보다 쉽지 않다. 이런 식으로 만들면 된다는 표준 샘플이 있는 것도 아니다. 스프링의 개발 표준이 존재하지는 않지만, 스프링 적용 베스트 프랙티스를 모아다가 그대로 따른다고 해도 스프링을 잘 사용하고 있다고 확신할 수는 없다. 레퍼런스 메뉴얼을 착실히 읽고 관련 서적을 여러 권 공부한다고 해도 스프링을 사용해 어떻게 개발해야 할지 막막할 수도 있다.
스프링은 그 기능과 API 사용 방법을 잘 안다고 해서 잘 쓸 수 있는게 아니다. JAVA 언어와 JDK 라이브러리를 모두 읽었다고 해서 자바 개발을 아주 잘 할수 있을까? 그렇지 않다는 것을 자바 개발자라면 잘 알것이다.
스프링도 마찬가지다. 목적을 바로 이해하고, 그 목적을 이루는 도구로써 스프링을 잘 사용해야 제대로 된 가치를 얻을 수 있다.
그렇다면 스프링의 목적은 무엇일까? '경량급 프레임워크인 스프링을 활용해서 엔터프라이즈 애플리케이션 개발을 편하게' 하는 것이다.
8.2.1. 엔터프라이즈 개발의 복잡함
2000년대 초반 한 조사에서는, 자바 엔터프라이즈 프로젝트가 80% 이상 기간과 예산을 맞추지 못한다고 했다. 가장 큰 이유는 '엔터프라이즈 시스템 개발이 너무 복잡해져서' 이다. 그 이유는 뭘까?
기술적인 제약조건과 요구사항이 늘어가기 때문
현대화가 진행되며, 엔터프라이즈 라는 것에 요구되는 사항들이 많아졌다. 더 좋은 보안성, 더 높은 확장성, 더 확실한 안정성. 이러한 점들을 고려하면서 개발을 진행하다 보면, 순수한 비즈니스 로직을 구현하는 것 외에도 기술적으로 고려할 사항이 많다는 뜻이다. 또한 웹을 통한 사용자 인터페이스 뿐만 아니라, 타 시스템과의 자동화된 연계와 웹 이외의 클라이언트와의 접속을 위한 리모팅 기술도 요구되는 등, 어마어마하게 복잡도가 올라간다.
하지만 이러한 점들을 단순히 고가의 WAS(애플리케이션 서버)나 툴을 사용한다고 충족될 수 있는게 아니다.
따라서 이런 종류의 기술적인 문제를 고려하면서 애플리케이션을 개발해야 하는 부담을 안게 된다.
엔터프라이즈 애플리케이션이 구현해야 할 핵심 기능중 하나인 비즈니스 로직의 복잡함이 증가하기 때문
위에서 나열한 "추가적인 요구사항"이 아니더라도, 핵심 비즈니스 로직 자체의 복잡도가 증가하고 있다.
과거에는 기업 업무 중 회계같은 작업에서만 IT 시스템을 사용했다. 하지만 갈수록 엔터프라이즈 시스템을 이용해 기업의 핵심 업무를 처리하는 비율이 늘어갔고, 점차 대부분의 업무 처리는 컴퓨터를 이용하지 않고는 진행하기 힘들만큼 업무 의존도가 높아졌다. 그만큼 이전과 달리 시스템 개발과 유지보수, 추가 개발 등의 작업에 대한 부담은 커지고 그에 따른 개발 난이도는 더욱 증가하는 것이다.
8.2.2. 복잡함을 해결하려는 도전
엔터프라이즈 개발의 근본적인 복잡함의 원인은 제거할 대상은 아니다. 물론 구현해야 할 비즈니스 로직의 적용범위를 줄이고, 기술적인 요구조건을 일부 생갹한다면 그만큼 개발은 편해질 것이고 적어도 실패하지 않을지는 모른다. 하지만 현실적으로는 불가능하다.
결국 근본적으로 엔터프라이즈 개발에 나타나는 복잡함의 원인은 제거 대상이 아니다. 대신 그 복잡함을 효과적으로 상대할 수 있는 전략과 기법이 필요하다. 문제는 1. 비즈니스 로직의 복잡함을 효과적으로 다루기 위한 방법과, 2. 기술적인 복잡함을 효과적으로 처리하는 데 적용되는 방법이 다르다는 점이다. 따라서 이 두 가지 복잡함을 분리해내는 것이 첫 번째 임무이다.
실패한 해결책 : EJB
놀랍게도, EJB도 마찬가지로 이러한 문제를 전부 알고 있었다. EJB의 기본 전략도 이 두 가지 종류의 복잡함을 분리하는 것이었다. 초반은 어느정도 성과가 있었으나, EJB 환경에서 동작하기 위해 특정 인터페이스를 구현하고, 특정 클래스를 상속하는 등 EJB 개발 방식은 잘못된 선택이었다. 오히려 EJB라는 환경과 스펙에 종속되는 코드로 만들어져야 하는 더 큰 부담을 안게 됐다.
EJB는 결국 일부 기술적인 복잡함을 덜어주려는 시도를 하다가 오히려 더 큰 복잡함을 추가하는 실수를 범했다. 가장 치명적인 건, EJB라는 틀 안에서 자바 코드를 만들게강제함으로써 자바 언어가 원래 가지는 장점마저 잃어버렸다는 사실이다. 다음은 그 예시들이다.
- EJB의 특정 클래스를 상속하게 함으로써 더 이상 상속구조를 적용하지 못하게 만듦.
- 다형성 적용을 근본적으로 제한.
EJB는 객체지향적인 특성은 잃어버린 밋밋한 서비스 스크립트성 코드로 변질돼갔다.
비침투적인 방식을 통한 효과적인 해결책 : Spring
침투적 기술 : 어떤 기술을 적용했을 때 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우라고 칭한다.
EJB는 이러한 침투적 기술이 굉장히 다분했다. 물론 꼭 필요한 기능에서 필요한 것들은 예외일 수 있으나, 여기선 그 정도 수준을 한참 넘어선 것들을 말한다. 이러한 침투적 기술의 반복은 복잡함을 가중시키는 원인이 된다.
반면 비-침투적인 기술은 적용 사실이 코드에 직접 반영되지 않는다는 특징이 있다. 물론 어딘가에선 기술의 적용에 따라 필요한 작업을 해줘야겠지만, 애플리케이션 코드 여기저기 불쑥 등장하지 않는다는 것이 특징이다.
스프링이 성공할 수 있었던 비결은 바로 비침투적인 기술이라는 전략을 택했기 때문이다. 스프링을 이용하면 기술적 복잡함과 비즈니스 로직을 다루는 코드를 깔끔하게 분리할 수 있다. 중요한 점은 그 과정에서 스프링 스스로가 불필요하게 나타나지 않도록 하는 것이다.
8.2.3. 복잡함을 상대하는 스프링의 전략
8.2.3.1. 기술적 복잡함을 상대하는 전략
첫 번째 문제 : 기술에 대한 접근 방식이 일관성이 없고, 특정 환경에 종속적이다.
환경이 바뀌고, 서버가 바뀌고, 적용되는 조건이 바뀌면 적용하는 기술이 달라지고 그에 따라 코드도 바뀐다는 건 심각한 문제다. 비록 동일한 목적으로 만들어졌지만 API의 사용 방법이 다르고, 접근 방식이 다른 기술이 난립하는 것이 현실이다. 그래서 목적이 유사하지만 호환이 안되는 표준, 비표준, 오픈소스, 상요제품 등이 제공하는 각기 다른 API를 사용하도록 코드를 일일이 변경해야 하는 번거로움이 발생한다.
이를 해결하기 위해 서비스 추상화 라는 공략 방법을 고안해냈다. 트랜잭션 추상화, 데이터 엑세스 등이 그런 대표적인 예다. 기술적 복잡함은 일단 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부 기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책이다.
두 번째 문제 : 기술적인 처리를 담당하는 코드가 성격이 다른 코드에 섞여서 등장한다.
앞에서 살펴본 것 처럼, 비즈니스 로직 전후로 경계가 설정돼야 하는 트랜잭션, 비즈니스 로직에 대한 보안 적용 등이 대표적인 예다. 이러한 복잡함을 해결하기 위한 스프링의 접근 방법은 바로 AOP다.
AOP는 최후까지 애플리케이션 로직을 담당하는 코드에 남아 있는 기술 관련 코드를 깔끔하게 분리해서, 별도의 모듈로 관리하게 해주는 강력한 기술이다.
8.2.3.2. 비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략
기술적인 코드, 침투적인 기술이 가져온 불필요한 흔적 등을 제거하고 나면 순수하게 애플리케이션 주요 기능/비즈니스 로직을 담은 코드 둘만이 존재하게 된다. 비즈니스 로직을 담은 코드는 애플리케이션에서 가장 중요한 핵심이 되는 부분이다. 또한 업무의 변화에 따라 자주 변경되거나 수정되는 부분이기도 하다. '증권사 사이트를 통해 주식거래를 분명히 완료했는데도 실제로는 체결이 되지 않거나 계좌의 잔액이 그대로' 라면 엄청난 손해일 것이다.
그래서 비즈니스 로직은 가장 중요하게 다뤄져야 하고, 가장 많이 신경써야 한다.
환경에 종속적인 기술과 침투적인 기법으로 인해 추가된 군더더기에 방해만 받지 않는다면 객체지향 언어로서의 장점을 잘 살려 비즈니스 로직의 복잡함을 최대한 효과적으로 다룰 수 있는 깔끔한 코드를 만드는 건 어렵지 않다.
물론 이 영역은 스프링조차 관여하지 않는다. 비침투적인 기술인 스프링은 핵심 로직에 특별한 이유가 없다면 스프링의 흔적조차 찾을 수 없을만큼 자신을 드러내지 않는다.
결국 비즈니스 로직의 복잡함을 상대하는 전략은 자바라는 객체지향 기술 그 자체다. 스프링은 단지 객체지향 언어의 장점을 제대로 살리지 못하게 방해했던 요소를 제거하도록 도와줄 뿐이다.
핵심 도구 : 객체지향과 DI
EJB가 자바 언어의 객체지향 프로그래밍 장점을 취하지 못하게 하면서, 특정 기술의 스펙에 종속된 설계 방식을 강요했다는 점에 불만을 가졌다.
그 반면 스프링의 모토는 결국 "기본으로 돌아가자" 이다. 자바의 기본인 객체지향에 충실한 설계가 가능하도록 단순한 오브젝트로 개발할 수 있고, 객체지향의 설계 기법을 잘 적용할 수 있는 구조를 만들기 위해 DI같은 유용한 기술을 편하게 적용하도록 도와주는 것이 스프링의 기본 전략이다.
DI는 객체지향의 설계 기술 없이는 그 존재 의미가 없다. 스프링은 단지 이걸 더욱 편하고 쉽게 사용하도록 도와줄 뿐이다. DI는 좋은 객체지향 설계의 결과물임과 동시에, DI를 열심히 적용하다 보면 객체지향 설계 원칙을 잘 따르게 되는 것이다.
결국 모든 스프링 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사용할 수 있도록 돕는 것이라고 볼 수 있다. 스프링 공부만 잘 하면 되는것이 아니라, 복잡한 문제를 풀어나갈 줄 아는 개발자의 능력이 중요한 것이다.
8.3. POJO 프로그래밍
스프링의 핵심 개발자들이 함께 쓴 저서에는, "스프링의 정수는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것" 이라고 쓰여있다. 엔터프라이즈 서비스라는 것은 보통 보안, 트랜잭션과 같은 엔터프라이즈 시스템에서 요구되는 기술을 말한다. 이런 기술을 POJO에 제공한다는 말은, 엔터프라이즈 서비스 기수과 POJO라는 애플리케이션 로직을 담은 코드를 분리했다는 뜻이기도 하다. '분리됐지만 반드시 필요한 엔터프라이즈 서비스 기술을 POJO 방식으로 개발된 애플리케이션 핵심 로직을 담은 코드에 제공한다' 는 것이 스프링의 가장 강력한 특징과 목표다.
8.3.1. 스프링의 핵심 : POJO
스프링의 주요 기술인 IoC/DI, AOP와 PSA(Portable Service Abstraction)은 애플리케이션을 POJO로 개발할 수 있는 가능기술이라고 불린다.
8.3.2. POJO란 무엇인가?
스프링 애플리케이션 개발의 핵심인 POJO를 좀 더 자세히 알아보자. POJO는 Plain Old Java Object의 약자이다. 뜻은 자바의 단순한 오브젝트를 활용하는 기술이다. 있어보이려고 지었다고 한다...(진짜다).
8.3.3. POJO 조건
특정 규약에 종속되지 않는다
POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 한다. 따라서 EJB와 같은 특정 규약에 따라 비즈니스 컴포넌트를 만드는 경우는 POJO에 해당하지 않는다. 특정 규약을 따라 만들게 하는경우는 대부분 특정 클래스를 상속하도록 한다. 그럴 경우 자바의 단일 상속 제한 때문에, 더 이상 해당 클래스에 객체지향적 설계 기법을 적용하기 어려워지는 문제가 생긴다. 또한 다른 환경으로 이전이 힘들다는 문제도 있다.
특정 환경에 종속되지 않는다
특정 환경에 종속적이어야만 동작하는 오브젝트도 POJO라고 할 수 없다. 특정 서버에서만 사용 가능한 API를 직접 쓴 코드를 갖고 있거나, 특정 OS에서 제공하는 기능을 직접 호출하도록 만들어진 오브젝트 등이다. POJO는 환경에 독립적이여야 한다.
그럼 특정 기술규약과 환경에 종속되지 않으면 모두 POJO라고 말할 수 있는가?
많은 개발자들은 평범한 자바 클래스를 써서 개발했다고 해서 POJO 방식으로 개발했다고 생각한다. 하지만 이는 오해인게, POJO는 객체지향적인 자바 언어의 기본에 충실하게 만들어져야 하기 때문이다. 자바는 객체지향 프로그래밍을 가능하게 해주는 언어지만, 자바 문법을 사용했다고 해서 자동으로 객체지향 프로그래밍과 객체지향 설계가 적용됐다고 볼 수는 없다(역은 성립 안함).
진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.
8.3.4. POJO 장점
POJO가 될 수 있는 조건이 그대로 POJO의 장점이 된다.
특정한 기술과 환경에 종속되지 않는 오브젝트
->깔끔한 코드가 될 수 있다.
또한 자동화된 테스트에 매우 유리하다. POJO코드는 매우 유연한 방식으로 원하는 레벨에서 코드를 빠르고 명확하게 테스트할 수 있다.
객체지향적인 설계를 추구
-> 객체지향적인 설계를 자유롭게 적용할 수 있다.
개발자들이 자바와 객체지향 프로그래밍, 모델링과 설계에 대해 배울 때 그려봤던 도메인 모델과, 오랜 경험을 통해 쌓여온 재활용 가능한 설계 모델인 디자인 패턴 등은 POJO가 아니고는 적용하기 힘들다.
객체지향 프로그래밍은 지금까지 나온 프로그래밍 패러다임 중에서 가장 성공했고, 가장 많은 언어에 적용됐으며, 무엇보다도 엔터프라이즈 시스템에서와 같이 복잡한 문제 도메인을 가진 곳에서 가장 효과적으로 사용될 수 있다는 사실이 이미 오랜 시간을 통해 증명됐다.
그렇다면 왜 이런 POJO방식이 EJB처럼 제약이 심하고 특정 기술에 종속적인 코드를 강요하고 자바의 객체지향을 무시하는 기술에 밀렸던 것일까? 그것은 엔터프라이즈 시스템 개발이라는 과제에 잘못된 접근 방법을 선택했기 때문이다.
8.3.5. POJO 프레임워크
스프링을 이용하면 POJO의 장점을 그대로 살려서 엔터프라이즈 애플리케이션의 핵심 로직을 객체지향적인 POJO로 깔끔하게 구현하고, 동시에 엔터프라이즈 환경의 각종 서비스와 기술적인 필요를 POJO 방식으로 만들어진 코드에 적용할 수 있다.

위 그림은 스프링 엔터프라이즈 시스템의 복잡함을 어떻게 다루는지를 보여준다. 스프링은 비즈니스 로직의 복잡함과 엔터프라이즈 기술의 복잡성을 분리해서 구성할 수 있게 도와준다. 하지만 스프링 자신은 기술영역에만 관여하지 비즈니스 로직을 담당하는 POJO에서는 모습을 감춘다. 스프링 자신은 직접 노출하지 않으면서 애플리케이션을 POJO로 쉽게 개발할 수 있게 지원해준다.
스프링은 개발자들이 복잡한 엔터프라이즈 기술보다는 객체지향적인 설계와 개발의 원리에 좀 더 집중할 수 있도록 기회를 제공해준다. 스프링은 매우 자연스럽게 개발자가 좋은 코드를 만들게 해주는 특별한 재주가 있다. 이것이 바로 스프링이 전 세계적으로 그토록 많은 개발자들에게 인정받고 인기를 누리는 이유다.
8.4. 스프링의 기술
엔터프라이즈 개발에서 POJO 개발이 가능하려면 삼각형의 각 변을 이루고 있는 기술들이 뒷받침돼야 한다. 그 세 가지 기술은 바로 IoC/DI, AOP, PSA이다.
어떤 개발자는 스프링을 단지 이 3개의 기술을 제공하기만 하는 기술 프레임워크로 이해하기도 한다. 물론 틀린 얘기는 아니지만, 그렇게 스프링을 특정 기술을 지원해주는 단순한 프레임워크 로 이해하면 스프링의 목적과 가치를 놓치기 쉽다.
스프링의 기술들은 스프링 프레임워크가 만들어진 진정한 목표인 POJO 기반의 엔터프라이즈 개발을 편리하게 해주는 도구일 뿐이다. 또 다른 관점에서 보자면 IoC/DI, AOP, PSA라는 것 자체가 이미 스프링이 중요한 가치를 두는 객체지향의 원리르 충실히 적용해서 나온 결과이기도 하다.
8.4.1. IoC/DI(제어역전/의존성 주입)
IoC/DI는 스프링의 가장 기본이 되는 기술이자 스프링의 핵심 개발 원칙이기도 하다. 나머지 두 기술도 여기에 기반한다.
이런 질문을 해볼 수 있다. 왜 두개의 오브젝트를 분리해서 만들고, 인터페이스로 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해서 외부에서 정하는 것일까? new로 생성하는것보다 좋은 점이 뭘까?
가장 간단한 답변은 '유연한 확장이 가능하게 하기 위해서' 라고 할 수 있다. DI는 개방 폐쇄 원칙이라는 객체지향 설계 원칙으로 잘 설명될 수 있다. 유연한 확장이라는 장점은 OCP(Open-Close Principle)의 '확장에는 열려있다' 에 해당하고, DI는 '변경에는 닫혀있다' 라는 말로 설명 가능하다. 폐쇄 관점에서 볼 때 장점은 '재사용이 가능하다' 라고 볼 수 있다.
DI의 활용 방법
좀 더 구체적으로 DI의 활용 방식을 살펴보면서 그 장점을 생각해보자
핵심기능의 변경
DI의 가장 대표적인 적용 방법은 바로 의존 대상의 구현을 바꾸는 것 이다. 예를 들어, 비즈니스 로직이 변경돼서 새로운 등급결정 정책을 적용해야 한다면, DI를 이용해 새로운 정책을 담은 클래스로 통째로 변경해주면 된다.
핵심기능의 동적인 변경
두 번째 활용 방법은 첫 번째랑 비슷하게 의존 오브젝트의 핵심기능 자체를 바꾸는 것이다. 하지만 일반적인 DI와 달리, 동적으로 매번 다르게 변경할 수 있다. 사용자가 속한 등급에 따라서 그때마다 다른 DataSource를 DAO가 사용할 수 있게 하거나, VIP 사용자는 좀 더 속도가 빠른 별도 DB를 이용하는 방법 등이 있다.
부가기능의 추가
DI의 세 번째 활용방법은 핵심기능은 그대로 둔 채로 부가기능을 추가하는 것이다. 데코레이터 패턴을 생각해보면 된다. 인터페이스를 두고 사용하게 하고, 실제 사용할 오브젝트는 외부에서 주입하는 DI를 적용해두면 쉽게 적용할 수 있다. 핵심 코드는 그대로 둔 채로, 부가기능을 얼마든지 추가할 수 있다.
가장 대표적인 예시는 트랜잭션 이다. 핵심 도메인 로직은 그대로 둔 채로, 결과나 전달 파라미터를 조작할 수 있고 추가적인 작업도 수행할 수 있다.
인터페이스의 변경
때로는 사용하려고 하는 오브젝트가 가진 인터페이스가 클라이언트와 호환되지 않는 경우가 있다.
// B 인터페이스 - A가 사용하도록 설계된 인터페이스
interface B {
void processData(String data);
}
// C 클래스 - B 인터페이스를 구현하지 않은 독립적인 클래스
class C {
public void handleRequest(String request) {
System.out.println("C가 요청을 처리합니다: " + request);
}
}
// A 클래스 - B 인터페이스를 사용하도록 만들어진 클라이언트
class A {
private B b;
// DI를 통해 B 인터페이스 구현체를 주입받음
public A(B b) {
this.b = b;
}
public void execute() {
System.out.println("A가 작업을 시작합니다.");
b.processData("데이터");
}
}
// 어댑터 클래스 - B 인터페이스를 구현하면서 내부적으로 C를 호출
class BToCAdapter implements B {
private C c;
public BToCAdapter(C c) {
this.c = c;
}
@Override
public void processData(String data) {
// B 인터페이스의 메서드를 C의 메서드로 변환(위임)
System.out.println("어댑터가 B 호출을 C로 변환합니다.");
c.handleRequest(data);
}
}
이 예제 코드로 설명이 가능하다. B 인터페이스를 사용하도록 만들어져 있는 A 클래스가 C 클래스를 쓰고 싶을 때, C를 사용하는 Adapter 클래스를 만듦으로써 가능하다.
프록시
프록시는 패턴의 선형적인 응용 방법이다. 필요한 시점에서 실제 사용할 오브젝트를 초기화하는 리소스를 줄이기 위해 지연 로딩을 적용한다. 원본 오브젝트를 호출할 때 마치 로컬에 존재하는 오브젝트처럼 사용할 수 있게 해주는 원격 프록시를 적용하려 할 때도 프록시가 필요하다. 스프링은 EJB 원격 호출을 포함해서 웹 서비스, REST 호출, HTTP 방식의 호출 등 다양한 리모팅 기술을 지원한다.
템플릿과 콜백
템플릿/콜백 패턴은 DI의 특별한 적용 방법이다. 변복적으로 동작하지만 항상 고정적인 작업 흐름과 그 사이에서 자주 바뀌는 부분을 분리해서 템플릿과 콜백으로 만든다. 스프링이 제공하는 20여 가지의 템플릿/콜백이 적용된 기능을 가진다. 템플릿은 한번 만들어두면 계속 재사용할 수 있다는건 기능의 확장에도 변하지 않는다는 OCP의 폐쇄 원칙에 가장 잘 들어맞는 것이다.
싱글톤과 오브젝트 스코프
DI가 필요한 중요한 이유 중 하나는 DI 할 오브젝트의 생명주기를 제어할 수 있다는 것이다. DI를 프레임워크로 이용하면 DI 대상 오브젝트를 컨테이너가 관리한다는 의미다. 오브젝트의 생성부터 관계설정, 이용, 소멸에 이르기까지의 모든 과정을 DI 컨테이너가 주관하기 때문에 그 오브젝트의 스코프를 자유롭게 제어할 수 있다.
가장 기본이 되는 스코프는 역시 싱글톤이다. 하나 또는 소수의 오브젝트가 수많은 클라이언트를 상대로 고성능 서비스를 제공하는 방식은 엔터프라이즈 개발에서 매우 중요하다. 상태를 갖지 않도록 만든 오브젝트가 동시에 여러 스레드의 요청을 처리하는 이런 방식을 적용하면 만들어지는 오브젝트의 개수를 제어하는 일이 매우 중요하다. 전통적인 싱글톤 패턴은 오브젝트에 많은 제약을 가해서 만들어지기 때문에 그다지 권장되지 않는다. 그보다는 컨테이너가 오브젝트를 관리하는 IoC 방식이 유용하다. 스프링의 DI는 기본적으로 싱글톤으로 오브젝트를 만들어서 사용하게 한다. 컨테이너가 알아서 싱글톤을 만들고 관리하기 때문에 클래스 자체는 싱글톤을 고려하지 않고 자유롭게 설계해도 된다는 장점이 있다.
이렇게 오브젝트 스코프를 제어하는 방법 또한 DI를 적용했기 때문에 가능한 활용 방법이다.
테스트
마지막으로 살펴볼 DI의 중요한 요도는 바로 테스트다. 오브젝트와 협력해서 동작하는 오브젝트를 효과적으로 테스트하는 방법은 가능한 한 고립시키는 것이다. DI 없이는 이런 테스트 기법을 적용하기란 불가능하다. 서비스 오브젝트는 테스트 목적으로 만들어진 목 오브젝트로 대체하면 유용하다.
8.4.2. Aspect Oriented Programming(AOP)
스프링의 3대 기술 중 하나이다. 계속해서 스프링은 객체지향 기술과 프로그래밍을 위해 존재하는 프레임워크라고 설명했는데, 난데없이 애스펙트 지향 프로그래밍이 왜 필요할까? 사실은 AOP와 OOP는 배타적인 두 방법론이 아니다.
객체지향 기술은 매우 성공적인 프로그래밍 방식임에 분명하나, 점점 복잡해져 가는 애플리케이션의 요구조건과 기술적인 난해함을 모두 해결하는 데 한계가 있기도 하다. AOP는 이러한 한계를 극복해주는 보조적인 프로그래밍 기술이다. AOP를 사용하면 OOP를 더욱 OOP답게 만들 수 있다. 이러한 AOP를 가장 성공적으로 엔터프라이즈 개발에 보급한 것이 바로 스프링이다.
스프링의 목적인 POJO만으로 엔터프라이즈 애플리케이션을 개발할 때 반드시 필요한 것이 바로 이 AOP 기술이다. IoC/DI를 이용해서 POJO에 선언적인 엔터프라이즈 서비스를 제공할 수 있지만, 일부 서비스는 순수한 객체지향 기법만으로는 POJO 조건 유지가 힘들다. 바로 이런 문제를 해결하고자 AOP가 도입되었다. 그 방식을 알아보자.
첫번째 방법 : 스프링과 같이 다이내믹 프록시를 사용하는 방법
기존 코드에 영향을 주지 않고 부가기능을 적용하게 해주는 데코레이터 패턴을 응용한 것이다. 자바의 객체지향 패턴을 활용한 방법이기 때문에, 만들기 쉽고 적용하기 간편하다. 대신 부가기능을 부여할 수 있는 곳은 메서드 호출이 일어나는 지점 뿐이라는 제약이 있다. 인터페이스와 DI를 활용하는 데코레이터 패턴이 기반원리이기 때문이다.
스프링의 기본적인 AOP 구현 방법은 다이내믹 프록시를 이용하는 프록시 AOP 방식이다. 엔터프라이즈 개발에서 필요로 하는 AOP는 대부분 이 프록시 방식의 AOP면 된다.
두 번째 방법 : 자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법
AspectJ라는 AOP툴이 존재한다. 이는 메서드 호출에서만 지원되는 프록시 AOP에서는 불가능한 다양한 Join포인트들을 제공해준다. 이런 고급 AOP를 적용하기 위해선 JAVA 와 JDK의 지원만으로는 불가능하다. 그만큼 사용하기 까다롭고 번거롭지만, 경우에 따라서는 AspectJ를 사용한다.
AOP의 적용 단계
AOP의 장점이 많다고 해서 무작정 사용하면 심각한 문제가 발생할 위험이 있다. AOP는 하나의 모듈이 수많은 오브젝트에 보이지 않게 적용되기 때문에 매우 주의해서 사용해야 한다.
AOP 적용 1단계 : 미리 준비된 AOP 이용
처음엔 스프링이 미리 만들어 제공하는 AOP를 그대로 가져다 적용하는 것으로 시작한다. 가장 대표적인 것은 역시 트랜잭션이다. 또 다른 하나는 @Configurable 어노테이션을 이용해서 도메인 오브젝트에 DI를 자동 적용시켜주는 AOP 기능이다(지금은 잘 사용하지 않는 방식이다).
AOP 적용 2단계 : 전담팀을 통한 정책 AOP 적용
좀 더 적극적으로 AOP를 이용하는 방식이다. 아직까지는 개발자 개개인이 AOP 기능을 직접 이용하게 하는 것은 안되지만, 애플리케이션 전체적으로 이용 가능한 것을 소수의 AOP 담당자 관리 하에 적용해볼 수 있다. 비즈니스 로직을 가진 오브젝트에 대한 보안, 특정 계층의 오브젝트 이용 전후의 작업 기록을 남기는 로깅, 데이터 추적을 위한 트레이싱, 특정 구간의 실시간 성능 모니터링과 같은 정책적으로 적용할 만한 기능에 AOP를 이용하는 것이다.
이런 일을 AOP를 이용해 한 번에 적용한다면 일반 개발자의 작업에는 전혀 영향을 주지 않을 수 있다. 또한 AOP는 언제든지 기능을 추가하거나 제거할 수 있다. 기존 코드에는 당연히 아무런 영향을 주지 않으면서 말이다.
AOP 적용 3단계 : AOP의 자유로운 이용
첫 번쨰와 두 번째 단계를 거쳐서 AOP에 어느 정도 친숙해지고, 그 장단점과 응용 전략, 위험성 등을 어느정도 이해했다면 이제 개발자 스스로가 AOP를 활용할 수 있는 단계로 넘어갈 수 있다. 이전에는 애플리케이션에 전체적으로 적용되는 정책 AOP 위주로 개발했다면, 이제는 개발자가 원하는 유용한 세부적인 AOP를 이용할 수 있다. 개발자가 위험만 주의한다면 얼마든지 자신이 다루는 코드에 AOP를 적극 활용할 수 있다.
8.4.3 포터블 서비스 추상화(PSA)
세 번째 가능기술은 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있게 해주는 PSA(Portable Service Abstraction)이다. POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않아야 한다. 스프링은 JavaEE를 기본 플랫폼으로 하는 자바 엔터프라이즈 개발에 주로 사용된다. 따라서 다양한 JavaEE기술에 의존적일 수밖에 없다.
특정 환경과 기술에 종속적이지 않다는 게 그런 기술을 사용하지 않는다는 뜻은 아니다. 다만 POJO 코드가 그런 기술에 직접 노출되어 만들어지지 않는다는 말이다. 이를 위해 스프링이 제공하는 대표적인 기술이 바로 일관성 있는 서비스 추상화 기술이다.
스프링은 엔터프라이즈 개발에 사용되는 다양한 기술에 대한 서비스 추상화 기능을 제공한다. 어떤 것은 AOP나 템플릿/콜백 패턴과 결합돼서 사용되기 때문에, 직접적으로 서비스를 이용할 필요가 없다. 대신 설정을 통해 어떤 종류의 기술을 사용할지 지정해줘야 한다.
트랜잭션 서비스 추상화는 코드를 이용해 트랜잭션을 제어하지 않는다면 직접 이용할 이유가 없다. 트랜잭션은 대부분 AOP를 이용해 적용하기 때문에, 직접적인 코드를 만들지 않기 때문이다. 대신 설정에서는 스프링의 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 구현한 구체적인 서비스 클래스를 빈으로 등록해줘야 한다. JTA를 이용해 트랜잭션을 적용하고 싶다면 JtaTransactionManager를 빈으로 등록하고 JTA 환경에 대한 설정을 프로퍼티로 넣어주면 된다.
직접 스프링이 제공하는 API를 사용해서 만드는 경우도 있다. OXM이나 JavaMail을 이용한다면 스프링이 정의한 추상 API를 이용해 코드를 작성한다. 구체적인 기술과 설정은 추가로 따로 작성한다.
스프링의 서비스 추상화의 개념과 장점을 잘 이해한다면 때에 따라 직접 서비스 추상화 기법을 적용할 필요도 있다. 엔터프라이즈 개발에 사용되는 기술은 끊임없이 쏟아져 나온다. 표준 기술뿐 아니라 오픈소스 라이브러리, 상용 프레임워크 형태로도 하루가 멀다하고 새로운 기술이 등장한다. 보편적으로 사용되는 기술이라면 다음 버전의 스프링에서 서비스 추상화 대상으로 포함시킬 가능성이 있지만, 그것을 굳이 기다려야 할 필요는 없다. 필요하면 스프링이 그랬던 것처럼 직접 추상 레이어를 도입하고 일관성 있는 API를 정의해서 사용하면 된다.
'Backend > Spring' 카테고리의 다른 글
| JAVA의 Thread와 Vitrual Thread(in Spring Framework) (0) | 2025.11.29 |
|---|---|
| 11. 데이터 액세스 기술 (0) | 2025.10.02 |
| AOP(Aspect Oriented Programming) (0) | 2025.09.24 |
| Spring에서의 테스트 (0) | 2025.09.16 |
| 자바와 스프링의 예외처리 (1) | 2025.09.10 |