Skip to content

Instantly share code, notes, and snippets.

@ihoneymon
Created August 29, 2018 00:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ihoneymon/d87396993d2acf6766fe219178f0099c to your computer and use it in GitHub Desktop.
Save ihoneymon/d87396993d2acf6766fe219178f0099c to your computer and use it in GitHub Desktop.

20180827 Boot Spring Boot! 북콘서트

북콘서트에서 하고 싶은 이야기 요약.

스프링 부트가 출시된지 어언 5년, 아직도 스프링 부트를 낯설어하는 개발자들을 위해서 스프링 부트 개발시 'Boot Spring Boot' 책 활용방법을 소개합니다. 이 책은 부제와 같이 '스프링 부트 2.0 참고서’입니다. 스프링 부트를 기반으로 프로젝트를 시작하고, 기능을 추가하고, 애플리케이션 배포하는 과정까지 고려사항과 이를 어떻게 프로젝트에 반영할 수 있는지 풀어냅니다. 프로젝트를 시작하면서 배포하고 운영하기까지 옆에 두고 가끔씩 펼쳐볼 수 있는 책이 되길 바랍니다. 처음에는 여느 책과 마찬가지로 하나의 스토리 라인을 가지고 프로젝트를 빌드하고 기능을 구현하면서 보여드릴까 하는 생각도 했지만 그렇게 되면 스프링 부트를 소개하기에는 많이 부족하다는 생각을 했습니다. 그러다가 문득 어렸을 적에 보던 '백과(혹은 전과)'책을 떠올려봤습니다. 그 당시 전과에는 교과서 전과목에 대해 다루어주었습니다. 저는 이런 전과 보는 것을 좋아했습니다. 스프링 부트에 대해서 이 전과처럼 쓸 수 있는 방법이 뭐가 있을까를 고민하다가 스프링 부트 레퍼런스 가이드를 떠올렸습니다. 스프링 부트 기반으로 개발하는데 있어서 이만한 것이 없습니다. 스프링 부트의 작동원리부터 시작해서 권장사용방법과 배포 방법에 이르기까지 정말 방대한 내용을 다루고 있습니다. 영어로 작성되어 있어 부담스러워하는 분들이 있는데 사실 구글신의 도움을 얻으면 접근하기가 쉬워집니다. 어쨌든 이 책은 '스프링 부트 참고서’의 목적으로 만들어졌습니다.

이 콘서트를 통해 '허니몬이 쓰는 방식도 나랑 크게 다르지 않네’하며 안도할 수 있을 겁니다.

스프링 부트

'Boot Spring Boot’가 나온지 벌써 한달이 됐습니다. 해외송금 서비스를 제공하던 회사를 그만두고 보름동안 놀고 와서 다시 해외송금 서비스를 하는 회사에서 1.5개월을 일하다가 파업해서 구직활동을 하다가 '우아한형제들’에 입사하고 수습과정을 무사히 마치고 정규직으로 전환을 마쳤습니다. 8월 초 이 책이 나오고 조금씩 발표준비를 하며 이 자리에 섰습니다. 스프링 부트가 나온지 벌써 5년! 스프링 부트와 관련된 많은 강연이 있었고, 많은 (경쟁) 책이 나왔습니다. 그런데…​ 아직까지 스프링 부트를 사용해본 적 없거나 그 존재를 무르는 분도 존재 합니다. 신기한 일이죠.

개발자인생은 B - C - D 다.

인생은 BCD이다

— 프랑스 철학자 '장 폴 사르트르''

라고 이야기 했습니다. 사람은 태어나서(Birth) - 죽기(Death)까지 그 사이에서 수많은 선택(Choice)을 합니다.

개발자인생은 BCD이다.

라고 생각합니다. 구축(Build) 하고 배포(Deploy)하기까지 그 사이에 수많은 코드(Code)를 구현해야 합니다.

스프링 부트 시작하기

스프링 부트는 개발자 인생의 BCD에서 코드에만 집중할 수 있게 해줍니다.

그렇다고 B, D는 신경쓰지 않아도 된다는 이야기는 아닙니다. 신경을 써야 합니다. 많이.

이후로 운영 중에 발생하는 문제를 해결하기 위해 많은 시간을 유지보수하는 것들은 차치해두고 말이죠.

스프링 부트 시작(Into the Spring Boot)

