스프링은 단순히 Ioc/DI를 편하게 적용하도록 돕는 단계를 넘어서 엔터프라이즈 애플리케이션 개발의 전 영역에 걸쳐 다양한 종류의 기술에 관여한다.

1. 스프링의 정의

자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크

이 정의에 나타나 있는 스프링의 중요한 특징에 대해서 알아보자.

  • 애플리케이션 프레임워크

먼저 프레임워크에 대해 생각해보면, MVC나 DB 등 특정 기술을 도와주는 역할로 알고 있다. 하지만 스프링은 JavaEE 전반을 도와주는 것이기 때문에 에플리케이션 프레임워크라고 불린다.
애플리케이션 프레임워크는 애플리케이션 개발의 전 과정을 빠르고 편리하며 효율적으로 진행하는데 일차적인 목표를 두는 프레임워크다.
Spring의 개발 배경을 살펴보면, JavaEE 개발을 하던 로드 존슨이 불편함을 느끼고 스스로 개선방법을 고안하다 나온 것이다. 로드 존슨은 항상 “프레임워크 기반으로 접근하라”의 전략을 가지고 코딩을 하였다 한다.

  • 경량급

스프링이 경량급이라는 건 스프링 자체가 아주 가볍다거나 작은 규모의 코드로 이뤄졌다는 뜻은 아니다.
그럼에도 가볍다고 하는 이유는 불필요하게 무겁지 않다는 의미다. 특히 스프링이 처음 등장하던 시절의 자바 주류 기술이었던 예전의 EJB 같은 과도한 엔지니어링이 적용된 기술과 스프링을 대비시켜 설명하려고 사용했던 표현이다.
Spring 전 EJB에서 개발환경을 구성하려면 EJB 컨테이너를 가진 WAS를 준비해야 했다. Tomcat이나 Jetty와 같이 단순한 서버환경에 비하면 너무나도 무거운 서버환경이였다.
그러면서 자연스럽게 서버환경을 구성하기 위한 코드들이 필요없어지고, 코드량이 줄어들게 되었다.
만들어진 코드가 지원하는 기술수준은 비슷하더라도 그것을 훨씬 빠르고 간편하게 작성하게 해줌으로써 생산성과 품질 면에서 유리하다는 것이 바로 경량급이라는 말로 표현되는 스프링의 특징이다.

  • 자바 엔터프라이즈 개발을 편하게

스프링은 근본적인 부분에서 엔터프라이즈 개발의 복잡함을 제거해내고 진정으로 개발을 편하게 해주는 해결책을 제시한다. 단순히 편리한 몇 가지 도구나 기능을 제공해주는 차원이 아니다.
편리한 애플리케이션 개발이란 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않으면서도 애플리케이션의 핵심인 사용자의 요구사항, 즉 비즈니스 로직을 빠르고 효과적으로 구현하는 것을 말한다.

2. 스프링의 목적

스프링을 사용해서 엔터프라이즈 애플리케이션 개발을 편하게 하려는 이유는 뭘까? 원래 엔터프라이즈 개발이란 편하지 않기 때문이다.

2.1 엔터프라이즈 개발의 복잡함

자바 엔터프라이즈(JavaEE) 개발이 실패하는 가장 대표적인 이유는 ‘엔터프라이즈 시스템 개발이 너무 복잡해져서’였다.

복잡함의 근본적 이유

  • 기술적인 제약조건과 요구사항의 증가
    예를 들어, DB 다중을 하나의 트랜잭션으로 처리하여 하고, 보안, 안정성 등 많은 요구사항이 있다.
  • 엔터프라이즈 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함의 증가
    단순 계산 뿐만 아니라 모든 영역에서 IT 시스템을 사용함

2.2 복잡함을 해결하려는 도전

제거될 수 없는 근본적인 복잡함
엔터프라이즈 개발의 근본적인 복잡함의 원인은 제거할 대상은 아니다. 현실적으로는 불가능하기 때문이다.

실패한 해결책:EJB
일부분은 복잡함을 해결하였지만 다른 큰 단점을 가졌다. 복잡함을 해결하기 위해 EJB의 구현체를 상속받아야 해 JAVA 언어 자체의 장점을 잃어버렸고, 다형성 적용을 근본적으로 막아 버렸다.

비침투적인 방식을 통한 효과적인 해결책: 스프링
스프링은 EJB의 실패를 보고 출발하였다. EJB처럼 어떤 기술을 적용했을 때 그 기술과 관련된 코드가 비즈니스 로직에 등장하는 것을 침투적인 방식이라 한다.
하지만, 스프링에서는 비침투적인 방식을 통해 코드에 불쑥 등장하지 않도록 하였다. 물론, 특정 기능을 하기 위해 어디선가는 설정해야 겠지만, 비즈니스 로직과 이 작업의 분리를 취할 수 있게 된다.

