8. What is Spring?

8장 스프링은 무엇인가?

8장은 DI, AOP, MVC 같은 얘기보다는, 스프링이 어떤 관점으로 앤터프라이즈 개발을 정리했는가에 초점이 맞춰져있습니다. 책에서는 복잡한WAS, XML 중심 설정을 유연하게 풀었습니다. 다만, 지금은 분산 시스템, 외부 설정, 가시성, 메시징, 배포, 운영 자동화 등이 새로운 복잡한 문제로 남아있습니다.

8.1. 스프링의 정의

스프링은 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크입니다. 요즘 스프링은 Spring Framework 라기보다는 Spring Boot를 포함한 생태계 전체를 포함합니다.

스프링은 특정 기능 하나를 담당하는 프레임워크라기 보다는, (웹, 데이터 접근, 객체 생성 등) 애플리케이션 전반을 대상으로 기술적인 요구사항이 핵심 비즈니스 로직을 침범하지 않도록 구조를 잡아주는 프레임워크입니다. 웹, 서비스, 데이터 접근, 트랜잭션, 테스트, 설정, 객체 관계 구성 등 애플리케이션이 돌아가는 전체 흐름에 관여합니다.

경량급

스프링 프레임워크가 경량급이라고 하는 이유는, 개발자가 작성하는 코드와 개발 과정이 불필요하게 무거워지지 않도록 해줍니다. 개발하는데에 있어서 불필요한곳에 신경을 쓰지 않기 위함입니다.

과거에는 자바 개발을 하려면 무거운 서버, 컨테이너 종속적인 컴포넌트 모델, 복잡한 배포 구조, 다양한 설정들이 필수였습니다. 개발자는 비즈니스 로직보다 기술 환경이 요구하는 형식에 코드를 맞추는데 시간을 많이 들였습니다. 스프링은 복잡한 진입장벽 없이, 일반적인 자바 객체 기반으로 애플리케이션을 만들 수 있게 해줬습니다.

지금은 쉬운 실행/배포, 간단한 시작 정도로 이해할 수 있을 것 같습니다. 최소한의 설정으로 시작하고, 외부 설정과 자동 설정을 활용해 애플리케이션을 빠르게 실행 가능한 형태로 제공하여, 개발자가 비즈니스 코드에만 집중할 수 있도록 최대한 도와주고 있습니다.

개발을 편하게 해준다

스프링은 자바 애플리케이션에서 비즈니스 로직 중심의 프로그래밍 모델을 제공합니다. 기술을 쓰더라도 핵심 로직이 기술 흐름에 종속적이지 않게 만드는 프레임워크입니다.

오픈 소스

과거에는특정 기업에 종속적이지 않고, 개발자 커뮤니티를 기반으로 성장했었다면, 지금은 공개된 문서, 생태계, 학습자료, 확장 프로젝트, 실전 적용사례까지 축적되어있습니다.

즉, 오픈소스라서 쓸 수 있다 보다는 오픈소스 생태계 전체가 이미 거대한 학습/실무 기반이 되어있다는데 초점이 있습니다.

8.2. 스프링의 목적

스프링은 개발을 편하게 하는 것입니다.

그럼 어떤게 개발을 어렵게 만들었냐?

  • 트랜잭션

  • 보안

  • 데이터 접근

  • 리소스 관리 등

비즈니스 로직에 여러 코드들이 뒤섞여 있었습니다.

스프링은 복잡함을 제거하는 것이 아니라, 이러한 것들을 분리하고, 각자 맞는 방식으로 다루게 만드는게 목적입니다.

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

요즘 애플리케이션은단순히 CRUD뿐만 아니라, 대량의 유저 관리, 동시 처리, 데이터 정합성, 외부 시스템 연동, 장애 상황 고려, 모니터링 등 모두 갖춰야합니다. 또한, 정책, 예외 케이스 등 업무 규칙까지 추가되면서 두 가지 복잡함이 존재하게 됩니다.

기술적 복잡함은 인프라와 실행 환경 떄문에, 비즈니스 복잡함은 도메인 규칙과 업무 흐름 때문에 생기게 됩니다.

