스프링(Spring)에 대해 알아보자!

코린이·2024년 9월 13일
post-thumbnail

서론

저는 올해 초 스프링에 입문하고 혼자 혹은 팀으로 여러 프로젝트를 진행했습니다. 지금 시점에서 저 자신을 돌아보니 코딩은 어느정도(?) 하는거 같은데 스프링에 대한 이론적인 지식은 많이 부족하다는 것을 알게되었습니다. 이번에 확실히 스프링 부트에 대한 이론은 제대로 알고 넘어가야 스프링 부트 개발자로서 더 성장할 수 있다고 생각되어 글을 쓰게 되었습니다. 그럼 함께 스프링에 대해 공부해볼까요?🥹

스프링

스프링이란 무엇일까요?
해당 의문에 대해 정답을 찾기위해 일부 블로그를 찾아보았고 저에게 가장 와닿는 정의는 다음과 같았습니다.

엔터프라이즈용 Java 애플리케이션 개발을 위한 오픈소스 경량 애플리케이션 프레임워크

즉, 기업에서 사용할 수 있을 정도의 대규모의 어플리케이션을 만들기에 적합한 오픈소스 프레임워크입니다.
해당 정의에서 생소할수도 있는 부분을 정리하고 넘어가겠습니다.

경량

대규모의 어플리케이션을 만들기에 적합하다고 했는데 왜 경량일까요?
여기서 말하는 경량은 개발자가 작성해야 할 코드의 길이가 적음을 의미합니다.
스프링을 사용하기 이전에 EJB라는 기술을 기업에서 많이 사용하였습니다. 하지만 해당 기술을 복잡하고 EJB에서 기술이나 패턴을 강제하였기 때문에 개발자가 비지니스 로직에 집중하기 힘들었스빈다.

스프링은 기존 기술(EJB)에서 불가피하게 사용되어야만 했던 복잡한 코드들을 모두 제거하고 코드의 복잡성을 낮춰 개발자가 비지니스 로직에 더 집중할 수 있도록 설계되었습니다.

애플리케이션 프레임워크

개발을 조금 해보신 분들이라면 프레임워크라는 단어를 접해본 적이 있을 겁니다.
프레임워크는 라이브러리와 서로 반대되는 개념이라고 볼 수 있습니다.

Libary VS Framework

라이브러리와 프레임워크 차이는 누가 제어권을 가지냐에 따라 다르다고 볼 수 있습니다.

라이브러리는 개발자가 필요한 기능을 선택하여 임포트하고, 언제, 어디서 호출할지 결정할 수 있어 제어권은 개발자가 가진다고 볼 수 있습니다.

반대로 프레임워크은 이미 전체 어플리케이션의 구조와 흐름이 프레임워크를 제작한 사람에 의해 결정되어 있고 개발자는 그 구조에 맞춰 코드를 작성해야 합니다. 그렇기에 제어권은 프레임워크에 있으며, 프레임워크가 개발자의 코드를 호출하게 됩니다.
이처럼 개발자가 작성한 객체나 메서드의 제어를 개발자가 아니라 외부에 위임하는 설계 원칙을 제어의 역전(Inversion Of Control, IoC) 이라고 합니다. 즉, 프레임워크는 제어의 역전 개념이 적용된 대표적인 기술이라고 할 수 있습니다.

import org.apache.commons.lang3.StringUtils;

public class LibraryExample {
    public static void main(String[] args) {
        String original = "   Hello, Java!   ";
        
        // 라이브러리의 trim 기능 호출
        String trimmed = StringUtils.trim(original);
        
        System.out.println("Original: \"" + original + "\"");
        System.out.println("Trimmed: \"" + trimmed + "\"");
    }
}

위의 예시코드는 라이브러리를 사용한 예제로 StringUtils 라이브러리의 trim() 메소드를 사용하여 original의 앞, 뒤 공백을 제거하는 코드입니다.
해당 코드에서 제어권은 개발자에게 있습니다. 개발자가 StringUtils.trim()을 언제 호출할지 결정합니다.

// 프레임워크 예시 (Spring Framework 사용)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class FrameworkExample {
    public static void main(String[] args) {
        SpringApplication.run(FrameworkExample.class, args);
    }
}

@RestController
class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring Framework!";
    }
}

반대로 위의 예시코드는 스프링이 처음이시라면 이해가 어려울 수 있지만 프레임워크를 사용한 예제로 Spring Boot 프레임워크를 사용해 간단한 웹 서버를 실행하고, /hello라는 경로로 요청이 들어오면 "Hello, Spring Framework!"를 반환하는 REST API를 만들었습니다.

해당 코드에서 제어권은 프레임워크에 있습니다. SpringApplication.run()을 호출하면 Spring이 애플리케이션을 제어하고, 요청이 들어왔을 때 hello() 메소드를 호출합니다.

