Baeldung - Why Choose Spring as Your Java Framework?

sycho·2024년 3월 14일
0

Baeldung의 글을 정리 및 추가 정보를 넣은 글입니다.

Overview

  • 프레임워크 사용 이유, 그리고 그 여러 프레임워크 중 왜 하필 스프링을 사용하는 이유를 배운다.

  • 스프링 사용 이유다. 스프링 부트 사용 이유가 아니다.

Why Use Any Framework

  • 프레임워크를 사용하는 이유는

    • 업무와 관련된 코딩에만 집중하기 위해. 업무와 관련없는 코드 (boilerplate code)의 코딩 시간을 최소화하기 위해
    • 디자인 패턴의 형태로 수년간의 노하우를 집약시켜줘 좋은 방향으로 코딩을 할 수 있도록 해주기 때문에
    • 기업 표준 / 제약 사항 준수를 쉽게 하도록 도와주기 때문에
    • 애플리케이션 소유로 인해 발생하는 비용을 최소화하기 때문에
  • 그러나 단점도 있다.

  • 보통 이 장단점 사이에서 저울질하며 프레임워크를 사용할지 안할지를 결정한다.

Brief Overview of Spring Ecosystem

  • Spring은 2003년에 등장했다. 당시에는 JEE (Java Enterprise Edition)가 막 유행하기 시작했으며, 애플리케이션 개발이 많이 번거로웠던 편.

  • 주된 목표는 JAva를 위한 IoC container이 되는 것이다. IoC container이 뭔지는 다른 글에서 다루겠다.

Spring Framework

  • Spring 이코시스템의 가장 핵심적인 영역은 당연히 Spring 프레임워크 자체다. 이것은 또 여러 모듈로 나뉘어진다. 크게 다음과 같이 나눌 수 있다.

  • Core : 스프링과 관련된 핵심 기능들 제공. DI, AOP, internationalization, validation 등을 지원한다.

  • Data Access : 데이터 접근과 관련된 기능을 제공. 여러 방식을 제공하는데 JTA, JPA, JDBC로 접근하는 방식을 지원한다. 대표적인 Spring Data JPA의 경우 JPA를 더 쉽게 활용할 수 있도록 돕는 모듈이다.

  • Web : Servlet API (by Spring MVC) / Reactive API(by Spring WebFlux), WebSocket, WebClient, STOMP등의 웹과 관련된 기능을 전부 제공한다.

  • Integration : 애플리케이션을 만들고 나면 이를 기업 구조 자체에 통합시키는 작업이 필요하다. 그 과정에서 통신이 필요할텐데 이와 관련된 기술인 JMS, JMX, RMI등을 제공한다.

  • Test : 유닛/통합 테스트를 하는데 필요한 Mock Object, Test Fixture, Context Management, 캐싱 등을 제공한다.

Spring Projects

  • 또 다른 Spring 이코시스템의 핵심 영역은 Spring과 관련된 여러 추가 프로젝트들이다. 이들은 모두 수년간 연구되었으며 Spring Framework를 더 유용하게 사용할 수 있도록 도와준다.

이 중 몇몇을 언급하자면

  • Spring Boot : Spring 기반 프로젝트를 빠르게 형성하는데 도와준다. 특정 Spring Project를 만드는데 필요한 dependency를 매우 구체적으로 정의해 주관이 많이 기입되어있지만, 일단 이를 수긍할 수 있으면 관련 프로젝트를 매우 빠르게 만들 수 있다.
  • Spring Cloud : 클라우드와 같은 분산 시스템의 여러 디자인 패턴에 기반한 개발을 손쉽게 하게 해준다. 대표적으로 Service discovery, circuit breaker, API Gateway같은 디자인 패턴을 손쉽게 해준다.
  • Spring Security : 웹 개발에서 authentication/authorization은 매우 신경 써야 할 요소다. 이를 손쉽게 설정할 수 있도록 도와주는 프로젝트다.
  • Spring Mobile : 디바이스 탐지 및 애플리케이션이 거기에 적응할 수 있도록 도와주는 프로젝트다. 또 디바이스를 의식하는 view 관리를 가능하게 해준다. 이 view 관리는 최적의 사용자 경험 제공에 도움을 주고 사이트 선호 관리, site switcher 기능 등을 만드는데 유용하다.
  • Spring Batch : 기업 시스템을 위한 batch application 개발을 하는데 유용한 가벼운 프레임워크를 제공해준다. 스케쥴링, 재시작, 스킵, 통계 수집, 로깅 등의 기능을 직관적으로 제공한다. 또 최적화와 파티션을 통해 대용량 태스크도 수행할 수 있

