SettingsDelegate.class
프로젝트의 전체적인 설정을 책임지는 클래스. 내부에 getSettings, include 와 같은 전체적인 프로젝트 구조에 도움을 주는 메서드가 내재되어있다. 유사품으로 ProjectDelegate.class 가 있다.
ResolutionsStrategy
Defines the strategies around dependency resolution. For example, forcing certain dependency versions, subsitutions, conflict resolutions or snapshot timeouts.
eachPlugin , eachDependency 같은 메서드로 특정 모듈의 버전을 고정할 수 있다. 살짝 package.json 에서 버전관리와 흡사한 느낌. 참고로 include 를 사용하여 멀티프로젝트를 구현할 수 있다.
Maven Central
Apache Maven 프로젝트에서 사용하는 중앙 저장소 중 하나. 소프트웨어 프로젝트를 관리하고 빌드하기 위한 도구이며, 종속성 관리를 통해 프로젝트에 필요한 라이브러리 및 패키지를 쉽게 관리할 수 있다. 프로젝트가 필요로 하는 라이브러리가 Maven Central 에 호스팅되어 있다면, Maven 은 해당 라이브러리를 검색하고 자동으로 다운로드하여 프로젝트 빌드에 포함시킨다.
보통 Maven 프로젝트의 'pom.xml' 파일에 의존성을 선언하면 Maven 은 Maven Central 과 같은 중앙 저장소에서 해당 라이브러리를 찾아내어 다운로드 한다. 이는 node 환경에서의 npm 패키지 관리 시스템과 비슷한 목적을 갖고 있기에, package.json 에 존재하는 라이브러리를 npm i 해주는 것과 동일한 동작을 수행한다.
npm i 과 마찬가지로 Maven 으로 모듈을 다운로드 받기 위해선 명령어가 필요하다. 그 명령어를 실행해주는 곳이 바로 gradlew 다. 리눅스 언어로 설정되어 있으니 필요한 명령어가 있다면 찾아서 넣어주도록 하자.
git 에서 gradlew 실행권한 주는거 까먹지 말자.
MavenLocal
npm 으로 치면 node_modules 디렉토리 같은 존재다. Maven 빌드 도구로부터 다운로드 종속성을 저장한다(캐시). 해당저장소는 Maven 프로젝트가 빌드될 때 사용되며, 로컬 머신에 캐시된 라이브러리를 저장하는 역할을 한다. 이러한 로컬 저장소들은 네트워크 대역폭을 절약하고 빌드 시간을 단축하는 데 도움을 준다.
Repositories / Dependencies
Repositories 는 프로젝트가 의존하는 외부 라이브러리를 어디에서 찾을지를 정의한다. 프로젝트가 사용하는 라이브러리가 어떤 원격 저장소 또는 로컬 저장소에서 다운로드되어야 하는지를 설정한다.
Dependencies 는 프로젝트가 의존하는 외부 라이브러리들의 목록을 정의한다. 해당 섹션에 선언된 라이브러리들은 빌드 도구가 해당 라이브러리들을 자동으로 다운로드하고 추가하게 된다. 이는 package.json 의 dependencies 와 동일하다.
Build Lifecycle
- 초기화 (Initialization) - 빌드 대상 프로젝트를 결정하고, 각각에 대한 Project 객체를 생성, settings.gradle 파일에서 프로젝트 구성 (멀티프로젝트 / 싱글프로젝트 구분)
- 구성 (Configuration) - 빌드 대상이 되는 모든 프로젝트의 빌드 스크립트를 실행 (프로젝트 객체 구성), configured Task 실행
- 실행 (Execution) - 구성 단계에서 생성하고 설정된 프로젝트의 테스트 중에 실행 대상 결정, gradle 명령행에서 지정한 태스크 이름 인지와 현재 디렉토리를 기반으로 태스크를 결정하여 선택된 태스크들을 실행
java.sourceCompatibility
java.sourceCompatibility, java.targetCompatibility 는 스프링 프로젝트에서의 컴파일 설정을 가리키는 속성이다. 컴파일러에게 사용할 자바 소스 코드의 버전을 명시적으로 지정하는 데 사용된다. 코틀린 스프링을 사용한다 해도, 코틀린 소스 코드가 특정 JVM버전과 호환되도록 해줘야 한다.
allOpen
코틀린에서 사용되는 특별한 플러그인이다. 주로 스프링과 함께 사용된다. 해당 플러그인은 @JvmOverloads, @JvmField 등과 같이 자바 코드와의 상호 운용성을 강화하기 위해 만들어졌다.
allopen 플러그인을 사용하면 코틀린의 open 키워드가 붙은 클래스들이나 메서드들에 자동으로 open 변경자가 추가된다. 스프링과 같은 프레임워크는 기본적으로 클래스를 상속하거나 프록시화 할 수 있는 open 클래스들을 필요로 하는데, 코틀린에서는 이를 명시적으로 선언해줘야한다.
코틀린의 클래스는 기본적으로 final 클래스/메서드이기에 클래스 상속을 하고 싶다면 open이라는 접근어를 설정해줘야한다. 물론 interface 를 통해 클래스간 상속을 안하더라도 충분히 기능구현을 할 수 있다. 필요하다면 open 으로 이용해 jpa 와 같이 기존 spring 에서 편리하게 사용하던 상속클래스 방식을 채택할 수 도 있다.
gradle-wrapper
주로 gradle 의 버전을 관리하는 데 사용 되는 도구. 특정 gradle 버전이 설치되지 않은 환경에서도 프로젝트를 빌드할 수 있게 된다. 이는 웹팩을 사용했을 때 .nvmrc 을 사용하여 node 버전을 제어하는 것과 비슷한 개념이다.
subprojects
하위 프로젝트에 공통으로 적용되는 설정을 정의할 수 있다. 필요하다면 project(':subproject name') 을 이용해 개별 프로젝트의 종속성을 컨트롤 할 수 도 있다.
gradle.properties
환경변수를 관리하는 파일이다. .env 와 동일하다고 봐도 좋다.
WAR, bundle.js
war 플러그인은 gradle 에서 web application archive(WAR) 를 빌드하기 위한 플러그인이다. WAR는 java 웹 어플리케이션 프로젝트를 빌드하고 WAR파일을 생성하는 데 필요한 태스크들을 추가할 수 있다. 해당 플러그인을 통해 어플리케이션에 필요한 디렉토리 구조, 라이브러리 종속성, 설정 파일 등을 정의하고 최종적으로 WAR 파일을 생성할 수 있다.
war 가 노드 클라이언트의 bundle.js 또는 build 파일이라 볼 수 있다. war는 단순히 어플리케이션의 모든 것을 담는다.
- 컴파일된 클래스 파일
- 정적 리소스
- 라이브러리 종속성
- 웹 설정 파일
실 배포 환경에서 war 파일은 어플리케이션을 실행하는데 필요한 데이터를 내재하고 있지만, 실제 프로젝트 단위로 실행을 하기 위해선 build.gradle 과 같은 프로젝트 규모의 파일을 추가적으로 배포해줘야한다.
war 파일과 bundle.js 은 흡사하다 (사실 JAR 가 bundle.js에 더 가깝다):
- 각각 서버 / 클라이언트에서 실행 되며 서블릿 컨테이너 / 브라우저에서 동작한다.
- 소스코드, 컴파일된 클래스(또는 모듈), 정적 리소스 (css,html,image 등), 라이브러리 종속성 등 서버 측 실행을 위한 모든 것을 포함한다. bundle.js 는 라이브러리 종속성 보단, 특정 라이브러리에서 import 해온 코드가 담겨져 있다.
프로젝트 측면에선 사실상 앞서말한 war(웹 어플리케이션 아카이브) 보단 JAR 파일이 훨씬 bundle.js 에 더 근접하다.
kapt
코틀린에서 Annotation 처리를 위해서 KAPT(Kotlin Annotation Processing Tool) 을 제공한다.
Kotlin 은 kotlinc 로 컴파일 되기 때문에 기존에 Java로 작성된 Annotation Process 로는 Kotlin의 Annotation이 제대로 처리되지 않는다.
plugins {
kotlin("kapt") version "1.5.30"
}
// 또는
apply plugin: 'kotlin-kapt'
// 실사용
dependencies {
// annotationProcessor -> kapt
kapt ("com.google...")
}
kapt 사용 가능한 library 인지 한번 체크해줘야한다.
dependencyManagement
이는 부모 프로젝트에서 의존성의 버전을 중앙 통제 및 관리를 하기위한 설정이다. 해당 섹션에 의존성을 넣어두면, 자식 프로젝트에서 dependency 를 통해 필요한 종속성을 설정해줄 수 있다.
부모 dependencyManagement 에서 denpendency 명령어로 종속성 넣어주면, 자식 dependencies (또는 dependency) 에서 implementation 으로 종속성을 가져와 사용할 수 있다.
이는 node 환경으로 비유하자면 node_modules 가 각 자식 프로젝트마다 존재하게 되는 멀티 모듈 시스템이라는 뜻이다. 그렇다면 부모 프로젝트에서 해줘야 하는 것은 종속성 중앙화 밖에 없다.
더 나아가 멀티프로젝트를 수행한다면, application, common, core, batch 등 중요한 기능들을 프로젝트 단위로 쪼개어 모듈들을 관리할 수도 있다. 각 프로젝트에는 필요한 종속성만 가져와 프로젝트를 보다 가볍게 사용할 수 있다. (의존성 분리)
dependencies 의 api / implementation
api project 는 해당 모듈의 의존성을 가져온다. 해당 모듈에 선언된 의존성을 설정해주고 그대로 실행할 수 있도록 한다. 하지만 부트서버는 하위 의존성이 필요없기에 구현된 소스만 가져오면 되어 implementation 으로 설정해준다.
주의할 점은, 멀티 모듈은 잘못 설정했을 경우 순환참조 현상이 일어나 서버에 배포할 수 없는 상황이 일어날 수 있다. 의존성 설계는 항상 문제 없도록 주의하자.
한번만 관계를 저 정립해보자면, api project (:batch) 를 하면 해당 batch 프로젝트의 종속성을 export 하는 행위이며 (api 처럼 지원하겠다), 부트서버 (웹이랑 상호작용하는 프로젝트) 에선 일반적으로 모든 프로젝트의 기능을 사용하기에 implemetation(각 프로젝트의 종속성을 import)해주면 된다.
다시한번 강조하지만 그래들의 종속성 관리는 상속관계가 아니다. 예를들어 application, batch, core, common 라는 최상위 프로젝트가 3개있다고 치자. 그리고 application 하위 모듈 api가 있다. 또한 api 모듈은 redis라는 하위 모듈을 갖고 있다.
application(1뎁스) > api(2뎁스) > redis(3뎁스)
잘못코드를 작성해보자:
// redis - build.gradle.kts
dependencies {
api project(':core')
api project(':common')
implementation('etc...')
}
왜 잘못된 코드일까? 3레벨의 모듈은 상위 모듈을 api 로 호출할 수 없기 때문이다.
정확한 코드는 아래와 같다:
// redis - build.gradle.kts
dependencies {
implementation(':core')
implementation(':common')
implementation('etc...')
}
마찬가지로 아래의 코드를 들여다보자:
// application - build.gradle.kts
dependencies {
implementation(':core')
implementation(':common')
implementation(':api')
implementation('etc...')
}
// api - build.gradle.kts
dependencies {
api project(':api:redis')
implementation('etc...')
}
// redis - build.gradle.kts
dependencies {
implementation('etc...')
}
의문인 점이 있을 것이다. 왜 application 에서 분명 api(redis의 부모)를 implementation을 해줬는데도 별개로 redis를 implementation 해줘야할까?
종속성을 상속하지 않기 때문이다. 즉 해당 프로젝트의(예를들어 api) implementation 되어있는 종속성만 implementation으로 불러오고 더 하위 모듈 (예를들어 redis) 은 완전 별개의 종속성으로 취급하기 때문이다.
이런 비상속 관계를 잘 이해하고 멀티모듈을 사용한다면 의존성 관리를 훨씬 깔끔하게 할 수 있다고 생각한다.
testImplementation
gradle 에서 테스트 코드를 작성할 때 필요한 의존성을 관리하는데 사용되며, 이는 node 에서 사용되는 devDependencies 와 유사한 역할을 한다. 이는 실제 런타임에서는 사용되지 않는 종속성이기에, 개발 중 필요한 라이브러리들을 등록해주면 좋을 것 같다.
repositories plugins 에서의 id / apply
다음과 같은 코드를 많이 볼 수있다.
apply 'org.example.plugin' version '1.0.0'
// 또는
plugins {
id 'org.example.plugin' version '1.0.0'
}
여기서 id 는 특정 플러그인의 고유한 식별자를 뜻한다. package.json 에서는 아래와 같은 명령어를 수행한 것과 동일하다:
npm install org/example/plugin
// 또는
dependency {
plugin: 'org/example/plugin'
}
npm install
정리해보자면, plugins 내부에 id 로 라이브러리를 명시해주면 추후에 자동으로 종속성을 프로젝트에 적용해준다. 여기서 apply 는 명시적으로 특정 플러그인을 설치하는 행위이다.
plugins / dependencies
plugins - 주로 gradle 빌드 스크립트 내에서 사용되는 특별한 종류의 확장 기능이나 빌드 설정을 제공하는데 사용된다. 예를들어 Java 플러그인은 Java 프로젝트의 빌드를 지원하고, Kotlin 플러그인은 Kotlin 언어를 사용하는 프로젝트를 지원한다. 일반적으로 plugins 는 프로젝트의 빌드 도구 및 환경 설정과 관련된 부분을 다루는 데 사용된다.
dependencies - 주로 프로젝트가 실행 중에 필요로 하는 외부 라이브러리를 지정하는 데 사용된다. dependencies 는 프로젝트가 실행될 때 classpath에 라이브러리를 추가하는 역할을 한다.
이 둘을 Node 환경에서 비유를 하자면 plugins 는 devDependencies에 더 가깝고, dependencies 는 용어 그대로 두 환경에서 비슷한 위치에 존재한다.
그럼 여기서 testImplementation 을 지나칠 수 없는데, 이 또한 package.json 에서의 devDependencies 와 흡사하기 때문이다. 하지만 testImplementation은 순전히 테스트를 목적으로 라이브러리를 관리하는 것이고, node 의 devDependencies 는 테스트나 다른 개발 목적으로 사용된다는 점이다.
관계를 정리해보자면:
// 좌 gradle 우 package.json
testImplementation + plugins ~= devDependencies
dependencies ~= dependencies
'BE' 카테고리의 다른 글
Backend Developer Roadmap (0) | 2022.01.15 |
---|