Note
스프링 부트 개요
  • Build

  • Code

    • spring-boot-starter

    • auto-configuration

    • Coding in Spring Environment

  • DDeploy

Build

Spring Initializer(https://start.spring.io/)를 통해서 스프링 부트 프로젝트를 만들게 됩니다.

그 과정에서 우리는 선택해야 합니다:

  • 어떤 언어를 사용할 것인가? Java vs Kotlin

  • 어떤 빌드도구를 사용할 것인가? Gradle vs Maven

    • Gradle .build.gradle

description = """
Project name: ${project.name}

'부트 스프링 부트' 도서를 보며 참고할 수 있는 예제 프로젝트입니다.
"""

buildscript { // 그레이들 바이너리 플러그인 의존성 정의
    ext {
        springBootVersion = '2.0.3.RELEASE'
        gradleGitPropertiesVersion = '1.4.17'
    }
    repositories {
        mavenCentral() // 메이븐 중앙저장소
        maven { url "https://plugins.gradle.org/m2" } // 그레이들 플러그인 저장소
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") // 스프링 부트 그레이들 플러그인 정의
        classpath("gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:${gradleGitPropertiesVersion}")
        // 깃정보속성 추출 플러그인 정의
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management' // 스프링 부트 의존성 관리 플러그인: https://github.com/spring-gradle-plugins/dependency-management-plugin
apply plugin: 'com.gorylenko.gradle-git-properties' // https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties

version = '1.0.0.RELEASE'

jar {
    baseName = "${project.name}"
    version = "${project.version}"
}

springBoot {
    /**
     * Spring Boot 리패키징 시 build-info에 추가될 속성
     */
    buildInfo {
        properties {
            additionalProperties = [
                    'written-by': 'honeymon'
            ]
        }
    }
}

bootJar {
    launchScript() // 쉘스크립트처럼 실행가능한 바이너리 jar 만들기: 리눅스, 유닉스 계열에 배포 운영시 유용
    manifest {
        attributes("Implementation-Title": "${project.name}",
                "Implementation-Version": "${project.version}")
    }
}


gitProperties {
    dateFormat = "yyyy-MM-dd'T'HH:mmZ"
    dateFormatTimeZone = "GMT"
}

group = "io.honeymon.boot"
version = "${jar.version}"

sourceCompatibility = 1.8

// 소스 인코딩 지정방법 1
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

// dependencies::start[]

//exclude common-logging
[configurations.runtime, configurations.default]*.exclude(module: 'commons-logging')

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}

configurations {
    compile.exclude module: 'tomcat-jdbc'
}

dependencyManagement {
//    imports {
//        mavenBom 'org.springframework.boot:spring-boot-starter-parent:2.0.1.RELEASE'
//    }
}

dependencies {
    compile('org.projectlombok:lombok:1.18.2')

    /**
     * 웹 애플리케이션에 필요한 의존성 포함
     * spring-web, spring-webmvc, tomcat, jackson(json 마샬링, 언마샬링), hibernate-validation
     * @see <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-starters/spring-boot-starter-web/pom.xml">spring-boot-starter-web</a>
     */
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')

    compile('com.h2database:h2')

    testCompile('org.springframework.boot:spring-boot-starter-test')
}
// dependencies::end[]
  • Maven .pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<!-- 애플리케이션 정보 메타 정의:: 시작 -->
	<groupId>io.honeymon.springboot.boot</groupId>
	<artifactId>boot-spring-boot</artifactId> <!-- -->
	<version>1.0.0.RELEASE</version>
	<packaging>jar</packaging>

	<name>boot-spring-boot</name>
	<description>Boot Spring Boot Project</description>
	<url>https://github.com/ihoneymon/boot-spring-boot</url>
	<organization>
		<name>honeymon.io</name>
		<url>http://honeymon.io</url>
	</organization>
	<!-- 애플리케이션 정보 메타 정의:: 끝 -->

	<!-- 스프링 부트 정의: 끝 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<!-- 스프링 부트 정의:: 끝 -->

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
            <version>1.18.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>build-info</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
	</build>

</project>
  • 어떤 패키징 방식을 선택할 것인가? JAR vs [red]#WAR

    Note

    스프링 부트는 기본적으로 실행가능한 JAR(Executable Jar) 방식으로 애플리케이션을 패키징한다. 이를 통해 어느 실행환경에서든지 실행할 수 있는 유연성을 제공한다. 실행가능한이란 패키징을 하는 과정에서 애플리케이션과 관련 라이브러리를 압축하는 1차 패키징 이후 내장컨테이너(EmbeddedContainer)를 포함시키는 2차 패키징(bootRepackage)을 거치게 된다. 이를 통해 패키징된 배포본은 자바애플리케이션으로 실행되어 자체적으로 웹서버 기능도 수행할 수 있게 된다.

    • Jar: 권장

    • War:

Code

Starter: spring-boot-starter

스프링 부트는 spring-boot-parent에서 스프링 부트에서 사용하는 서드파티 라이브러리 버전을 관리하는 spring-boot-dependencies를 이용한다. 이 모듈덕분에 스프링 부트에서 추가하는 모듈에 버전을 명시하지 않아도 사전에 정의된 버전으로 대체가 가능하다.

스프링 부트에서 공식적으로 제공하는 스타터는 spring-boot-starter-~로 시작한다. 그 기본이 되는 spring-boot-starterspring-boot-autoconfigurespring-boot-dependencies를 가지고 있다.

spring-boot-autoconfigure는 다음에서 설명하겠지만, 스프링 부트 개발팀에서 관례적으로 작성한 구성과 빈을 포함하고 있다. spring-boot-dependencies는 스프링 부트에서 지원하는 라이브러리 들에 대한 버전을 정의하고 있습니다. spring-boot-parent에서 spring-boot-dependencies를 참조하고 spring-boot-starters 에서 spring-boot-parent의 하위로 선언되어 있다.

자동구성: auto-configuration

스프링 부트의 spring-boot-autoconfigure 모듈을 살펴보면 @Configuration@ConditionalOn~이 조합을 이뤄 ~AutoConfiguration이라는 이름을 가지고 있는 클래스들이 각 패키지 모듈별로 작성되어 있다. 이 자동구성 클래스는 앞서 설명한 spring-boot-starter 모듈 혹은 특정 기능을 가진 라이브러리를 프로젝트에 추가하고 클래스패스에서 접근이 가능해지면 @ConditionalOn 조건에 따라 활성화 여부가 결정된다.

대표적인 예로 WebMvcAutoConfiguration를 열어보자. .WebMvcAutoConfiguration

@Configuration	// 쿠성클래스 라는 선언
@ConditionalOnWebApplication(type = Type.SERVLET) // 웹애플리케이션이 서블릿타입인 경우
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) // 3개의 클래스가 클래스패스 상에 존재할 때
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // ``WebMvcConfigurationSupport`` 빈을 찾을 수 없을 때
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)	// 자동구성 우선순위는 상위 10위권
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		ValidationAutoConfiguration.class }) // 2개 자동구성 클래스가 활성화된 후에 구성을 시작한다.
public class WebMvcAutoConfiguration {
	//생략

이와 유사한 구조를 가진 클래스들이 스프링 부트 팀에서 작성한 자동구성 클래스다.

Coding in Spring Environment

여기서부터 개발자가 스프링 부트가 구성한 개발환경에서 업무에 필요한 기능을 구현하게 된다. 이 때 우리는 스프링에서 제공하는 다양한 컴포넌트 타입(@Component, @Repository, @Service, @Controller 등)을 선언한 컴포넌트를 작성하고 이를 주입(@Autowired)를 이용해서 주입받아 재사용성을 높이면서 코드를 작성한다.

자신이 작성하는 코드를 누군가와 함께 보고 개선해나가는 경험을 지속적으로 느낄 수 있기를 바란다.

BOM(Bill of Material)