위의 내용들을 처음 보면 너무 방대하다 느낄 수 있는데, 추후 글들에서 천천히 다뤄보도록 하겠다.

Spring in Action

  • 간단하게 Spring 프로젝트가 어떻게 구성되는지 알아보자. 세부적인 것은 나중에 알아보겠다.

Project Set-Up

  • 보통 Spring initializr을 쓰는데... 사이트로 제공이 되지만 필자는 IntelliJ를 활용했다.

  • IntelliJ에서 Spring Boot 프로젝트를 만들겠다고 하면 원하는 dependency (starter)을 바로 추가하는 것이 가능하다. 이 프로젝트에서는 Web, JPA, H2, Security를 추가했다.

Domain Model and Persistence

  • 상호작용할 Data를 나타내는 class를 만들어보자. 이것은 Spring 문법을 활용하지 않고, JPA 문법을 활용한다.

  • 아마 아는 사람도 있을텐데 JPA는 Java 객체랑 DB 사이를 어떻게 매핑 및 관계를 형성하는지에 대한 '명시'다. 즉 실제로 우리가 사용하는 것들은 JPA에서 지정한 약속들을 지키는 프레임워크들이다. (Hibernate, EclipseLInk)

  • Spring은 여기에 한층 더 나아가, 위의 JPA를 구현한 프레임워크들 위에 하나의 층을 더 쌓았고 그것이 Spring Data, 정확히는 Spring Data JPA다. 목적은 앞에서 말했듯, JPA 구현 프레임워크들을 더 쉽게 사용할 수 있도록 하기 위해서다. JPA, JPA 구현체, Spring Data JPA에 대한 더 자세한 얘기는 이 글 참고.

  • 뭐 이런 관계는 알겠다. 그러면 Spring에서 JPA를 사용할려면 대체 어떻게 해야 하는가? 먼저 밑과 같은 Entity Class를 만든다. @Entity로 annotate된 class가 Entity class다. 이 class의 목표는 DB에 실제로 있는 Table 내의 각 record를 Java에서 어떤 형태의 class(의 객체)로 생각할지를 알려주는 것이다.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    private String firstName;

    @NotNull
    private String lastName;
    // Standard constructor, getters and setters
}
  • 여기서 유의할게 있는데, @NotNull이라는 annotation을 사용하려면 Jakarta Bean Validation API를 사용해야 한다. maven이든 gradle이든 이 extension을 추가하도록 하자.
  • 또 이미 어느정도 공부한 분들은, @Table이 class에 필요하지 않냐고 할 수 있다. 사실 그게 명시되어 있지 않아도 알아서 DB의 table과 mapping을 하는게 가능하다. 이 때 mapping되는 table은 class의 camelcase 이름에 대응되는 underscore 이름을 가지는 table. 뭐 애초에 @Entity가 table을 생성하는데 사용되기도 하지만 이건 나중 이야기.
  • 타 annotation에 대한 설명이나 주석 부분에 들어가야 할 코드는 나중에 Spring Data 공부를 하면서 차차 알아보도록 하자. 뭔지 대충 감은 오겠지만 말이다. 사실 이번 글에서는 저것의 정의가 그렇게 중요한건 아니다. 저 Entity를 위한 JPA repository를 얼마나 손쉽게 만들수 있냐가 중요한 것이다.

  • Spring은 Repository라는 인터페이스를 제공한다. 이는 어떤 Entity랑 연관된 Table을 접근하는데 사용된다. 여기서 JPA를 위한 Repository, JpaRepository라는 것이 존재한다. 이 녀석은 JPA랑 관련된 모든 method들을 제공하는 인터페이스다. 이 때문에 JPA 중 몇몇 기능만 사용하는 경우 좀 과하다.

  • 우리는 CrudRepository라는 인터페이스를 사용할 것이다. CRUD (Create, Read, Update, Delete)랑 관련된 method만 가진 인터페이스다. 다음과 같이 코드를 만들어보자.