2.3 복잡함을 상대하는 스프링의 전략

스프링의 기본적인 전략은 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것이다. 이 분리를 통해 두 가지 복잡함의 문제를 효과적으로 공략하게 해준다.

엔터프라이즈 기술을 적용했을 때 발생하는 복잡함의 문제

1. 기술에 대한 접근 방식이 일관성이 없고, 특정 환경에 종속적이다.
일관성 없는 기술과 서버환경의 변화에 대한 스프링의 공략 방법은 바로 서비스 추상화다.
앞에서 보았던 트랜잭션 추상화, OXM 추상화, 데이터 액세스에 관한 일관된 예외변환 기능, 데이터 액세스 기술에 독립적으로 적용 가능한 트랜잭션 동기화 기법 등이 대퐁적인 예다.
기술적인 복잡함은 일단 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책이다.

2. 기술적인 처리를 담당하는 코드가 성격이 다른 코드에 섞여서 등장한다.
기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 스프링의 접근 방법은 바로 AOP다.
AOP는 최후까지 애플리케이션 로직을 담당하는 코드에 남아 있는 기술 관련 코드를 깔끔하게 분리해서 별도의 모듈로 관리하게 해주는 강력한 기술이다.

3. POJO 프로그래밍

스프링의 핵심 개발자들이 함께 쓴 『Professional Spring Framework』란 책이 있다. 이 책에서 스프링의 핵심 개발자들은,

스프링의 정수(essence)는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것이다.

라고 했다. 엔터프라이즈 서비스라고 하는 것은 보안, 트랜잭션과 같은 엔터프라이즈 시스템에서 요구되는 기술을 말한다.
이런 기술을 POJO에 제공한다는 말은, 뒤집어 생각해보면 엔터프라이즈 서비스 기술과 POJO라는 애플리케이션 로직을 담은 코드를 분리했다는 뜻이기도 하다.
분리됐지만 반드시 필요한 엔터프라이즈 서비스 기술을 POJO 방식으로 개발된 애플리케이션 핵심 로직을 담은 코드에 제공한다‘는 것이 스프링의 가장 강력한 특징과 목표다.

3.1 스프링의 핵심 : POJO

스프링 소스의 CTO인 아드리안 콜리어Adrian Colyer가 스프링의 핵심 개념을 설명하기 위해 만든 그림인 ‘스프링 삼각형’에는 스프링의 핵심이 가장 잘 나타나있다.

.

위의 그림은 스프링으로 개발한 애플리케이션의 기본 구조를 보여준다. 스프링 애플리케이션은 POJO를 이용해서 만든 애플리케이션 코드와, POJO가 어떻게 관계를 맺고 동작하는지를 정해놓은 설계정보로 구분된다.

3.2 POJO란 무엇인가?

POJO는 Plain Old Java Object의 첫 글자를 따서 만든 약자다.

3.3 POJO의 조건

단순하게 보자면 그냥 평범한 자바오브젝트라고 할 수 있지만 좀 더 명확하게 하자면 적어도 다음의 조건을 충족해야 POJO라고 불릴 수 있다.

  • 특정 규약에 종속되지 않는다.
    POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 한다.
    따라서 특정 클래스를 상속해서 만들어야 하는 식으로 특정 규약을 따라서 만들게 하는 것들(ex. EJB, 스트럿츠1)은 POJO가 아니다. 상속을 강제하게 되면 그 상속을 통해 탄생한 클래스들은 객체지향을 살릴 수 없게 되기 때문이다.

  • 특정 환경에 종속되지 않는다.
    특정 환경에 종속적이어야만 동작하는 오브젝트도 POJO라고 할 수 없다.
    EJB 3 는 분명 이전 버전의 문제점이었던 규약에 따라 오브젝트를 만들어야 한다는 단점은 극복했다. 그러나 여전히 JNDI 라는 서버 서비스를 필요로 하며, JNDI가 없는 환경에서는 그대로 사용하기가 힘들다. 이렇게 특정 환경에 종속적이어도 POJO라고 할 수 없다. 그러나 오해하면 안 되는 것이, 특정 기술 규약과 환경에 종속되지 않는다고 다 POJO인 것은 아니다. POJO는 객체지향이 충실하게 적용되었을 때에만 사용할 수 있는 용어이다.
    진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.
    그런 POJO에 어플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO 프로그래밍이라고 할 수 있다.

3.4 POJO의 장점