  • 스프링 부트 버전별 검증을 마친 서드파티 라이브러리 버전 명시

  • 스프링 부트를 사용하면 스프링 부트 버전만 신경쓰면 된다.

    • 서드파티 라이브러리 버전에 대해서는 크게 신경스지 않아도 된다.

  • build.gradle 혹은 pom.xml 내에서 서드파티 라이브러리 버전을 명시하지 않아도 된다.

CDD(Convention Driven Development): 관례주도개발

  • 스프링 부트팀에서 작성한 자동구성 모음 spring-boot-autocofigure

    • 스프링 개발환경에서 이용하는 기술 흐름에 맞춰서 스프링 부트 개발팀에서 작성

    • 관례에 따라 작성

      • 사용포트, 기본을 따르는 구성

  • RDD(Reference Driven Development): 참조주도개발

  • 내가 기존에 작성한 프로젝트를 보고개발한다.

애플리케이션 속성정의

애플리케이션 내에서 사용할 컴포넌트를 구성해야고, 컴포넌트를 구성하기 위해서 이런저런 속성을 설정한다. 스프링 부트가 제공하는 애플리케이션 속성을 정의하는 3가지 방법이 있다.

  • 코드로 명시적으로 작성하는 방법

  • 애플리케이션 속성파일 정의