성격이 다른 문제들이 하나로 묶여있있다는 것에 대해 개발이 복잡하다고 합니다. 이런 복잡함을 과거에는 분리했지만, 요즘에는 분산 환경, 운영 복잡성이 추가가 됐습니다.

복잡함의 종류는 다르지만, 결국 기술적 복잡함이 비즈니스 로직 안으로 들어오면 유지보수가 어렵게 된다는 것이 중요합니다.

8.2.2. 복잡함을 해결하려는 도전

먼저 기술적 복잡함에 대해서는 서비스 추상화, 템플릿/콜백, 일관된 프로그래밍 모델이 사용됩니다. 기술마다 API가 다르고 예외 구조가 다르고 실행 환경이 다르면 애플리케이션 코드는 복잡할 수밖에 없습니다. 스프링은 기술의 세부 구현보다 더 안정적인 추상화 계층을 제공해, 애플리케이션 코드가 특정 기술 세부사항에 덜 종속적으로 만듭니다. 또한 반복적이고 기술 중심적인 보일러플레이트를 줄여 개발자가 핵심 로직만 더 선명하게 남기도록 돕습니다.

다음으로 공통 관심사가 핵심 로직을 침범하는 문제에 대해서는 AOP라는 전략이 등장합니다. 트랜잭션, 보안, 로깅, 모니터링처럼 여러 곳에 반복되지만 비즈니스 로직의 본질은 아닌 기능을 메서드 안에 직접 포함하지 않도록 분리하는 것입니다. 여기서 중요한 것은 프록시를 쓴다는 기술적 사실보다, 기술적 부가 기능을 핵심 코드 바깥으로 빼낸다는 설계 의도입니다.

마지막으로 비즈니스 로직의 복잡함에 대해서는 결국 객체지향 설계와 DI가 핵심 도구가 됩니다. 기술적인 코드를 바깥으로 밀어낸 뒤에도 도메인 규칙 자체는 여전히 복잡합니다. 그 복잡함은 좋은 객체 설계, 역할 분리, 협력 구조, 느슨한 결합, 테스트 가능한 구조로 다뤄야 합니다. DI는 단지 의존성을 주입하는 편의 기능이 아니라, 좋은 객체 설계를 실제 애플리케이션 구조로 유지하게 해주는 수단입니다.

과거에는 XML 설정과 직접적인 빈 조립이 더 많이 보였다면, 지금은 애노테이션, 자바 기반 설정, 자동 설정, 외부 설정이 기본 흐름이 되었습니다. 또한 현대 스프링은 여기서 한 걸음 더 나아가 운영 복잡성까지 프레임워크 차원에서 다룹니다. 즉, 오늘날 스프링 전략은 코드 안의 복잡함 분리에 머물지 않고, 애플리케이션을 둘러싼 운영 복잡성까지 구조적으로 밀어내는 것으로 확장되었습니다.

재해석

스프링은 엔터프라이즈 개발에서 피할 수 없는 복잡함을 없애는 프레임워크가 아니라, 그 복잡함이 핵심 비즈니스 로직을 직접 침범하지 못하게 막는 프레임워크입니다. 과거에는 그 대상이 EJB와 무거운 엔터프라이즈 자바 기술이었고, 지금은 분산 시스템과 운영 환경까지 포함한 더 넓은 복잡성이 그 대상입니다.

하지만 스프링의 중심 철학은 바뀌지 않았습니다. 기술은 필요하지만, 기술 때문에 핵심 로직이 기술 그 자체처럼 보이면 안 됩니다. 스프링이 지금까지도 유효한 이유는 바로 이 원칙이 여전히 현재 문제에도 잘 맞기 때문입니다.

스프링을 단순히 DI, AOP, MVC의 묶음으로 이해하면 이후의 기술도 각각 따로 배운 기능처럼 보이지만, 이후에 등장하는 거의 모든 스프링 기술은 같은 목적을 향하고 있습니다.

비즈니스 로직을 중심에 두고, 기술적 복잡함은 추상화하고, 공통 관심사는 분리하고, 좋은 객체 설계를 유지하게 돕는 것.

8.3. POJO 프로그래밍

스프링의 목적은 IoC/DI나 AOP 같은 기능을 제공하는 것 자체가 아니라, 엔터프라이즈 서비스를 적용하면서도 애플리케이션 핵심 로직은 가능한 한 평범한 자바 오브젝트로 유지되게 만드는 데 있습니다.