import org.springframework.data.repository.CrudRepository;
import java.util.List;

public interface EmployeeRepository 
  extends CrudRepository<Employee, Long> {
    List<Employee> findAll();
}
  • 이렇게 짜면 일단 우리는 이 우리만의 RepositoryEmployeeRepository에 기본적인 CRUD operation을 하는게 가능하다. 왜냐? CrudRepository를 extend 했으니까.

  • 그러면 findAll은 뭐냐. 일단 이름을 보면 Table에 있는 모든 Employee를 구하라는 것을 유추하는게 가능하다. 그러면 정의는? 이건 SpringJPA에서 알아서 짠다. 이게 놀라온 부분이다. DAO 구현 없이 이게 가능하며, 어떻게 이름만 가지고 이를 파악하는지 궁금하면 이 글이랑 이 공식 문서를 확인해보자.

  • 여하튼 이게 Spring의 무시무시한 점 중 하나다.

Controller

  • 우리는 앞의 Employee에 대한 Table에 대한 CRUD operation 결과물을 획득할 수 있는 REST 기반 API를 만들려고 한다. 이 때 REST API를 위한 Controller을 만드는 것도 쉽다. 여기서 주로 활용하는건 Spring Web이다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class EmployeeController {
    @Autowired
    private EmployeeRepository repository;
    @GetMapping("/employees")
    public List<Employee> getEmployees() {
        return repository.findAll();
    }
    // Other CRUD endpoints handlers
}
  • 이러면 끝난다. 자세한 annotation은 다음에 알아보도록 하자.