  • 애플리케이션 속성을 외부에서 구성하는 방법

    • 환경변수로 지정하는 방법

    • 실행인자로 지정하는 방법

스프링 부트 구성속성(@ConfigurationProperties)
Note

스프링 부트 자동구성은 사용한 구성속성(@ConfigurationProperties) 클래스를 JSON 메타데이터로 추출하여 제공합니다.

개발툴(IDE)에서 @ConfigurationProperties를 사용하면 그 속성을 자동완성으로 사용할 수 있도록 spring-boot-configuration-processor 의존성을 선언하길 권장합니다. spring-boot-configuration-processor를 선언하면 @ConfigurationProperties을 선언한 클래스를 탐색하여 META-INF/spring-configuration-metadata.json를 생성합니다. 혹은 보다 상세한 내용을 기술하기 위해서 개발자가 직접 작성할 수도 있습니다.

보다 자세한 내용은 스프링 부트#Appendix B. Configuration Metadata를 살펴보라.

프로파일(@Profile)

애플리케이션 속성과 관련된 프로파일은 default(spring.profiles.active 정의하지 않았을 때 적용되는 프로파일)와 사용지정 프로파일(spring.profiles.active을 정의한 프로파일)으로 구분 지을 수 있다.

  • 애플리케이션 속성키 spring.profiles 속성과 관련이 있음

    • 관련클래스: org.springframework.boot.context.config.ConfigFileApplicationListener

      Note

      애플리케이션이 시작될때 애플리케이션 구성파일을 읽어서 속성을 적재하는 과정을 수행한다. 이 과정에서 애플리케이션에 수집된 Environment spring.profiles.activespring.profiles.include를 읽어들여 프로파일을 구성한다.

이 프로파일을 어떻게 조합하여 활성화느냐에 따라서 애플리케이션 동작이 달라질 수 있다.

Note

(허니몬) 실행환경은 크게 5가지 프로파일로 구분된다:

  • local: 애플리케이션을 빠르게 실행해볼 수 있는 내가 개발하는 환경

  • develop: 운영과 유사한 환경을 가지며 수시로 배포하여 애플리케이션 기능 확인

  • beta: 운영 환경과 같은 데이터를 가지며 품질검사를 목적으로 여러 사람이 함께 보는 환경

  • product: 운영 환경

  • test: 단위 테스트, 통합테스트 등을 목적으로 하는 실행환경

application-db.yml // DB 연결정보
application-internal-api-client.yml //
application-external-api-client.yml //
application.yml
#default: common
spring.profiles.include:
  - db
	- internal-api-client
	- external-api-client

---
spring.profiles: dev
# dev 프로파일 속성정의

---
spring.profiles: beta
# beta 프로파일 속성정의

---
spring.profiles: product
# product 프로파일 속성정의
JavaConfig(With Profile)

자바로 구성을 정의하는 것은 하드코딩에 가깝다. 물론 @ConfigurationProperties@Value를 조합하여 애플리케이션 Environment에 정의한 값을 주입받아 변경하는 것도 가능하지만 상당수는 문자열이나 enum 등의 상수값을 이용해서 정의하는 경우가 많기 때문에 가급적이면 코드에서 상수로 정의하는 것은 권장하지 않는다.

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

		SpringApplication app = new SpringApplication(BootSpringBootApplication.class);
		app.addListeners(new ApplicationPidFileWriter());
		//app.setWebEnvironment(false);	// 2.0 에서 Deprecated 됨
		app.setWebApplicationType(WebApplicationType.SERVLET);
		app.run(args);
	}
}
Note

실행환경(프로파일)에 따라서 변경될 수 있는 값에 대해서는 하드코딩(정적코드)으로 지정하지 않는다.

내부: application.yml or appliation.properties

  • 관례적으로 /src/main/resources에 생성하는 애플리케이션 속성파일

  • .properties 일 때는

  • .yml 일 때는