POJO는 단순히 new로 만들 수 있는 자바 클래스라는 뜻이 아니라, 특정 기술 규약, 특정 실행 환경, 특정 프레임워크 API에 의해 객체의 본질이 결정되지 않는 상태를 뜻합니다. 스프링은 이런 POJO를 중심에 놓고, 필요한 엔터프라이즈 기술은 바깥에서 비침투적으로 적용하려고 합니다.

8.3.1. 스프링의 핵심: POJO

스프링 삼각형이란, 가운데에는 POJO가 있고, 그 주변을 IoC/DI, AOP, PSA가 받치고 있습니다. 여기에 설계 정보가 더해져 하나의 애플리케이션 구조가 만들어집니다.

이 그림은 스프링 애플리케이션의 중심은 기술 프레임워크가 아니라 POJO이고, 스프링이 제공하는 기술은 그 POJO를 더 유연하고 확장 가능하게 만드는 보조 기술이라는 점입니다.

스프링을 기술 모음집으로 보면 핵심을 놓친다는 사실입니다. IoC/DI, AOP, PSA 각각은 중요하지만, 그 자체가 스프링의 목적은 아닙니다. 그 세 가지는 결국 애플리케이션을 POJO 중심으로 개발할 수 있도록 도와주는 enabling technology입니다. 즉, POJO가 목적이고, 나머지는 수단입니다.

요즘에는 POJO 중심 개발보다 자동 설정 기반 개발이 먼저 체감되기도 합니다. 하지만 그 안을 들여다보면 핵심은 여전히 같습니다. Boot가 편리한 이유도 결국 POJO로 작성된 애플리케이션 코드 위에 설정, 자동 조립, 운영 기능을 얹기 때문입니다. 즉, 오늘날에도 스프링의 중심은 여전히 POJO이고, Boot는 그 철학을 더 빠르게 실현하게 해주는 시작점이라고 볼 수 있습니다.

8.3.2. POJO란 무엇인가?

POJO는 Plain Old Java Object의 약자입니다. 이름만 보면 아주 평범한 자바 객체라는 뜻인데, 책이 이 용어에 주목하는 이유는 단순히 평범한 객체를 예쁘게 부르기 위해서가 아닙니다. 당시에는 EJB 같은 기술이 너무 무겁고 복잡했기 때문에, 오히려 평범한 자바 객체로 비즈니스 로직을 풀자는 발상 자체가 하나의 중요한 대안처럼 등장했습니다. 그래서 POJO라는 말은 단순한 명칭이 아니라, 객체를 다시 객체답게 돌려놓겠다는 문제의식이 담긴 표현이었습니다.

POJO는 평범하다는 말보다 객체로서의 본질을 잃지 않았다는 말에 더 가깝습니다. 기술을 사용하지 않는 객체가 아니라, 기술 때문에 객체의 설계가 망가지지 않은 객체가 POJO입니다. "핵심 로직이 프레임워크 API 호출 흐름에 종속되지 않아야 한다는 원칙"

8.3.3. POJO의 조건

특정 규약에 종속되지 않는다

어떤 객체가 특정 기술의 규약을 만족하기 위해 반드시 정해진 클래스를 상속해야 하거나, 특정 인터페이스를 구현해야 하거나, 정해진 형태로만 작성되어야 한다면 그 객체는 이미 POJO로서의 자유를 잃은 것입니다. 객체의 구조가 업무 모델링보다 프레임워크 규약 때문에 먼저 결정된다면, 그 객체는 더 이상 평범한 자바 객체라고 보기 어렵습니다.

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

POJO는 특정 서버, 특정 컨테이너, 특정 런타임 환경이 있어야만 정상적으로 동작하는 구조가 되어서는 안 됩니다. 환경이 바뀌어도 객체의 본질적인 역할과 로직이 유지되어야 합니다. 만약 어떤 객체가 JNDI 같은 특정 환경의 검색 방식, 특정 WAS API, 특정 서버 전용 기능에 의존한다면, 그 객체는 환경 독립성을 잃게 됩니다.

