[배워서 바로 쓰는 스프링 부트2] 1.3 예제 프로젝트의 구성
1.3 예제 프로젝트의 구성
한빛미디어 예제 다운로드: https://www.hanbit.co.kr/lib/examFileDown.php?hed_idx=4599
원본 저자 GitHub: https://github.com/miyabayt/spring-boot-doma2-sample
spring-boot-doma2-sample
├── build.gradle - 부모 프로젝트의 빌드 스크립트
├── sample-common - 공통으로 사용하는 유틸리티를 관리하는 모듈
├── sample-domain - 도메인 객체를 관리하는 모듈
├── sample-web-base - 웹 모듈의 공통 기능을 관리하는 모듈
├── sample-web-front - 최종 사용자용 웹 애플리케이션
├── sample-web-admin - 운영자용 웹 애플리케이션
└── sample-batch - 정기 실행 배치를 관리하는 모듈
멀티 프로젝트
멀티 프로젝트 구성 장점
- 빌드 스크립트를 공통화할 수 있어 작성량이 줄어든다
- 로컬/원격 저장소에 아티팩트를 업로드하지 않고도 소스 코드의 변경이 반영된다
- 각각의 프로젝트를 관련지어 작업을 실행할 수 있다
애플리케이션 아키텍처
프레젠테이션 층
입력된 값을 받아 값을 확인하거나 값의 변환을 실시하는 층
- 웹 모듈의 Form 클래스
- FormValidator 클래스
애플리케이션 층
프레젠테이션 층에서 받은 값을 도메인 층에 전달하는 층
비즈니스 로직은 포함하지 않지만 화면 전환을 제어하거나 세션을 사용하여 다음 화면에 값을 전달한다
- 웹 모듈의 Controller
도메인 층
도메인 객체를 가지고 비즈니스 로직을 처리하는 메인층
도메인 객체는 모든 계층에서 사용되지만 반대로 도메인 층은 다른 계층에 의존해서는 안됨
- 도메인 모듈의 Service
인프라스트럭처 층
도메인 계층에서 전달된 데이터를 영속화하는 층
애플리케이션 계층의 영향을 받지 않도록 범용적인 부품으로 만들어야 함
- 도메인 모듈의 Repository 클래스
예제 프로젝트의 빌드 스크립트
모듈의 의존관계**
웹 모듈 -> 도메인 모듈 -> 공통 모듈
- 스프링 부트 버전을 확장 속성으로 설정한다
- 테스트 코드는 Spock 프레임워크(Groovy 언어로 작성)를 이요하므로 그루비를 사용할 수 있도록 설정
- 모든 서브 프로젝트에서
spring-boot-gradle-plugin
을 사용하도록 설정 - 모든 서브 프로젝트에서
dependency-management
플러그인을 사용하도록 설정 - 자바 컴파일러 준수 레벨을 11로 변경
src/main/resources
를 클래스 경로에 추가하여 개발 중 변경 내용이 즉시 반영되도록 설정- 리소스 파일의 출력 위치를 소스 파일의 출력 위치로 변경
- 컴파일 전에 리소스 파일의 출력을 실시
- 도마의 의존관계에 버전 차이가 있는 스프링 부트를 제외
- Spring Session 모듈을 사용하여 세션 정보를 데이터베이스에 저장
- Gradle의 프로젝트 속성을 bootRun의 인수에 전달
buildscript { ext { springBootVersion = "2.1.3.RELEASE" // 1. 스프링 부트 버전을 확장 속성으로 설정한다 spockVersion = "1.2-groovy-2.5" groovyVersion = "2.5.3" lombokVersion = "1.18.2" dockerComposePluginVersion = "0.6.6" } repositories { mavenLocal() mavenCentral() jcenter() } dependencies { // 1. 스프링 부트 버전을 확장 속성으로 설정한다 classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" } }
subprojects {
apply plugin: "java"
// 2. 테스트 코드는 Spock 프레임워크(Groovy 언어로 작성)를 이요하므로 그루비를 사용할 수 있도록 설정
apply plugin: "groovy"
apply plugin: "idea"
apply plugin: "eclipse"
// 3. 모든 서브 프로젝트에서 spring-boot-gradle-plugin
을 사용하도록 설정
apply plugin: "org.springframework.boot"
// 4. 모든 서브 프로젝트에서 dependency-management
플러그인을 사용하도록 설정
apply plugin: "io.spring.dependency-management"
sourceCompatibility = 11 // 5. 자바 컴파일러 준수 레벨을 11로 변경
targetCompatibility = 11
[compileJava, compileTestJava, compileGroovy, compileTestGroovy].options.encoding = "UTF-8"
sourceSets {
test.resources {
// 테스트 시 src/main/resources에 있는 설정 파일을 사용
srcDirs "src/main/resources"
srcDirs "src/test/resources"
}
}
repositories {
mavenCentral()
jcenter()
// jasperreports
maven { url "http://jasperreports.sourceforge.net/maven2/" }
maven { url "http://jaspersoft.artifactoryonline.com/jaspersoft/third-party-ce-artifacts/" }
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
inheritOutputDirs = false
outputDir = compileJava.destinationDir
testOutputDir = compileTestJava.destinationDir
}
}
eclipse {
classpath {
containers.remove("org.eclipse.jdt.launching.JRE_CONTAINER")
containers "org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"
}
}
bootRun {
// 6. `src/main/resources` 를 클래스 경로에 추가하여 개발 중 변경 내용이 즉시 반영되도록 설정
sourceResources sourceSets.main
jvmArgs "-XX:TieredStopAtLevel=1", "-Xverify:none"
}
ext["groovy.version"] = groovyVersion
dependencies {
compileOnly "org.projectlombok:lombok:${lombokVersion}"
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
testCompile "org.assertj:assertj-core"
testCompile "org.spockframework:spock-core:${spockVersion}"
testCompile "org.mockito:mockito-core"
}
}
task startFakeSmtpServer(type: JavaExec) {
classpath = buildscript.configurations.classpath
args = ["--start-server", "-m"] // -m (memory-mode)
main = "com.nilhcem.fakesmtp.FakeSMTP"
}
apply plugin: "docker-compose"
dockerCompose {
useComposeFiles = ["docker/docker-compose.yml"]
}
project(":sample-common") {
bootJar {
enabled = false
}
jar {
enabled = true
}
dependencies {
// springframework
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
compile "org.springframework.boot:spring-boot-starter"
compile "org.apache.commons:commons-lang3"
compile "org.apache.commons:commons-text:1.4"
compile "org.apache.commons:commons-compress:1.14"
compile "commons-codec:commons-codec"
compile "org.apache.commons:commons-digester3:3.2"
compile "commons-io:commons-io:2.5"
compile "org.apache.tika:tika-core:1.15"
compile "dom4j:dom4j"
compile "com.ibm.icu:icu4j:59.1"
}
}
project(":sample-domain") {
bootJar {
enabled = false
}
jar {
enabled = true
}
// 도마(Doma) 2를 위한 코드
// Java클래스와 SQL파일의 출력 디렉터리를 동일하게 한다
// 7. 리소스 파일의 출력 위치를 소스 파일의 출력 위치로 변경
processResources.destinationDir = compileJava.destinationDir
// 컴파일하기 전에 SQL파일을 출력 디렉터리에 복사하기 때문에 의존관계를 역전한다
// 8. 컴파일 전에 리소스 파일의 출력을 실시
compileJava.dependsOn processResources
dependencies {
compile project(":sample-common")
// springframework
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-validation"
compile "org.springframework.boot:spring-boot-starter-mail"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-jdbc"
compile "org.springframework.boot:spring-boot-starter-json"
// doma exclude springframework
annotationProcessor "org.seasar.doma.boot:doma-spring-boot-starter:1.1.1"
compile("org.seasar.doma.boot:doma-spring-boot-starter:1.1.1") {
// 9. 도마의 의존관계에 버전 차이가 있는 스프링 부트를 제외
exclude group: "org.springframework.boot"
}
// jackson
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-csv"
// modelmapper
compile "org.modelmapper:modelmapper:0.7.5"
// thymeleaf
compile "org.codehaus.groovy:groovy:${groovyVersion}"
compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.3.0") {
exclude group: "org.codehaus.groovy", module: "groovy"
}
// mysql database
compile "mysql:mysql-connector-java"
compile "org.flywaydb:flyway-core"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.spockframework:spock-spring:${spockVersion}"
}
}
project(":sample-web-base") {
bootJar {
enabled = false
}
jar {
enabled = true
}
dependencies {
compile project(":sample-domain")
// springframework
compile "org.springframework.boot:spring-boot-starter-cache"
compile("org.springframework.boot:spring-boot-starter-web") {
// Jetty를 사용하기 위해 내장된 톰캣 제외
exclude module: "spring-boot-starter-tomcat"
}
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-starter-jetty"
// 세션 보관 위치로 DB를 사용할 경우
// 10. Spring Session 모듈을 사용하여 세션 정보를 데이터베이스에 저장
compile "org.springframework.session:spring-session-jdbc"
// 세션 보관 위치로 redis를 사용할 경우
//compile "org.springframework.boot:spring-boot-starter-data-redis"
// thymeleaf
compile "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
// jasperreports
compile "net.sf.jasperreports:jasperreports:6.4.0"
compile "com.lowagie:itext:2.1.7.js5"
// apache POI
compile "org.apache.poi:poi:3.16"
compile "org.apache.poi:poi-ooxml:3.16"
// EhCache
compile "net.sf.ehcache:ehcache"
// webjars
compile "org.webjars:webjars-locator-core"
compile "org.webjars:bootstrap:3.3.7"
compile "org.webjars:jquery:2.2.4"
compile "org.webjars:jquery-validation:1.17.0"
compile "org.webjars:bootstrap-datepicker:1.7.1"
compile("org.webjars.bower:iCheck:1.0.2") {
exclude module: "jquery"
}
compile "org.webjars:html5shiv:3.7.3"
compile "org.webjars:respond:1.4.2"
compile "org.webjars:AdminLTE:2.3.8"
compile "org.webjars:font-awesome:4.7.0"
compile "org.webjars:ionicons:2.0.1"
testCompile "org.springframework.security:spring-security-test"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.spockframework:spock-spring:${spockVersion}"
}
}
project(":sample-web-front") {
bootJar {
launchScript()
}
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
}
dependencies {
compile project(":sample-web-base")
developmentOnly "org.springframework.boot:spring-boot-devtools"
testCompile "org.springframework.security:spring-security-test"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.spockframework:spock-spring:${spockVersion}"
}
}
project(":sample-batch") {
bootRun {
// 프로젝트 프로퍼티를 인수로 건넨다
// 11. Gradle의 프로젝트 속성을 bootRun의 인수에 전달
if (project.hasProperty("args")) {
args project.args.split("\s+")
}
}
dependencies {
compile project(":sample-domain")
// springframework
compile "org.springframework.boot:spring-boot-starter-batch"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "org.springframework.batch:spring-batch-test"
testCompile "org.spockframework:spock-spring:${spockVersion}"
}
}
task wrapper(type: Wrapper) {
gradleVersion = "4.10.2"
}
##### 예제 프로젝트 설정(`setting.gradle`)
```groovy
include "sample-common", "sample-domain", "sample-web-base", "sample-web-front", "sample-web-admin", "sample-batch"
메인 애플리케이션 클래스가 없는 모듈의 경우(build.gradle
)
bootJar
작업(실행 가능한 JAR
파일 작성 작업)을 실행할 때 오류가 발생 아래와 같이 설정한다
bootJar {
enabled = false
}
롬복 이용하기
https://projectlombok.org/features/all
Lombok
을 이용하여 boilerplate code의 절감을 도모
애플리케이션 실행시에는 필요하지 않기 때문에 Scope는 compileOnly
의존관게에 롬복 추가하기(build.gradle
)
dependencies {
compileOnly "org.projectlombok:lombok:${lombokVersion}"
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
}
Getter와 Setter 구현 배제 하기
@Getter
와 @Setter
애너테이션을 작성하면 애너테이션 프로세서에 의해 Getter와 Setter가 자동 생성 됨
@Getter
와 @Setter
애너테이션 부여하기
@Getter
@Setter
public class Person {
String name;
}
자동 생성된 Getter와 Setter
public class Persion {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
변수 타입을 val
로 통일하기
Lombok
에서는 로컬 변수 타입을 val
로 설정할 수 있음
긴 타입명도 val
로 통일할 수 있으며 자동 생성되는 타입에는 final
이 부여됨
자바 10부터 로컬 변수 타입 추론 var
를 사용할 수 있음var
로 정의된 변수는 final
로 한정되지 않음
변수 타입을 자동으로 생성하기
public String valExample() {
val example = new ArrayList<String>();
example.add("Hello, World!");
val foo = example.get(0);
return foo.toLowerCase();
}