//TODO PID 생성 설정
//TODO webApplicationType 선언
외부: 외부구성(External Configuration)

애플리케이션 12요소 중 구성(Config) 관련 내용을 살펴보면, 12요소에서는 구성값(외부인증정보, API 호스트 이름 등)을 코드에서 분리하도록 엄격하게 요구합니다. 설정은 실행환경에 따라 달라지지만 코드는 그렇지 않은 경우가 많습니다.

애플리케이션의 모든 속성이 코드와 분리되었는지 테스트할 수 있는 간단한 방법은 지금 당장 소스를 공개해도 어떠한 인증정보도 유출시키지 않을 수 있는지 확인하는 것이다.

실행인자로 애플리케이션 속성값을 전달하는 경우
// dld
$ java -jar boot-spring-boot.1.0.0.RELEASE.jar --spring.profiles.active=product --spring.database.url=~~~ --spring.database.username=

Deploy

코드를 작성하여 구현한 서비스 애플리케이션을 실제로 동작하도록 만드는 행위다. 이 배포하는 절차는 기업에 따라서 조금씩 다르지만 대략적인 절차는 다음과 같다.

  1. 기능을 정의하고 이를 관리하기 위한 이슈 발급

  2. 기능(feature)개발 하고 리뷰 받고 OK하면 develop 브랜치에 푸시

  3. 개발서버에 배포

  4. 개발서버에서 개발된 기능확인

  5. 개발서버에서 확인을 마치면

  6. 베타서버 배포

  7. 배타서버에서 기능관련자(기획자)의 기능확인

  8. develop 브랜치 코드를 master에 머지하고 푸시

  9. 운영서버 배포

  10. 운영서버 적용(최종점검…​)

'스프링 부트를 이용해서 개발한다' 는 이야기에 주절거림

커뮤니티나 SNS를 살펴보다보면 스프링 부트로 만들었다고 이야기하며 자신의 문제점을 해결해주길 바라는 분들이 많습니다. 그런 분들이 간과하는 것 중 하나가 사용하고 있는 스프링 부트 버전이다. 스프링 부트 버전에 따라서 사용하는 서드파티 라이브러리의 버전도 달라진다. 개발 중에 질문을 할 때는 사용하는 스프링 부트의 버전을 거론하면 조금 더 빠른 답변을 얻을 수 있다.

내 개발이야기

Note

조금 더 정리가 필요하다.

스프링 부트 변화

Important

스프링 부트 v1.5는 2019-08-01 지원이 중단된다. 새로 시작하는 프로젝트라면 Java 8 이상 스프링 부트 2.0 부터 시작하자.

Spring Boot v1.5 v.2.0

Java Version

Java 8 까지

Java 8 부터

Spring Version

v4.3 까지

v5.0 부터

Spring Data

v1.X.X

v2.0.0.RELEASE

Spring Security

v4.X.X.RELEASE

v5.0.0.RELEASE

Actuator

/{endpoint}

/actuator/{endpoint}

Java 8부터 사용할 수 있도록 변경되면서 스프링 프레임워크에서 제공하는 인터페이스에도 많은 변화가 생긴다. 가장 큰 변화 중 하나는 default 메서드를 적극적으로 활용하여 어댑터 역할을 하던 추상클래스들이 Deprecated 선언이 되고 최종적으로는 삭제가 된다. 그리고 스프링 데이터 하위 프로젝트에서는 CrudRepository의 변화와 같이 반환형태가 Optional<T> 형식으로 변경되어 객체의 null(비어있는 상태)에 대한 체크로직을 조금 더 안정적으로 할 수 있게 된다.

정리

  • 스프링 부트는 지속적으로 업그레이드가 되고 새로운 기능을 제공할 것이다. 그러니 공부를 멈추지 말자.

  • 프로젝트를 시작할 떄는 스프링 부트 2.x 부터 사용하길 고려하자.

    • Java 8 이상

    • 스프링 5 이상

    • 스프링 Data JPA 2.0 이상

    • 스프링 시큐리티 5 이상

  • 스프링 부트를 이용하면 MSA(MicroService Architect)를 구현하는데 용이하다.

    • 내장 컨테이너를 포함한 자기완비상태로 배포 가능

    • 운영에 필요한 기능 제공(액츄에이터, Actuator)

궁금하신 점이 있을 경우에는

궁금할 때는 바로바로 물어봐주세요.

참조

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment