[토비의 스프링 3.1] 8장. 스프링이란 무엇인가

    Source : yes24

    1. 해당 포스팅은 책읽기 스터디의 활동을 통해 작성된 포스팅입니다.
    2. 공부하면서 블로그를 참고하였는데, 책 내용을 그대로 정리하는데 그치는 글들이 절반이었습니다. 스스로 새롭게 알게 된 내용이거나 책의 설명이 너무 불친절한 경우 부가설명을 작성하거나 또는 새롭게 쓰고자 노력했습니다. 공부하다 생기는 의문들은 레포지토리 이슈에서 질의응답을 주고 받았으니 학습하다 궁금한 점이 생기면 검색 해보시기를 권장드립니다.
    3. 틀린 내용이 있다면 댓글로 알려주시면 감사하겠습니다.
    4. 코드나 책 내용 캡쳐 내용들은 다른 블로그의 캡쳐본이나 텍스트를 가져와 작성하였습니다. 대부분 출처를 표기하였으나 누락된 경우 원하시는 조치 내용을 댓글로 남겨주시면 시정하겠습니다.

    스프링은 무엇일까? IoC와 DI 프레임워크로 제한하기엔 스프링이 너무 다재다능하다.

    스프링의 가장 잘 알려진 정의는

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

    자바 엔터프라이즈란 JVM에서 동작하는 기업 운영에 필요한 기술들의 집합체라고 한다. 자세한건 링크 참조.

    스프링의 특징들

    애플리케이션 프레임워크

    애플리케이션 프레임워크란 특정 계층이나 기술, 업무에 국한되지 않고 애플리케이션 전 영역을 포괄하는 프레임워크를 말한다. 일반적으로 라이브러리나 프레임워크는 특정 기술에 특화되어 만들어 진다. 예를 들면 웹을 MVC 구조로 쉽게 만들 수 있도록 지원한다던가, 간단하게 RDBMS와 자바 오브젝트를 매핑하고 바인딩하는 ORM 기술 등이 예시가 된다. 그에 비해 스프링은 코드를 객체지향적으로 아름답게 만들수 있도록 수많은 기능을 제공하며 애플리케이션 전체를 관통하는 일관된 프로그래밍 원칙을 통해 각 분야의 특성에 맞는 필요를 채워주고 있다.

    TMI : 책에서는 스프링의 기원을 로드 존슨(Rod Johnson)이 2003년에 저술한 Expert One-on-One J2EE Design and Development에 J2EE 애플리케이션 설계와 개발의 노하우를 다루면서 "항상 프레임워크 기반으로 접근하라"는 원칙을 증명하기 위해 제공한 3만 줄의 샘플 코드에서 시작되었다고 서술한다.

    즉, 스프링의 목적은 AOP, DI/IoC 등 제공하는 다양한 기술에 프로그래밍 모델을 일관적으로 적용하여 각기 다른 자바 엔터프라이즈를 다룰 때 단일한 전략과 규칙으로도 편리하게 개발할 수 있도록 돕는 것이다.

    경량급

    스프링은 불필요하게 무겁지 않다. 과거 EJB는 자바의 주류 기술이었는데, 과도한 엔지니어링적 욕심 때문에 개발환경, 운용서버, 빌드, 테스트, 코드 작성 모두를 무겁게 만들었다. 그리고 고가의 느리고 무거운 WAS가 필요했다. 배포도 힘들었기 때문에 고가의 제품들이 필요했다.

    그에 반해 스프링은 톰캣이나 제티같은 가벼운 WAS에서도 동작한다. 서블릿 컨테이너만으로도 충분히 동작하기 때문에 개발 과정도 단순해진다. 이런 측면에서 '경량급'이라 부를 수 있다.

    코드 측면에서도 작고 단순해졌다. 같은 기능을 수행하는 코드지만 EJB에선 프레임워크와 서버환경에 의존적인 부분을 반복적으로 작업해야했던 것에 비해서, 스프링은 그런 부분이 사라져 단순하고 가벼운 코드로 개발을 할 수 있게 되었다.

    경량급이란 단어는 기능이 부족함을 이르는게 아니라, 같은 기술을 빠르고 생산성있게 개발해줄 수 있다는 의미이다.

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

    수많은 자바 엔터프라이즈 기술과 프레임워크는 '개발을 편하게 해준다'는 모토를 들고왔다. 책에서 신랄하게 까는 EJB의 경우에도 비슷한 모토를 들고왔고 실제로 어느정도 고민을 덜어주었지만, 이 목표를 달성하는 과정에서 더 복잡함을 증가시켰던 실책이 있다고 한다.

    편리한 애플리케이션의 개발이란 것은, 로우레벨 기술에 많은 신경을 쓰지 않고도 핵심 비즈니스 로직을 빠르게 개발하는 것을 말한다고 한다. 스프링은 초기 기술 설정을 잘하고 나면 추후 스프링과 관련된 코드에 크게 신경쓸 필요가 없다.

    오픈소스

    스프링은 오픈소스다. 공짜로 공개되고, 특별한 라이선스 없이 사용할 수 있으며 소스를 자유롭게 열람할 수 있고 필요하면 수정할 수 있으며 스프링으로 개발한 제품과 소스를 공개적으로 배포할 자유도 있다. 스프링은 아파치 라이선스 버젼 2.0을 적용하고 있다.

    오픈소스의 특징으로, 스프링 또한 커뮤니티가 있으며 버그를 신고하거나 새 기능추가 요청을 자유롭게 할 수 있다.

    스프링은 오픈소스고 개발 과정에 자유롭게 의견을 개진할 수 있지만 실제 개발은 VMWare의 SpringSource 사업부의 소수 개발자가 진행한다. 오픈소스의 단점인 '개발자의 여유와 은혜에 크게 의존하는' 불안정성, 즉 언제 오픈소스 프로젝트가 문을 닫을 지 모르는 불확실성을 전문 기업을 설립하여 해소하였다. 이 회사는 Spring의 기술지원을 통해 수익을 창출하여 이 자금으로 스프링 개발에 투자하고 있다.

    스프링의 목적

    스프링의 기술 사용법을 익혀선 스프링의 장점을 온전히 활용할 수 없다.

    원래 엔터프라이즈 개발은 편하지 않은게 맞다고 한다. 2000년대 초반 대부분의 자바 엔터프라이즈 프로젝트는 시간과 비용의 제약을 준수하지 못하거나 엎어지는 일이 많았다고 한다. 가장 대표적인 원인은 '자바 엔터프라이즈 시스템 개발이 너무 복잡해서'라고 한다.

    왜 자바 엔터프라이즈 시스템 개발은 복잡할까?

    • 서버에서 동작하며 다수의 요청을 안정적으로 처리하고 효울적인 자원분배와 이용, 기업환경에서 필수적인 보안성과 확장성까지 기술적인 요구가 늘어나면서 복잡해진다.
    • 기업 업무는 원래 복잡하기 때문에 핵심 비즈니스 로직의 구현도 복잡해졌다.
    • 세상이 복잡하고 빠르게 흘러가면서 업무/비즈니스 프로세스의 변경 요구가 증가했다.

    복잡함의 원인

    기존 엔터프라이즈 프레임워크들이 실패했던 이유는, 비즈니스 로직과 엔터프라이즈 기술이라는 두 가지 복잡함이 얽혀있기 때문이다. 비즈니스 로직을 구현할 때 기술적인 문제까지 고려해야 하는 일이 많아졌다.

    복잡함을 해결하려는 도전

    근본적인 복잡함 자체를 없앨 수는 없다. 쓰기 편하라고 보안을 소홀히 한다던가 확장성이 떨어지는 시스템을 만들 수는 없기 때문이다. 대신 그 복잡함을 어떻게 효율적으로 다루느냐가 목표가 된다. 비즈니스 로직과 기술적인 문제를 분리해내는 것이 가장 먼저 스프링이 해결해야 할 일이었다.

    EJB는 왜 실패했는가?

    EJB는 몇몇 기술적인 특징으로 두 가지 종류의 복잡함을 제거할 수 있었지만, EJB의 틀 안에서 코드를 작성하도록 강제했기 때문에 EJB의 환경과 스펙에 종속적으로 코드를 작성해야만 했다. 이로 인해 자바의 강점마저 잃어버렸다.

    반면교사 : 스프링의 해결법

    스프링은 EJB의 전철을 밟지 않았다. EJB의 규약이 코드에 묻어나는 침투적인 기술의 단점을 해소하고자 했다.

    침투적인(invasive) : 특정 프레임워크의 규약 등이 코드에 묻어나는 것. 코드가 프레임워크에 종속적일 때.

    그에 비해 스프링은 non-invasive, 즉 비침투적인 방법을 취해 스프링 스스로가 애플리케이션의 비즈니스 로직을 담당하는 코드에 불필요하게 등장하지 않도록 했다. 성격이 다른 복잡함을 깔끔하게 분리할 수 있었다.

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

    기술에 대한 일관적인 접근방법

    기술의 변화에 효과적으로 대응하기 위해 스프링은 서비스 추상화를 택했다. 기술적인 복잡함은 추상화를 통해 로우레벨의 복잡한 구현을 인터페이스 아래 숨기고, 환경과 세부 기술에 독립적인 인터페이스에 의존하도록 했다.

    자바메일과 같이 잘못 설계되었지만 표준으로 널리 쓰이는 기술도 추상화 적용을 통해 테스트를 용이하게 만들었으며, 기술의 세부 설정과 환경으로부터 독립적인 코드를 작성할 수 있다.

    데이터 엑세스 예외의 추상화는 특정 기술의 예외에 종속되지 않도록 한다.

    템플릿/콜백 패턴으로 반복적인 작업 흐름과 API 사용을 반복하지 않도록 만들었다.

    기술과 관련된 코드와 비즈니스 로직의 핵심 코드와 분리하기

    비즈니스 로직 전후로 트랜잭션 경계를 설정하는 것을 비즈니스 로직과 분리하는 것을 책에서 다루었다. 책임에 따라 계층을 구분하더라도 근본적으로 책임이 다른 코드가 섞이는 것을 방지하기 어려운데, AOP로 이 문제를 해결했다.

    비즈니스/애플리케이션 로직의 복잡함 상대하기

    비침투적인 프레임워크를 만드려는 스프링의 노력 덕에 비즈니스 로직과 기술적인 코드가 분리될 수 있었다. 기술적인 부분에 문제가 발생했다면 서버 리셋이나 서버 증설등의 문제로 해결할 수 있지만, 비즈니스 로직에 문제가 있다면 계좌의 돈이 이유없이 줄어들거나 주식 주문이 체결로 이어지지 않는 불상사가 발생할 수도 있다.

    과거에는 DB에 비즈니스 로직을 포함시키는게 유행이었다고 하는데, 복잡성을 컨트롤할 수 없어 위험한 일로 여겨졌다고 한다.

    그래서 엔터프라이즈 시스템은 점점 비즈니스 로직은 애플리케이션 내에서 처리하도록 하는 것이 트렌드라고 한다. DB는 단순 데이터의 영구적 저장과 복잡한 조건의 검색 등 DB만이 할 수 있는 일들에 집중하고, 그 데이터를 분석/가공 및 관련 로직을 처리하는 것은 애플리케이션 서버에서 처리하도록 했다.

    더 알아볼 키워드 : OOAD, CBD, 모델링을 중심으로 한 개발방법론

    스프링의 핵심 도구 : 객체지향과 DI

    기존의 자바 엔터프라이즈 기술이 객체지향이 장점인 자바의 특징을 활용하지 못하도록 하는 것의 반작용으로 등장했다. 객체지향의 설계 기법을 적용할 수 있고, 단순한 오브젝트로 개발할 수 있도록 하는 것이 스프링의 기본 전략이다.

    스프링의 AOP, 템플릿/콜백, 서비스 추상화 등은 DI에 기반을 두고 있으며, DI도 객체지향 설계위에 존재한다. DI를 사용하다보면 자연스럽게 객체지향적인 생각에 도달하게 해준다. 코드에서 어떤 것이 자주 바뀔 수 있을까? 성격이 다르고 변경의 이유가 다른건 무엇일까?를 찾게 되면 이러한 지점에서 오브젝트를 분리하고 인터페이스에 의존하며 DI로 주입받게 될 것이다.

    다른 관점에서 보면 비즈니스 로직에서 기술적인 복잡함을 분리할 떄는 DI가 바탕이 된 기술들이 적용되지만, 비즈니스 로직 자체에는 객체지향의 분석과 설계에서 나온 도메인 모델을 쉽게 적용할 수 있다. 상속과 다형성, 위임 등 여러 디자인 패턴과 설계 기법을 잘 녹일 수 있다. 이를 위해 스프링에서 이 둘을 분리하고자 했던 것이다.

    결국 스프링은 자바의 객체지향적 특성을 최대한 활용하기 위한 기술이다.

    POJO 프로그래밍

    스프링의 핵심 개발자가 쓴 Professional Spring Framework란 책에서 스프링의 정수는 엔터프라이즈 서비스 기능을 POJO에 적용하는 것이라고 정의했다. 다르게 생각하면 POJO란 말 자체가 이미 기술과 관련된 코드를 애플리케이션 로직에서 분리했다는 말이기도 하다.

    스프링의 CTO인 아드리언 콜리어(Adrian Colyer)는 스프링의 핵심 개념을 설명하기 위해 스프링의 삼각형을 만들었다.

     

    Simple Object, 즉 POJO에 DI/AOP/PSA 등을 이용해 외부의 설계정보와 관계를 맺도록 해준다.

    그래서 POJO는 무엇일까?

    Plain Old Java Object의 첫 글자를 따서 만들었다. 마틴 파울러(Martin Fowler)가 컨퍼런스 발표를 준비하다가 단순한 자바 오브젝트를 이용해 비즈니스 로직을 구현하면 EJB보다 나을거란 생각을 했는데, 당시 개발자들이 이를 꺼려했다. 이러한 방법론에 EJB와 같은 이쁜 이름이 없었기 때문에 세련된 이름을 지어서 사용자들이 '단순한 자바 오브젝트를 사용하는데요' 대신 'POJO' 방식의 기술을 사용합니다' 로 말하도록 하여 사람들에게 기술뽕을 채워주는 것이 의도였다고 한다.

    POJO로 불리기 위해선 아래 3가지 조건을 지켜야 한다.

    특정 규약(contract)에 종속되지 않는다.

    POJO는 자바 언어와 꼭 필요한 API 외에는 종속되지 않아야 한다. 특정 규약에 종속되는 것의 예로는 서블릿에서 HttpServlet 클래스를 상속하여야 서블릿 클래스를 작성할 수 있는 제약조건을 들 수 있다. 자바의 클래스는 단일 상속만을 지원하기에 더 이상 객체지향적인 설계기법을 적용하기 어려워지는 문제가 생길 수 있다.

    특정 환경에 종속되지 않는다.

    문자 그대로 코드가 특정 환경이나 프레임워크에 종속되지 않아야 한다. 비즈니스 로직을 담는 POJO는 웹 기술을 담고 있는 클래스나 인터페이스를 사용하면 종속된다고 말 할수 있다.

    소스코드에 메타정보를 담는 애너테이션을 사용하면 POJO가 아닐까? 코드에 직접 영향을 미치지 않는 부가적인 정보를 담고 있지 않다면 문제없지만, 애너테이션에 특정 기술과 환경에 종속이 되는 정보가 있다면 POJO라고 부를 수 없다.

    단일 책임 원칙을 지키는 클래스

    책임과 역할이 다른 코드를 한 클래스에 우겨넣어 재사용이 힘들 정도로 결합이 강하게 이루어 진다면, 상속과 다형성과 같은 객체지향적 해결방법이 존재하지만 분기문 떡칠로 해결한다면 POJO라고 부를 수 있을까?

    위 원칙을 지키며 POJO에 핵심 로직을 담고 설계하는 방법을 POJO 프로그래밍이라 부를 수 있다.

    POJO의 장점은 무엇일까?

    • 특정한 기술과 환경에 종속되는 코드가 사라져 깔끔하게 작성할 수 있고 디버깅과 테스트 자동화가 용이하다.
    • OOP, 도메인 모델, 디자인 패턴 등 객체지향적인 설계를 적용할 수 있다.

    스프링의 기술

    기술과 비즈니스 로직의 분리는 스프링과 같은 POJO 프레임워크로 가능하다. 스프링에는 POJO 프로그래밍을 쉽게 할 수 있는 3가지 Enabling Technology를 제공하는데, 위의 스프링의 삼각형에서 소개된 IoC/DI, AOP, PSA이다. 스프링은 이 세 가지 기술을 자바 엔터프라이즈 개발의 전 영역에 자연스럽게 녹아들 수 있도록 프레임워크 형태로 제공된다.

    이 세 가지 Enabling Technology가 목적이 아니라, POJO 기반의 개발을 편하게 해주는 도구라고 생각하면 좋다.

    IoC/DI

    왜 두 개의 오브젝트를 분리해서 만들고, 인터페이스로 느슨하게 연결하여 프레임워크로 하여금 DI하여 사용하도록 만들까? '유연한 확장이 가능하게 하기 위해서' 라고 할 수 있다. SOLID 원칙 중 OCP는 확장에는 열려있지만(주입하는 빈은 구현이 바뀌거나 다른 클래스가 될 수 있음) 변경에는 닫혀있다(주입받는 클래스는 빈이 어떻게 바뀌든 변경의 이유가 생기지 않음)으로 설명할 수 있다.

    주로 아래와 같이 활용가능하다.

    • 핵심 기능의 변경
      • 전략 패턴과 같이 의존하는 대상의 구현 클래스를 DI 설정의 변경을 통해 쉽게 변경할 수 있다.
    • 핵심 기능의 동적인 변경
      • DI는 런타임 시 한 번 의존 오브젝트를 연결하면 바뀌지 않지만 동적으로 의존 오브젝트를 바꿀 수 있다.
      • 다이나믹 라우팅 프록시나 프록시 오브젝트 기법을 활용하여 적용할 수 있다.
    • 부가기능의 추가
      • 데코레이터 패턴과 같이 인터페이스에 의존하고 사용할 구체 클래스 인스턴스를 DI 받도록 만들면 핵심 기능을 구현한 코드에 영향을 주지 않고 부가기능을 쉽게 부여할 수 있다.
    • 인터페이스의 변경
      • 어댑터 패턴을 적용할 수 있다. A가 C에 의존하는데, A는 B 인터페이스를 사용하도록 구현하였고 C는 B를 구현하지 않았을 때, B를 구현한 클래스를 만들어 내부에서 C를 호출하도록 하고 이 클래스를 DI하는 방식으로 적용할 수 있다.
    • 프록시
      • Lazy Loading이나 원격 오브젝트를 로컬의 오브젝트처럼 사용할 수 있도록 하는 원격 프록시를 적용하려고 할 때 프록시가 필요하며, DI로 이를 구현할 수 있다./
    • 템플릿과 콜백
      • 반복되는 로직과 변하는 로직을 템플릿과 콜백으로 분리하면 지저분한 반복을 줄일 수 있다.
    • 싱글톤과 오브젝트 스코프
      • 컨테이너가 DI할 오브젝트의 생성, 이용, 소멸, 관계설정, 생명주기를 관리할 수 있다. 그 중 생명주기는 싱글톤, HTTP Request당 하나의 오브젝트, HTTP Session당 하나의 오브젝트 등 다양한 스코프로 제어가 가능하다.
    • 테스트
      • 다른 오브젝트와 밀접하게 협력하거나 연관된 오브젝트를 테스트하는 좋은 방법은 고립시키는 것이다. 이 고립된 테스트를 위해 스텁이나 목 오브젝트 같은 테스트 대역을 주입하여 실제 환경이나 또는 오래 걸리는 작업을 배제한 채 테스트에 집중할 수 있다.

    AOP

    객체지향 프로그래밍 패러다임이 현대의 복잡한 요구항과 기술적인 난해함을 해결하는데 한계가 있는데 이를 보조해주는 것이 AOP이다. AOP와 OOP는 대척점에 있는게 아니라, AOP가 OOP를 보조하여 더욱 OOP에 맞는 코드를 작성할 수 있도록 돕는다.

    AOP를 적용하는 방법은 크게 두 가지로 분류할 수 있다.

    • 스프링과 같이 다이나믹 프록시를 사용하는 방법
      • 데코레이터 패턴을 응용하여 부가기능을 적용하도록 다이나믹 프록시를 이용할 수 있다. 스프링에서 구현할 수 있지만 부가기능을 부여할 수 있는 곳이 오로지 메서드 호출지점이라는 단점이 존재한다.
    • 자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법
      • 다양한 조인포인트를 이용하기 위해선 AspectJ라는 유명한 AOP 오픈소스 라이브러리를 사용할 수도 있다. 메서드 호출, 인스턴스 생성, 필드 엑세스 등에도 부가 기능을 적용할 수 있다. 직접 바이트코드를 수정하기 때문에 AOP 변경사항이 존재할 때 마다 새로 컴파일을 해주어야 한다는 단점이 있다.

    AOP를 적용하는 3단계

    1. 스프링이 제공하는 AOP 이용
      • 스프링이 대표적으로 제공하는 AOP는 트랜잭션이다. 특정 아키텍쳐에서 @Configurable 애너테이션을 이용한 도메인 오브젝트에 DI를 자동적용 해주는 AOP도 스프링에서 제공하지만 AspectJ를 필요로 한다.
    2. 전담팀을 이용한 정책 AOP 적용
      • 처음엔 보안, 로깅, 트레이싱, 성능 모니터링과 같은 정책적인 기능을 전담하는 소규모 팀에서 AOP를 전담하도록 한다.
    3. AOP의 자유로운 이용
      • 개발자가 AOP에 익숙해졌다면 개발자가 구현하는 기능에 적용할 AOP를 이용하도록 한다. 작은 단위에서도 AOP로 분리하면 좋을 기능들이 존재하기 마련이다.

    Portable Service Abstraction

    PSA는 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있도록 한다. POJO 코드가 기술에 직접적으로 노출되어 만들어져서는 안된다는 의미이다. 스프링이 정의한 트랜잭션 추상화 인터페이스인 PlatformTransactionManager를 통해 트랜잭션 기술을 이용한다면, 추후DB가 바뀌거나 하는 문제들에도 코드는 변하지 않고 제 역할을 해낼 것이다.

    댓글