이 조건이 중요한 이유는 결국 테스트와 이식성 때문입니다. 특정 환경이 없으면 객체를 실행할 수 없고 검증할 수도 없다면, 개발과 유지보수 비용이 급격히 올라갑니다.

객체지향 설계 원리에 충실해야 한다

책에서 가장 중요한 조건은 사실 세 번째입니다. 단순히 특정 API를 안 쓴다고 해서 자동으로 POJO가 되는 것은 아닙니다. 자바 문법만 사용했다고 해서 객체지향 설계가 보장되는 것도 아닙니다. 객체가 책임을 나누지 못하고, 계층마다 해야 할 일이 뒤섞여 있고, 거대한 서비스 클래스 하나에 모든 규칙과 흐름이 몰려 있다면, 그 코드는 기술 종속이 없더라도 좋은 POJO 설계라고 보기 어렵습니다.

즉, POJO는 프레임워크 흔적이 없다는 소극적 조건만으로 완성되지 않습니다. 역할과 책임이 적절히 나뉘고, 협력이 자연스럽고, 변경에 유연한 구조를 가져야 비로소 POJO 프로그래밍의 가치가 살아납니다.

8.3.4. POJO의 장점

특정 기술에 묶이지 않는 객체는 코드가 더 깔끔해지고, 비즈니스 로직이 더 잘 드러나며, 재사용과 테스트가 쉬워집니다. 특히 자동화된 테스트에 유리하다는 점은 POJO의 큰 장점입니다. 복잡한 컨테이너를 띄우지 않고도 핵심 로직을 검증할 수 있기 때문입니다.

또 하나 중요한 장점은 객체지향 설계를 자유롭게 적용할 수 있다는 점입니다. 상속, 위임, 다형성, 패턴, 도메인 모델링 같은 설계 기법은 객체가 기술 규약에 먼저 묶이지 않을 때 가장 잘 작동합니다. 결국 POJO의 장점은 단순히 코드가 예뻐진다는 수준이 아니라, 객체지향 언어를 객체지향답게 쓸 수 있게 된다는 데 있습니다.

8.3.5. POJO 프레임워크

스프링은 POJO만 사용하라고 말하는 프레임워크가 아니라, 엔터프라이즈 개발에서 필요한 기술을 POJO 위에 비침투적으로 얹어줄 수 있는 프레임워크입니다. 즉, POJO를 목표로 삼되, 그 POJO가 현실의 엔터프라이즈 요구사항을 감당할 수 있도록 기술적인 다리 역할을 해주는 것이 스프링입니다.

여기서 스프링의 역할은 이중적입니다. 한쪽에서는 트랜잭션, 데이터 접근, 보안, 메시징 같은 엔터프라이즈 기술을 다뤄야 합니다. 다른 한쪽에서는 핵심 비즈니스 로직이 그런 기술 세부사항으로 오염되지 않도록 지켜야 합니다. 스프링이 POJO 프레임워크라는 말은 바로 이 두 요구를 동시에 만족시키려는 프레임워크라는 뜻입니다.

지금은 이 개념을 POJO 프레임워크라는 말 그대로보다, 핵심 로직은 순수하게 두고 인프라는 어댑터와 추상화로 감싼다는 현대적 아키텍처 감각으로 읽는 편이 자연스럽습니다. 헥사고날 아키텍처, 클린 아키텍처, 도메인 중심 설계 같은 흐름과도 잘 맞닿아 있습니다. 오늘날 스프링은 Boot, 자동 설정, 외부 설정, 관측성, AOT 같은 기능까지 넓어졌지만, 여전히 핵심 비즈니스 로직을 프레임워크 안쪽 깊숙이 가두기보다는 바깥 기술과 분리하려는 철학을 유지하고 있습니다. 즉, 이름은 예전 표현처럼 들릴 수 있어도, POJO 프레임워크라는 개념 자체는 지금도 충분히 현대적입니다.

8.4. 스프링의 기술

스프링의 대표 기술을 세 가지로 정리합니다. IoC/DI, AOP, PSA입니다. 중요한 점은 이 세 가지를 각각 독립된 기술 주제로 외우는 것이 아니라, POJO 중심 개발을 가능하게 만드는 도구로 함께 이해해야 한다는 것입니다.