해당 프레임워크 섹션의 개념들이 좀 많이 낮설고 왜 쓰는지 이해가 안될 수 있다고 생각합니다. 뒷부분에서 조금 더 상세하게 다뤄보겠습니다.

스프링의 특징

지금까지 스프링의 정의에 대해 알아보았습니다.
그렇다면 스프링 프레임워크의 특징에 대해 알아보도록 하겠습니다.

POJO 프로그래밍

스프링은 POJO 프로그래밍을 채택하였습니다.

POJO 프로그래밍은 다른 기술을 사용하지 않고 오로지 Java 기술만을 사용하여 객체를 만드는 방식입니다.

POJO(Plain Old Java Object)는 오래된 방식의 간단한 자바 오브젝트라는 말로서 Java EE 등의 중량 프레임워크들을 사용하게 되면서 해당 프레임워크에 종속된 무거운 객체를 만들게 된 것에 반발해서 사용되게 된 용어이다. POJO는 2000년 9월에 마틴 파울러, 레베카 파슨, 조쉬 맥킨지 등이 사용하기 시작한 용어이다.

public class Burger {
    
    private String bread; // 빵
    private String patty; // 패티
    
    // getter
    public long getBread() {
        return bread;
    }
    public String getPatty() {
        return patty;
    }
    
    // setter
    public void setBread(String bread) {
        this.bread = bread;
    }
    public void setPatty(String patty) {
        this.patty = patty;
    }
}

위의 코드는 field, getter, setter만을 이용하여 만들어진 POJO 프로그래밍의 조건을 만족하는 코드입니다.
그렇다면 스프링은 왜 POJO 프로그래밍을 채택하였을까요?

스프링이 나오기 전 기업에서는 EJB(Enterprise Java Bean)이라고 불리는 기술을 사용하여 서비스를 구현하였습니다.

EJB(Enterprise Java Bean)
기업환경의 시스템을 구현하기 위한 서버측 컴포넌트 모델이다.
자바를 이용해 비즈니스 서비스를 개발할 때 비즈니스 로직 뿐만 아니라 트랜잭션, 보안 등 로우레벨의 로직까지 작성해야하는 부담감을 없애고자 EJB(Enterprise Java Beans)를 만들게 되었다.

당시에 EJB는 강력한 기능들을 지원했지만 그에 따라 아래와 같은 문제점이 존재했습니다.

  • EJB의 복잡성: EJB는 트랜잭션 관리, 원격 호출, 보안 등 엔터프라이즈 기능을 제공했지만, 이를 사용하려면 많은 설정과 복잡한 구조를 요구했습니다. 개발자는 EJB 스펙에 맞춰 작성해야 했고, 이는 학습 곡선을 높이며 생산성을 떨어뜨렸습니다.

  • 프레임워크 종속성: EJB에서 작성된 코드는 특정 컨테이너에 종속적이었으며, 이를 다른 환경에서 재사용하기 어려웠습니다. 비즈니스 로직을 단순하게 구현하더라도 EJB 스펙을 따르다 보니 코드가 무거워지고 유지보수성이 떨어졌습니다.

  • 유연성 부족: EJB는 특정 기술이나 패턴을 강제했기 때문에, 개발자가 더 유연한 설계를 적용하기 어려웠습니다. 또한 테스트 환경 설정이 복잡해 단위 테스트를 효율적으로 수행하기 어려웠습니다.

이러한 문제를 해결하고자 Spring에서는 POJO 프로그래밍을 지향하게 되었고 다음과 같은 장점을 누릴 수 있었습니다.

  • 단순한 비즈니스 로직 작성: POJO는 단순한 자바 객체를 의미합니다. EJB처럼 복잡한 인터페이스를 구현하거나 특정 규칙을 따를 필요가 없기 때문에 개발자는 자유롭게 비즈니스 로직을 구현할 수 있습니다. 이는 코드의 가독성과 유지보수성을 크게 향상시켰습니다.

  • 테스트 용이성: POJO 기반의 코드는 프레임워크에 종속적이지 않으므로, 독립적으로 테스트할 수 있습니다. 스프링은 객체 생성과 의존성 관리를 IoC 컨테이너를 통해 처리하기 때문에, 테스트 시 의존성을 쉽게 대체할 수 있어 단위 테스트가 매우 용이해졌습니다.

  • 경량화: 스프링은 EJB처럼 무겁고 복잡한 설정 파일 없이도 핵심 엔터프라이즈 기능을 제공했습니다. POJO 객체를 관리하면서도, 트랜잭션 관리, AOP(Aspect-Oriented Programming), 의존성 주입 등 다양한 기능을 유연하게 제공했습니다.

  • 프레임워크 종속성 탈피: 스프링의 POJO 프로그래밍 방식은 특정 API나 기술 스택에 종속되지 않습니다. 스프링에서 관리하는 빈(Bean)은 단순한 자바 객체이므로, 다른 환경에서도 쉽게 사용 가능하며 재사용성도 높습니다.

