스프링(Spring)이란?
스프링은 엔터프라이즈용 Java 애플리케이션 개발을 편하게 할 수 있게 해주는 오픈소스 경량급 애플리케이션 프레임워크이다.
정의를 하나씩 뜯어보자면 다음과 같다.
엔터프라이즈용 Java 애플리케이션 개발을 편하게 할 수 있게 해주는
기업에서 운영하는 웹 서비스에는 비즈니스 로직이라는 것이 있다. 비즈니스 로직이란, 기업이 제공하는 서비스를 코드로 구현한 것으로, 사용자의 요구사항을 해결하기 위한 실질적인 코드를 의미한다. 스프링이 등장하기 이전에는 비즈니스 로직을 구현하기 위해 기술 자체에 대한 공부를 추가적으로 해야만 했다. 비즈니스 로젝을 구현하는 기술 자체가 복잡하고 어려웠기 때문이다.
그러나, 스프링은 이전 기술에 비해 사용 방법이 상대적으로 덜 복잡하다.
따라서, 개발 초기에 기본적인 설정과 적용시킬 기술들만 잘 선택해 준다면, 기술보다는 애플리케이션의 로직 자체에 더 집중하여 비즈니스 로직을 구현할 수 있다.
오픈소스
스프링은 모든 사용자에게 무료로 열려있다. 즉, 어떤 개인 및 기업도 스프링을 사용하여 웹 애플리케이션을 개발할 수 있으며, 필요하다면 스프링의 코드를 일부 수정하여 사용하여도 무관하다. 이처럼 오픈소스로 프로젝트를 공개해 놓으면 여러 사람이 프로젝트의 코드를 사용해 봄으로써 다양한 검증 과정을 거칠 수 있다.
하지만, 뚜렷하게 정해진 인원이 프로젝트의 개발과 관리를 맡는 것이 아니기 때문에, 프로젝트의 개발과 개선이 안정적이지 못하다는 단점 또한 가질 수밖에 없다. 기업에서 사용하는 프레임워크는 반드시 안정적이어야 한다.
프레임워크의 불안정성은 서비스 제공의 불안정성으로 이어지고, 결국 기업의 수익 감소로 이어질 수 있기 때문이다.
그러나, 스프링은 오픈소스 프레임워크이지만, 안정적인 개발과 개선이 보장된다. 현재 스프링은 오픈소스로 배포되어 누구나 이용할 수 있지만, 스프링소스(SpringSource)라는 IT기업에서 관리하고 있으며, 스프링의 소스코드를 수정하거나 개선하는 일에는 스프링소스의 한정적인 인원만 참여할 수 있다.
경량급
스프링은 수십개의 세부 모듈 및 수십만 줄의 방대한 코드로 이루어진 프레임워크이다. 그럼에도 불구하고 스프링을 정의할 때 경량급이라는 수식어를 사용한다. 여기서 스프링의 경량급은 기존에 스프링 대신 사용하던 기술들과 비교하여, 스프링을 사용했을 대에 개발자가 작성해야 할 코드가 상대적으로 단순하다는 것을 표현하기 위함이다.
스프링이 등장하기 이전에는 EJB(Enterprise Java Bean)이라는 기술이 주로 사용되었다. EJB도 이전에 사용되던 기술의 단점을 보완하기 위하여 등장했지만, 여전히 불필요하게 복잡한 코드를 작성해야만 했다. 이에 따라 많은 개발자들이 이런 불필요한 코드를 어떻게 걷어내고 복잡성을 줄일 수 있지 고민했고, 그 결과 스프링이 탄생했다.
따라서, 스프링을 정의함에 있어 경량급 애플리케이션 프레임워크라 함은, 스프링을 사용함으로써 기존 기술을 사용할 때 불가피하게 작성해야만 했던 복잡한 코드를 제거하여 코드의 복잡성을 낮출 수 있음을 의미한다.
애플리케이션 프레임워크
애플리케이션 프레임워크가 무엇인지 이해하기 위해서는 프레임워크에 대해서 알아야 한다.
자동차를 만든다고 가정해 보자.
이 때, 처음부터 끝까지 자동차의 모든 것을 손수 만드는 것보다는, 자동차 회사에서 자동차의 차체를 사 와서 차체에 각 부품을 만들어 붙이는 것이 훨씬 더 편리할 것이다. 즉, 자동차 회사에서는 미리 자동차의 차체를 만들어뒀고, 우리는 그것을 가져다가 사용하기만 하면 되는 것이다. 여기서 자동차의 차체에 해당되는 것이 바로 프레임워크이다. 웹 개발에 있어 프레임워크란, 어떤 목적을 쉽게 달성할 수 있도록 해당 목적과 관련된 코드의 뼈대를 미리 만들어둔 것을 의미한다.
그렇다면 애플리케이션 프레임워크란 무엇일까?
일반적으로 프레임워크는 특정 업무 분야 혹은 하나의 기술에 집중한다. 즉, 어떠한 특화된 목적을 편리하게 달성할 수 있게 하기 위해 만들어진다. 반면 애플리케이션 프레임워크는 특정 업무 분야 및 특정 기술이 아니라, 애플리케이션 개발에 필요한 모든 과정에 집중한다. 다시 말해, 애플리케이션 프레임워크는 애플리케이션을 개발하는 데에 있어 필요한 모든 업무 분야 및 모든 기술과 관련된 코드들의 뼈대를 제공한다.
스프링의 특징
POJO 프로그래밍을 지향
스프링의 가장 큰 특징은 POJO 프로그래밍을 지향한다는 것이다.
POJO란, Plain Old Java Object, 즉 순수 Java만을 통해서 생성한 객체를 의미한다.
순수 Java만을 사용한다는 것은 Java 및 Java 스펙에 정의된 기술만 사용한다는 의미이다. 즉, 어떤 객체가 외부 라이브러리나 외부의 모듈을 가져와서 사용하고 있다면, 그 객체는 POJO라고 할 수 없다. POJO는 말 그대로, 다른 기술을 사용하지 않는 순수한 Java만을 사용하여 만든 객체인 것이다.
POJO가 중요한 이유가 무엇일까?
외부 라이브러리를 import 하여 라이브러리의 메서드를 사용하고 있는 객체가 있다고 가정해보자.
이 객체는 순수 Java 외에 기술을 사용하고 있으므로, POJO가 아니다. 이 객체가 사용하고 있는 기술이 Deprecated 되거나, 개선된 신 기술이 등장하여 기존 기술과 관련된 코드를 모두 고쳐야 하는 상황이 발생하면 해당 기술을 사용하고 있는 모든 객체들의 코드를 전부 바꿔주어야만 한다.
반면, POJO는 순수 Java만을 사용하여 만든 객체이므로 특정 기술이나 환경에 종속되지 않는다. 따라서, 외부 기술이나 규약에 변화를 얽매이지 않아 보다 유연하게 변화와 확장에 대처할 수 있다. 이러한 POJO를 사용하여 비즈니스 로직을 구현하면 객체지향 설계를 제한 없이 적용할 수 있으며, 코드가 단순해져 테스트와 디버깅 또한 쉬워진다. 이처럼 비즈니스 로직을 구현하는 데에 POJO를 적극적으로 활용하는 프로그래밍 패러다임을 POJO 프로그래밍이라고 한다.
POJO에 대한 자세한 글 : https://brightstarit.tistory.com/18
POJO 프로그래밍을 위해 스프링이 지원하는 기술인 IoC/DI, AOP, PSA에 대해서 알아보자.
IoC / DI (Inversion of Control / Dependency Injection, 제어의 역전 / 의존성 주입)
Java는 객체지향 언어이므로, 객체들 간의 관계를 적절히 맺어주는 것이 중요한 요소일 것이다.
여기서 관계를 맺어준다는 것은, A 인스턴스가 B 인스턴스의 메서드를 호출하고 있다면 A는 B와 의존 관계를 맺은 것이 되며, 이 둘의 관계를 'A가 B에 의존하는 관계'라고 표현할 수 있다.
class A {
public void methodOfA() {
B b = new B();
b.example();
}
}
class B {
public void example() {
...
}
}
위의 A와 B의 의존 관계는 개발자에 의해 만들어진다. 개발자가 직접 new
키워드를 사용하여 인스턴스를 생성하는 코드를 작성했기 때문이다. 하지만 위처럼 코드를 작성할 때에 필연적으로 발생하는 문제가 있다. 만약, A가 사용할 객체를 B가 아니라, 새롭게 C를 정의해서 사용하고자 한다면 A코드를 다음과 같이 변경할 수밖에 없다.
class A {
public void methodOfA() {
// B b = new B();
// b.example();
C c = new C();
c.example();
}
}
class B {
public void example() {
...
}
}
class C {
public void example() {
...
}
}
위 예제에서는 기존에 B를 사용하던 객체가 A하나뿐이므로 간단하게 바꿔주면 되지만, 만약에 기존에 B를 사용하던 객체가 A 뿐만 아니라, 수십 또는 수백 개가 있다면 모든 객체의 코드를 수정해주어야 합니다.
// (1) A가 사용하는 메서드를 인터페이스 I의 추상 메서드로 정의한 다음, (추상화)
interface I{
void example();
}
class A {
// (3) 그 다음, 인터페이스 타입의 필드를 선언하고,
private I i;
// (4) 생성자를 통해 외부로부터 인스턴스를 받아와 i를 초기화해줍니다. (다형성)
public A(I i){
this.i = i;
}
public void methodofA() {
// (5) 외부로부터 받아온 인스턴스의 메서드를 호출합니다.
i.example();
}
}
// (2) A가 사용하는 메서드를 가진 객체들이 I를 구현하도록 합니다.
class B implements I {
public void example() {
...
}
}
class C implements I {
public void example() {
...
}
}
위 예제에서는 A는 자신이 사용할 객체를 스스로 생성하지 않고, 생성자를 통해 외부로부터 받아오고 있다. 즉, A는 자신이 사용할 객체를 알지 못하며, 그저 i에 할당된 인스턴스에 example()
이라는 메서드가 존재한다는 것만 알고 있다.
그렇다면 누군가 A가 사용할 객체를 결정하고 생성해서 A가 인스턴스화될 때 인자로 전달해주어야만 한다. 그래야 A는 B의 것이든, C의 것이든 example()
메서드를 호출할 수 있을 것이기 때문이다.
그 누군가가 바로 스프링이다. 스프링을 사용하면 애플리케이션의 로직 외부에서 A가 사용할 객체를 별도로 설정할 수 있다. 개발자가 설정 클래스 파일에 A가 사용할 객체를 C로 설정해 두면, 애플리케이션이 동작하면서 스프링이 설정 클래스 파일을 해석하고, 개발자가 설정해 둔 대로 C 객체를 생성하여 A의 생성자의 인자로 C를 전달해 준다.
이처럼 개발자가 아닌 스프링이 A가 사용할 객체를 생성하여 의존 관계를 맺어주는 것을 IoC(Inversion of Control, 제어의 역전)라고 하며, 그 과정에서 C를 A의 생성자를 통해 주입해 주는 것을 DI(Dependency Injection, 의존성 주입)라고 한다.
AOP (Aspect Oriented Programming, 관심 지향 프로그래밍)
애플리케이션을 개발할 때에 구현해야 하는 기능들은 크게 공통 관심 사항과 핵심 관심 사항으로 분류할 수 있다.
핵심 관심 사항은 애플리케이션의 핵심 기능과 관련된 사항으로, 커피 주문 애플리케이션을 예로 든다면 메뉴 등록하기, 주문하기, 주문 변경하기 등이 있을 것이다.
반면, 공통 관심 사항은 모든 핵심 관심 사항에 공통적으로 적용되는 관심 사항들을 의미한다. 예를 들어, 메뉴 등록하기, 주문하기, 주문 변경하기 등 모든 핵심 관심 사항에는 로깅이나 보안 등과 관련된 기능들이 공통적으로 적용되어야만 한다.
이때, 핵심 관심 사항과 공통 관심 사항이 코드에 아래와 같이 함께 모여 있으면 필연적으로 공통 관심 사항과 관련된 코드가 중복될 수밖에 없다. 이처럼 코드가 중복되어 있는 경우, 공통 관심 사항을 수행하는 로직이 변경되면 모든 중복 코드를 찾아서 일일이 수정해주어야만 한다.
class OrderApp {
...
public void 주문하기 () {
// 공통 관심 사항
로깅 관련 코드
보안 관련 코드
// 핵심 관심 사항
주문 관련 로직
}
public void 주문_변경하기() {
// 공통 관심 사항
로깅 관련 코드
보안 관련 코드
// 핵심 관심 사항
주문 변경 관련 로직
}
public void 메뉴_등록하기() {
// 공통 관심 사항
로깅 관련 코드
보안 관련 코드
// 핵심 관심 사항
메뉴 등록 관련 로직
}
}
위의 예제에서 발생하는 코드의 중복이라는 문제를 해결하기 위해서 공통 관심 사항과 관련된 기능들을 별도의 객체로 분리해 낸 다음, 분리해낸 객체의 메서드를 통해 공통 관심 사항을 구현한 코드를 실행시킬 수 있도록 해야 한다. 이처럼, 애플리케이션 전반에 걸쳐 적용되는 공통 기능을 비즈니스 로직으로부터 분리해 내는 것을 AOP(Aspect Oriented Programming, 관심 지향 프로그래밍)라고 한다.
PSA(Portable Service Abstraction, 일관된 서비스 추상화)
스프링은 Java 백엔드 개발에 있어 핵심적인 역할을 수행하는 프레임워크이며, 백엔드 개발에서 데이터베이스는 떼어놓기 어렵다. 웹서버는 데이터베이스와 소통하며 웹 클라이언트의 요청을 처리해야 하기 때문이다. 데이터베이스의 종류는 MySQL, Oracle, Maria DB, Mongo DB 등 다양하다.
만약, MySQL을 사용하여 개발을 완료했는데, Maria DB로 데이터베이스를 바꿔야 하는 상황을 가정해 보자.
이때, 각 데이터페이스마다 사용 방법이 다르다면 기존에 작성한 코드를 전부 지우고 새로 작성해야 하거나, 기존 데이터베이스와 새로운 데이터베이스 간에 사용 방법이 다른 코드를 모두 찾아서 일일이 수정해주어야 할 것이다.
그러나, 스프링을 사용하면 동일한 사용방법을 유지한 채로 데이터베이스를 바꿀 수 있다. 이는 스프링이 데이터베이스 서비스를 추상화한 인터페이스를 제공해 주기 때문에 가능하다. 즉, 스프링은 Java를 사용하여 데이터베이스에 접근하는 방법을 규정한 인터페이스를 제공하고 있으며, 이를 JDBC(Java DataBase Connectivity)라고 한다.
각 데이터베이스를 만든 회사들은 자신의 데이터베이스에 접근하는 드라이버를 Java 코드의 형태로 배포하는데, 이 드라이버에 해당하는 Java 코드의 클래스가 JDBC를 구현한다. 따라서, JDBC를 기반으로 하여 데이터베이스 접근 코드를 작성해 두면, 이후에 데이터베이스를 바꾸어도 기존에 작성한 데이터베이스 접근 로직을 그대로 사용할 수 있다.
이러한 JDBC처럼 특정 기술과 관련된 서비스를 추상화하여 일관된 방식으로 사용될 수 있도록 한 것을 PSA(Portable Service Abstraction, 일관된 서비스 추상화)라고 한다.
참고 자료
'백엔드 > Spring' 카테고리의 다른 글
레이어간 의존 관계 문제 (0) | 2024.07.29 |
---|---|
[Spring] 동적 프록시(Dynamic Proxy) (1) | 2023.05.26 |
[Spring Cloud] Spring Cloud OpenFeign (0) | 2023.05.22 |
[Spring] Spring Boot Actuator (0) | 2023.05.18 |
[Spring] POJO(Plain Old Java Object) (0) | 2023.04.27 |