IoC/DI는 객체 관계를 외부에서 조립하게 하여 결합도를 낮추고, AOP는 기술적 부가기능을 핵심 로직 밖으로 밀어내며, PSA는 서로 다른 기술에 대한 접근 방식을 일관된 추상화로 감쌉니다. 즉, 세 기술은 모두 방향이 같습니다. 핵심 비즈니스 로직이 기술 세부 구현에 끌려다니지 않게 만드는 것입니다.

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

가장 대표적인 DI 활용은 구현 교체입니다. 어떤 오브젝트가 의존하는 구현체를 바꾸더라도 클라이언트 코드는 변하지 않게 만드는 것입니다. 전략 패턴이나 OCP와 연결되는 부분입니다. 책이 말하는 DI의 첫 번째 장점은 결국 변경 지점을 객체 내부가 아니라 외부 조립으로 밀어낸다는 점입니다.

예전에는 JDBC 구현을 다른 구현으로 바꾸는 식의 예시가 많았다면, 지금은 외부 API 클라이언트, 메시지 발행기, 캐시 전략, 정책 오브젝트, 저장소 구현, 멀티 리전 설정 같은 더 넓은 문제에 적용됩니다. 또한 현재 공식 문서는 생성자 주입과 세터 주입을 모두 지원하지만, 필수 의존성에는 생성자 주입을 우선 권장합니다. 스프링 팀도 필수 의존성에는 생성자, 선택 의존성에는 세터나 설정 메서드를 쓰는 방식을 일반적인 원칙으로 안내합니다. 오늘날 실무에서 DI를 이해한다는 것은 단순히 @Autowired를 쓴다는 뜻이 아니라, 어떤 변경이 객체 내부가 아니라 외부 조립에서 해결되어야 하는가를 판단하는 일에 더 가깝습니다.

이 개념이 @Profile, 조건부 빈 등록, 전략 맵 주입, 팩토리, Feature Flag, 멀티테넌시용 선택 로직 같은 방식으로 자주 드러납니다. 즉, DI는 더 이상 단순한 객체 조립 기술이 아니라, 환경과 정책 차이를 애플리케이션 시작 시점 혹은 요청 흐름에 맞게 분기하는 구조적 도구로도 쓰입니다.

부가기능의 추가

데코레이터 패턴과 연결해서, 핵심 기능은 그대로 둔 채 부가 기능을 덧씌우는 방식도 DI의 활용으로 설명합니다. 트랜잭션, 로깅, 추가 검증 같은 기능을 본래 객체를 건드리지 않고 붙이는 방식입니다. 이것은 뒤의 AOP와도 자연스럽게 이어집니다.

인터페이스의 변경

DI는 인터페이스가 직접 맞지 않는 오브젝트를 중간 어댑터를 통해 연결하는 데도 쓰입니다. 이런 연결을 통해 클라이언트는 바뀌지 않고도 다른 구현체나 다른 API를 받아들일 수 있다고 설명합니다. 결국 DI는 구현 교체만이 아니라 구조 변환의 접점도 제공합니다.

프록시

프록시도 DI 맥락에서 설명합니다. 진짜 대상 앞에 대리 오브젝트를 두고, 실제 호출 전후를 제어하거나 지연 로딩, 원격 호출, 접근 제어 같은 기능을 붙일 수 있다는 것입니다. 이는 이후 AOP 설명으로 이어지는 중요한 연결점입니다.

템플릿과 콜백

템플릿/콜백 역시 DI의 특별한 적용 형태로 볼 수 있습니다. 반복되는 흐름과 가변 로직을 분리해 재사용성과 확장성을 함께 얻는 방식입니다. 스프링의 여러 템플릿 클래스가 이런 철학을 반영합니다.

싱글톤 오브젝트와 스코프

컨테이너가 객체 생명주기와 스코프를 관리한다는 점도 DI의 실전적 효용입니다. 싱글톤, 프로토타입, 요청 스코프, 세션 스코프처럼 객체를 어떻게 생성하고 얼마나 유지할지 컨테이너가 통제할 수 있습니다. 결국 객체 생성 자체도 애플리케이션 코드 바깥의 관심사가 됩니다.

테스트