POJO 프로그래밍를 지원하는 Spring

스프링에서 이러한 POJO 프로그래밍을 지원하기 위해 다음과 같은 대표적인 3가지 기술들을 사용합니다. 각각 알아보도록 하겠습니다.

IoC(Inverse of Control)

먄약 우리가 스프링 프레임워크에 우리가 만들고자 하는 비지니스 로직을 추가할려면 어떻게 해야 될까요?

스프링 프레임워크는 IoC라는 설계 원칙을 따라 구현된 프레임워크이기 때문에 객체의 생성과 의존 관계를 개발자가 직접 관리하는 것이 아니라, Spring 컨테이너가 대신 관리하도록 위임합니다.

우리가 추가하고자 하는 비지니스 로직은 객체로 이루어져 있습니다. 이러한 객체를 스프링이 스스로 관리하도록 위임하기 위해서는 우리의 비지니스 로직 객체를 스프링 프레임워크에게 전달해야 합니다.
이러한 전달을 위해 스프링에서는 DI(Dependency Injection)이라고 불리는 개념을 적용하였습니다.

의존성 주입(DI)은 객체가 다른 객체에 의존할 때, 그 의존성을 객체 내부에서 직접 생성하지 않고 외부에서 주입하는 방식입니다. 즉, 우리의 비지니스 로직을 의존성이라는 형태로 스프링 프레임워크에 주입하게 되면 비지니스 로직이 동작하게 되는 것이죠.

스프링에서 IoC (Inversion of Control) 개념을 구현하는 중요한 역할을 하는 것이며
Spring Container는 Spring 프레임워크에서 객체의 생성, 관리, 소멸을 담당하는 핵심 컴포넌트입니다.

Spring Container까지 다루기에는 글이 너무 길어져 다음 번에 기회가 된다면 따로 다룰 수 있도록 하겠습니다.

AOP(Aspect-Oriented Programming)

Spring AOP(Aspect-Oriented Programming)는 관점 지향 프로그래밍을 의미하며, 코드에서 공통적으로 사용되는 기능을 분리하여 관리하는 방법입니다. 핵심 로직 외에 공통적으로 적용되는 기능(예: 로깅, 트랜잭션 관리, 보안 등)을 분리하여 모듈화함으로써 코드의 가독성과 유지보수성을 높일 수 있습니다.

즉, 비지니스 로직이라는 핵심적인 관심 사항과 로깅, 트랜잭션 관리, 보안 등과 같은 공통적으로 사용되는 관심 사항을 서로 분리시켜 개발자가 비지니스 로직에 더 집중하고 유지보수성을 높일 수 있습니다.

PSA(Portable Service Abstraction)

Spring PSA(Portable Service Abstraction)는 스프링 프레임워크의 핵심 개념 중 하나로, 서비스 추상화 계층을 제공합니다. 말이 어려운데 쉽게 설명하면 PSA는 다양한 서비스의 구현을 추상화하고, 현재 필요한 구현 클래스로 바꿔끼는 방식으로 쉽게 다른 기술로 전환할 수 있다는 의미입니다.

예를 들어 API로 요청이 들어오면 쿼리문을 통해 MySQL 데이터베이스에 요청받은 데이터를 저장해야 된다고 가정해보겠습니다.
이때 갑자기 MySQL에서 MariaDB로 데이터베이스를 바꾼다고 생각해보겠습니다. 아마 MySQL에 맞춰 짠 쿼리문을 모두 변경해야될 수도 있습니다. (저 같으면 아마 멘붕이 올 거 같네요.🤯)

이런 경우 스프링에서 제공하는 JDBC(Java DataBase Connectivity)나 JPA를 사용하면 추상화된 코드를 작성하고 필요한 데이터베이스 벤더에 맞춰 드라이버만 설정해주면 앞선 상황을 방지할 수 있다. 또한 중간에 데이터베이스의 벤더가 바뀌더라도 일부 코드만 수정하면 되기 때문에 유지보수에도 도움이 된다.

마무리

정말 오랜만에 쓰는 글이라서 글에 두서가 없는 거 같습니다.😭
혹시라도 잘못된 부분이 있다면 댓글을 남겨주시면 확인하고 반영하겠습니다.
오늘도 즐거운 코딩 되세요!!

profile
호기심이 많고, 문제를 끝까지 해결하려는 집념이 강한 개발자입니다.

0개의 댓글