POJO가 될 수 있는 조건이 그대로 POJO의 장점이 된다. 정리하자면,
(1) 특정 기술과 환경에 종속되지 않는 오브젝트는 그만큼 깔끔한 코드가 될 수 있다.
(2) POJO로 개발된 코드는 자동화된 테스트에 매우 유리하다.
(3) 객체지향적 설계를 자유롭게 적용할 수 있다는 것도 큰 장점이다.

3.5 POJO 프레임워크

스프링은 POJO를 이용한 엔터프라이즈 애플리케이션 개발을 목적으로 하는 프레임워크이다.
POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크를 POJO 프레임워크라고 한다.
스프링은 엔터프라이즈 애플리케이션 개발의 모든 영역과 계층에서 POJO 방식의 구현이 가능하게 하려는 목적으로 만들어졌다.

4. 스프링의 기술

스프링에는 POJO 프로그래밍을 손쉽게 할 수 있도록 지원하는 세 가지 기능기술enabling technology이 존재한다. 앞서 살펴본 스프링 삼각형에서 알 수 있듯이, 엔터프라이즈 개발에서 POJO 개발이 가능하려면 삼각형의 각 변을 이루고 있는 기술들이 뒷받침돼야 한다.
그 세 가지 기술은 바로 IoC/DI, AOP, PSA다.

4.1 제어의 역전(IoC) / 의존관계 주입(DI)

왜 두 개의 오브젝트를 분리해서 만들고, 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해 외부에서 지정하는 것일까?
직접 자신이 사용할 오브젝트를 new 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?

가장 간단한 답변은 ‘유연한 확장이 가능하게 하기 위함’이다. DI는 개방 폐쇄 원칙(OCP)이라는 객체지향 설계 원칙으로 잘 설명될 수 있다.
유연한 확장이라는 장점은 OCP의 ‘확장에는 열려 있다(개방)’에 해당한다.
DI는 역시 OCP의 ‘변경에는 닫혀 있다(폐쇄)’라는 말로도 설명이 가능하다. 폐쇄 관점에서 볼 때 장점은 ‘재사용이 가능하다’라고 볼 수 있다.

DI의 활용 방법

  • 핵심기능의 변경
    DI의 가장 대표적인 적용 방법은 바로 의존 대상의 구현을 바꾸는 것이다. 디자인 패턴의 전략 패턴이 대표적인 예다. 실제 의존하는 대상이 가진 핵심기능을 DI 설정을 통해 변경하는 것이 대표적인 DI의 활용 방법이다

  • 핵심기능의 동적인 변경
    두 번째 활용 방법은 첫 번째랑 비슷하게 의존 오브젝트의 핵심 기능 자체를 바꾸는 것이다. DI도 기본적으로는 런타임 시에 동적으로 의존 오브젝트를 연결해주는 것이긴 하지만, 일단 DI 되고 나면 그 후로는 바뀌지 않는다. 즉 동적인 방식으로 연결되지만 한 번 DI되면 바뀌지 않는 정적인 관계를 맺어주는 것이다. 하지만 DI를 잘 활용하면 애플리케이션이 동작하는 중간에 그 의존 대상을 다이내믹하게 변경할 수 있다.

  • 부가기능의 추가
    세 번째 활용 방법은 핵심기능은 그대로 둔 채로 부가기능을 추가하는 것이다. 데코레이터 패턴을 생각해보면 된다. 인터페이스를 두고 사용하게 하고, 실제 사용할 오브젝트는 외부에서 주입하는 DI를 적용해두면 데코레이터 패턴을 쉽게 적용할 수 있다. 그래서 핵심기능과 클라이언트 코드에는 전혀 영향을 주지 않으면서 부가적인 기능을 얼마든지 추가할 수 있다.

  • 인터페이스의 변경
    사용하려고 하는 오브젝트가 가진 인터페이스가 클라이언트와 호환되지 않는 경우가 있다. 이렇게 클라이언트가 사용하는 인터페이스와 실제 오브젝트 사이에 인터페이스가 일치하지 않는 경우에도 DI가 유용하다. 디자인 패턴에서 말하는 오브젝트 방식의 어댑터 패턴의 응용이라고 볼 수 있다. 이를 좀 더 일반화해서 아예 인터페이스가 다른 다양한 구현을 같은 방식으로 사용하도록, 중간에 인터페이스 어댑터 역할을 해주는 레이어를 하나 추가하는 방법도 있다. 서비스 추상화(PSA)가 그런 방법이다. PSA는 클라이언트가 일관성 있게 사용할 수 있는 인터페이스를 정의해주고 DI를 통해 어댑터 역할을 하는 오브젝트를 이용하게 해준다. 이를 통해 다른 인터페이스를 가진 로우레벨의 기술을 변경하거나 확장해가면서 사용할 수 있는 것이다.

  • 프록시
    필요한 시점에서 실제 사용할 오브젝트를 초기화하고 리소스를 준비하게 해주는 지연된 로딩(lazy loading)을 적용하려면 프록시가 필요하다. 원격 오브젝트를 호출할 때 마치 로컬에 존재하는 오브젝트처럼 사용할 수 있게 해주는 원격 프록시를 적용하려고 할 때도 프록시가 필요하다. 두 가지 방법 모두 DI를 필요로 한다.

  • 템플릿과 콜백
    템플릿/콜백 패턴은 DI의 특별한 적용 방법이다. 반복적으로 등장하지만 항상 고정적인 작업 흐름과 그 사이에서 자주 바뀌는 부분을 분리해서 템플릿과 콜백으로 만들 고 이를 DI 원리를 응용해 적용하면 지저분하게 매번 만들어야 하는 코드를 간결하게 만들 수 있다.

  • 싱글톤과 오브젝트 스코프
    DI가 필요한 중요한 이유 중 한 가지는 DI 할 오브젝트의 생명주기를 제어할 수 있다는 것이다. DI를 프레임워크로 이용한다는 건 DI 대상 오브젝트를 컨테이너가 관리한다는 의미다. 오브젝트의 생성부터 관계 설정, 이용, 소멸에 이르기까지의 모든 과정을 DI 컨테이너가 주관하기 때문에 그 오브젝트의 스코프를 자유롭게 제어할 수 있다. 스프링의 DI는 기본적으로 싱글톤으로 오브젝트를 만들어서 사용하게 한다. 컨테이너가 알아서 싱글톤으로 만들고 관리하기 때문에 클래스 자체는 싱글톤을 고려하지 않고 자유롭게 설계해도 된다는 장점이 있다.

  • 테스트
    다른 오브젝트와 협력해서 동작하는 오브젝트를 효과적으로 테스트하는 방법은 가능한 한 고립시키는 것이다. 즉 다른 오브젝트와의 사이에서 일어나는 일을 테스트를 위해 조작할 수 있도록 만든다. 그래야만 테스트 대상인 오브젝트의 기능에 충실하게 테스트가 가능하다. 복잡한 테스트할 대상에 의존하는 오브젝트를, 테스트를 목적으로 만들어진 목 오브젝트로 대체하면 유용하다.