마지막으로 DI의 아주 중요한 용도로 테스트를 강조합니다. 협력 객체를 교체하거나 가짜 구현으로 대체하기 쉬워지기 때문입니다. 이 점은 지금도 전혀 바뀌지 않았습니다.

8.4.2. AOP

AOP를 객체지향 프로그래밍의 한계를 보완하는 기술로 설명합니다. 객체지향 설계만으로도 많은 문제를 풀 수 있지만, 트랜잭션, 보안, 로깅, 모니터링처럼 여러 객체와 계층을 가로질러 반복되는 기능은 객체 내부로만 밀어 넣기 어렵습니다. 이런 횡단 관심사를 핵심 로직 밖으로 분리해 적용하는 것이 AOP의 목적입니다.

중요한 점은 AOP를 단순히 어떤 신기한 기술로 이해하면 안 된다는 것입니다. 책이 말하는 AOP의 핵심은 기술적인 부가 기능이 핵심 비즈니스 코드를 더럽히지 않게 한다는 데 있습니다. 즉, AOP는 POJO 프로그래밍을 깨뜨리는 기술이 아니라, 오히려 POJO를 지키기 위해 필요한 기술입니다.

지금도 AOP의 핵심 용도는 크게 바뀌지 않았습니다. 여전히 가장 대표적인 예는 @Transactional입니다. 현재 공식 문서 역시 많은 사용자가 선언적 트랜잭션 관리를 선택한다고 설명하고, 이 방식이 애플리케이션 코드에 가장 덜 침투적이라고 말합니다. 다만 실무 감각은 조금 달라졌습니다. 오늘날 많은 개발자는 AOP를 쓴다기보다 @Transactional, 캐시 추상화, 일부 관측성/로깅/보안 기능을 애노테이션과 프록시 기반으로 적용한다`는 방식으로 AOP를 체감합니다. 즉, 개념은 AOP지만 사용 경험은 훨씬 선언적이고 일상적이 되었습니다.

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

PSA는 특정 기술 자체를 감추겠다는 뜻이 아니라, 서로 다른 기술에 대해 애플리케이션이 더 일관된 방식으로 접근할 수 있게 추상화 계층을 제공한다는 뜻입니다. 책은 이를 통해 POJO가 특정 환경이나 특정 기술 구현에 직접 종속되지 않도록 돕는다고 설명합니다.

대표적인 예로 책은 트랜잭션 추상화를 듭니다. 트랜잭션을 직접 JTA API나 특정 기술 API로 다루지 않고, 스프링의 일관된 추상화를 통해 접근하게 만드는 것입니다. 같은 아이디어는 자원 접근, 예외 추상화, 캐시, 메일, OXM 같은 여러 영역에도 적용됩니다.

핵심은, 애플리케이션은 무슨 기술을 쓰는가보다 무슨 역할이 필요한가에 더 가까운 인터페이스와 프로그래밍 모델에 기대야 합니다. 구체 기술은 바뀔 수 있지만, 애플리케이션이 기대하는 역할은 상대적으로 안정적이기 때문입니다.

재해석

8.3은 스프링의 본질이 POJO 프로그래밍에 있다는 점을 설명합니다. POJO는 단순히 평범한 자바 클래스가 아니라, 특정 기술 규약과 환경에 본질적으로 종속되지 않고 객체지향 설계 원리에 충실한 객체를 뜻합니다. 8.4는 그런 POJO 중심 개발을 가능하게 만드는 스프링의 기술을 설명합니다. IoC/DI는 객체 관계를 외부로 밀어내고, AOP는 공통 기술 관심사를 핵심 로직 밖으로 분리하며, PSA는 서로 다른 기술에 대한 접근 방식을 더 안정된 추상화 뒤로 감춥니다.

결국 스프링을 잘 이해한다는 것은 기술을 몇 개 안다가 아니라, 왜 이 기술들이 모두 POJO를 지키는 방향으로 설계되어 있는가를 이해하는 것입니다. 지금의 Spring Framework와 Spring Boot 환경에서도 이 관점은 그대로 유효합니다. 오히려 자동 설정과 다양한 통합 기능이 늘어난 지금일수록, 중심에는 여전히 순수한 비즈니스 로직과 좋은 객체 설계가 있어야 한다는 사실을 더 강하게 기억해야 합니다.

Last updated