Security

  • Employee를 추가하거나 삭제하는 operation에 대해서는 막고 싶다고 해보자. 그러면 해당 request에 대해서는 authentication이 이루어져도 관련 작업을 하지 못하도록 막아야 한다. 또 직원 정보 자체를 인증된 사람만 볼 수 있게 하고 싶다고 해보자. 이 때 Spring Security가 등장한다.

  • Baeldung의 코드는 deprecate된게 많으며, 같은 효과를 내려면 밑과 같이 작성해야 한다.

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize ->
                        authorize
                        .requestMatchers(HttpMethod.GET, "/employees", "/employees/**")
                        .permitAll()
                        .anyRequest()
                        .authenticated()
                );
        return http.build();
    }
    // other necessary beans and definitions
}
  • 구체적인 과정은 나중에 알아보도록 하자. 이 부분이 제일 복잡해보이죠... 하지만 대충 코드를 보면 알 수 있는데 GET 요청을 /employees/employees/**에 대해서 허용하고 나머지는 안되며, 저 요청마저도 인증된 경우에만 허용한다.

Testing

  • Spring의 또 다른 장점은 앞의 기능들을 간단하게 '만들기'만 지원해주는게 아니라, 그렇게 만든걸 '테스트'할 수 있는 환경도 미리 만들어준다는 것이다. 밑은 REST controller에 대한 unit test다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class EmployeeControllerTests {
    @Autowired
    private MockMvc mvc;
    @Test
    @WithMockUser()
    public void givenNoEmployee_whenCreateEmployee_thenEmployeeCreated() throws Exception {
        mvc.perform(post("/employees").content(
            new ObjectMapper().writeValueAsString(new Employee("First", "Last"))
            .with(csrf()))
          .contentType(MediaType.APPLICATION_JSON)
          .accept(MediaType.APPLICATION_JSON))
          .andExpect(MockMvcResultMatchers.status()
            .isCreated())
          .andExpect(jsonPath("$.firstName", is("First")))
          .andExpect(jsonPath("$.lastName", is("Last")));
    }
    // other tests as necessary
}

Running the Application

  • 마지막으로 Spring Application은 따로 servlet container을 형성한 다음에 거기에 배포를 하는게 아니라 임베드된 Tomcat Server에다가 실행하는 것이 가능하다. (따로 관련 확장자를 미리 넣긴 해야 한다. 어려운건 아니지만.)

  • 그러면 밑과 같은 코드로 실행이 가능하다.

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

Alternatives to Spring

  • 특정 영역에서 좀 더 우위를 가질 수 있는 프레임워크로 Guice, Play, (아까 말한 JPA 관련) Hibernate가 있고 Spring이 못하는 기능이 있는 프레임워크로 Micronaut와 Quarkus가 있다고 한다.

So, Why Choose Spring?

  • 사용감 : 개발자가 쉽게, 본인이 만들어야 하는 프로젝트와 관련된 환경설정을 하고 개발을 시작하는게 가능하다. 특히 Spring Boot가 이 장점을 더 증진시킴.

  • 모듈성 : 스프링 프레임워크 전체를 꼭 넣을 필요 없고, 필요한 모듈들만 넣어서 프로젝트 개발이 가능. 심지어 타 프레임워크랑 같이 사용하는 것도 가능

  • 다양한 표준 형태 준수 : Jakarta EE라고 들어보았는가? Java EE가 이클립스 재단에 넘어가면서 바뀐 이름이다. Java SE가 단순 프로그램 개발용 플랫폼인데, 여기에 서버 개발과 관련된 내용물을 추가한 것이 Jakarta EE다. Spring의 경우 이 Jakarta EE를 전부는 아니지만 꽤 많이 따르고 있다. 특히 기술 부분은 전부 따르고 있으며, 표준 요구사항보다 더 발전한 형태로 지원을 하는경우도 많다. 게다가 타 산업체 명시사항들도 따르는 것들이 몇개 있는 등 다양한 표준 형태를 가진다는게 장점이다.

  • 테스트 관련 : 앞에서 봤듯이 테스트를 위한 환경도 금방 설정해주기 때문에 TDD와 같은 개발 방식에 매우 최적화되어 있다. 게다가 사용하는 오브젝트 대부분이 POJO라 테스트 만드는것도 크게 꼬이기가 힘들고, 설령 시뮬레이션하기 힘든 테스트들도 mock object를 활용해서 미믹하는게 가능함.

  • 성숙도 : 엄청 오랬동안 발전한 프레임워크다. 그래서 어지간한 문제, 특히 흔히 있는 문제들은 최적으로 해결하는게 가능함. 새로운 언어 기능에 대한 지원 등의 발전 환경도 매우 잘 되어 있음

  • 커뮤니티 : 오픈 소스다. 그리고 이 덕분에 많은 개발자들이 이 프레임워크를 어떻게든 유지 보수 및 발전시키고 있음.

Reasons Not to use Spring

  • 배우는게 힘들다. 앞에 있는 예제도 매우 간단하지만 생략한 개념들이 매우 많으며 스프링에서 추구하는 디자인 철학까지 공부해야 하다보니 매우 빡세다. 구체적인 아키텍처까지 공부한다면? 어휴. 근데 순수하게 구현하는것도 빡세서 별 상관이 없는것 같다.

  • 어쨌든 추가 자원을 소모하는 것이라 최적화가 매우 중요한 프로젝트에서는 기피된다.

profile
안 흔하고 싶은 개발자. 관심 분야 : 임베디드/컴퓨터 시스템 및 아키텍처/웹/AI

0개의 댓글