4.2 애스펙트 지향 프로그래밍(AOP)

AOP도 스프링의 3개 기술중의 하나다. 사실 애스펙트 지향 프로그래밍은 객체지향 프로그래밍(OOP)처럼 독립적인 프로그래밍 패러다임이 아니다. AOP와 OOP는 서로 배타적이 아니라는 말이다.
객체지향 기술은 매우 성공적은 프로그래밍 방식임에 분명하다. 하지만 한편으로는 복잡해져 가는 애플리케이션의 요구조건과 기술적인 난해함을 모두 해결하는데 한계가 있기도 하다. AOP는 바로 이러한 객체지향 기술의 한계와 단점을 극복하도록 도와주는 보조적인 프로그래밍 기술이다

IOC/DI를 이용해서 POJO에 선언적인 엔터프라이즈 서비스를 제공할 수 있지만 일부 서비스는 순수한 객체지향 기법만으로는 POJO의 조건을 유지한 채로 적용하기 힘들다. 바로 이런 문제를 해결하기 위해 AOP가 필요하다.

AOP를 자바 언어에 적용하는 기법은 크게 두 가지로 분류할 수 있다.

  • 스프링과 같이 다이내믹 프록시를 사용하는 방법
    아래 포스팅에 자세히 설명해놨다.
    https://kimjinyounga.github.io/server/TobySpring_6(3)/
  • 자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법
    AspectJ라는 유명한 오픈소스 AOP 툴이 있다. AspectJ는 프록시 방식의 AOP에서는 불가능한 다양한 조인포인트를 제공한다. 메소드 호출뿐 아니라 인스턴스 생성, 필드 액세스, 특정 호출 경로를 가진 메소드 호출 등에도 부가기능을 제공할 수 있다.

4.3 포터블 서비스 추상화(PSA)

세 번째 기능기술은 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근 할 수 있게 해주는 PSA(Portable Service Abstraction)다.
POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않아야 한다. 스프링은 JavaEE를 기존 플랫폼으로 하는 자바 엔터프라이즈 개발에 주로 사용된다. 따라서 다양한 JavaEE 기술에 의존적일 수밖에 없다. 특정 환경과 기술에 종속적이지 않다는 게 그런 기술을 사용하지 않는다는 뜻은 아니다. 다만 POJO 코드가 그런 기술에 직접 노출되어 만들어지지 않는다는 말이다.
이를 위해 스프링이 제공하는 대표적인 기술이 바로 일관성 있는 서비스 추상화 기술이다.

댓글남기기