본문 바로가기

Programing/Framework

Kotlin DSL Gradle: 멀티 모듈로 변경하기

단일 모듈 기반의 프로젝트

spring initializr 에서 프로젝트를 만들면 기본적으로 단일 모듈 기반의 프로젝트를 생성한다.

이 구조는 단순하고 만들기 쉽지만 단점도 있다.

핵심 비즈니스 구현을 하는 도메인 영역과 세부사항의 일종인 구현 기술의 코드가 섞이게 되기 때문이다.

실무를 하면서 일종의 기술 영역인 JPA의 Entity 가 도메인 영역인 서비스를 통과해 Controller 까지 전달했던 것을 본 적이 있다.

 

물론 자바나 코틀린에서는 패키지를 구분하여 모듈을 분리할 수 있는 구조를 제공한다.

하지만 이것은 구조화적인 것으로 의존성에 대해 직접적인 통제를 할 수 없다.

페리페리크 안티 패턴(Périphérique anti-pattern)

헥사고날 아키텍처 - 혹은 '포트와 어댑터' - 라는 구조를 따르면 자연스럽게 멀티 모듈로 구성을 하게 된다. (아래 그림 2차 출처)


하지만 나는 페리페리크 안티 패턴(Périphérique anti-pattern)을 더 선호한다.

이유는 처음 프로젝트 코드를 보는 사람도 어디에 있다는 것을 직관적으로 판단할 수 있을 정도로 단순하기 때문이다.
또한 비즈니스에 대한 도메인에 대한 것은 안에 존재하고, 밖에서는 안의 도메인을 참조하는 구조를 갖추면 자연스레 도메인에 기술 요소가 붙는 것을 막을 수도 있다.

 

이 패턴과 관련해서는 로버트 C 마틴의 Clean Architecture에서 Simon Brown이 기고한 34장 빠져 있는 장에 소개하고 있다.

참고로 이 패턴의 이름은 파리의 Boulevard Périphérique 에서 따왔다.

 

Domain은 Infrastructure를 모른다!

 


지도를 찾아보니 A86이나 La Francilienne 와 같은 더 많은 외곽순환 도로가 경계(boundary)를 나누고 있다는 것을 알게 되었다.

프로젝트를 진행하다보면 또 자연스럽게 경계가 생기는 것을 느꼈다. inner/outer 에서 inner / infra / outer1, outer2 등으로 말이다.


멀티 모듈 구성하기

IntelliJ IDEA, Kotlin, Groovy 기준으로 기록해본다.

참고로 starter.spring.io 에서 뼈대를 만들었고 일부 코드가 있다고 가정한다.

- Project: Gradle Project

- Language: Kotlin (이 옵션을 생성하면 Gradle 이 Kotlin DSL로 생성한다. build.gradle 파일 대신 build.gradle.kt 파일이 생김)

 

기존 프로젝트 이름은 xxx-api 였다.

xxx-api 를 xxx-service 로 바꾸고, 내부에 xxx-core와 xxx-api 로 모듈을 구성하고 싶다. xxx-api는 xxx-core에 의존하게 설정한다.

 

New Module - api

단일 모듈로 되어 있는 프로젝트를 열고, File > Module... 을 선택한다.

시행착오를 겪으면서 배운 것인데, 일단 Kotlin DSL로 만들었기에 Kotlin DSL build script에 체크를 하고

디폴트로 체크되어 있는 Java는 체크 해제를 하고, Kotlin/JVM 으로 선택한다.

다음은 New Module 창

Name: 부분에 새로운 모듈 이름을 입력한다.

xxx-api 를 먼저 옮기고 xxx-core를 나중에 만들어서 분리할 생각이다.

 

이러고 나면 gradle 이 자동으로 재로딩을 하는데 경고에 에러에 프로젝트가 문제가 있다고 난리다.

당황하지 않고 프로젝트 구조를 바꾸어주어야 한다.

src 디렉토리 옮기기

프로젝트 바로 아래에 있는 src 디렉토리를 방금 만든 곳으로 이동을 한다.

mv 명령을 써도 되고 git으로 소스코드를 관리한다면 git mv 명령으로 옮긴다.

git mv src xxx-api

자식에 있는 build.gradle.kts 배너 우측에 Load Script Configurations 라고 뜨지만 이걸 누른다고 해결되지는 않는다.

Load Script Configurations

최상위 build.gradle.kts 수정

plugins

이것 저것 테스트를 해보니 모듈에서 새로운 플러그인 정의는 못하고 최상위 build.gradle.kts 에서 정의한 것에 대한 사용은 가능했다.

최상의 플러그인들을 false로 적용한다.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "2.5.0" apply false
	id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
	kotlin("jvm") version "1.4.32" apply false
	kotlin("plugin.spring") version "1.4.32" apply false
	groovy
}

뒤에 apply false 만 붙여주면 된다.

변경 전(왼쪽)과 후 (오른쪽)

나중에 안 것이지만 groovy 플러그인에는 apply false를 붙이면 에러가 발생했다!

allprojects 블록 추가

하위 모듈에 공통으로 적용할 값들을 설정한다.

group, version, tasks.withType, 등이 있다.

group은 같이 가져가고 version 은 필요에 따라 배포 단위별로 관리하는 것이 좋다. (마틴 밥이 외치지 않는가?!)

다만 현재는 배포 단위가 1개라 같이 가져가는 것으로 설정했다.

java.sourceCompatibility = JavaVersion.VERSION_11

allprojects {
	group = "com.tistory.namocom"
	version = "0.0.3-SNAPSHOT"

	tasks.withType<KotlinCompile> {
		kotlinOptions {
			freeCompilerArgs = listOf("-Xjsr305=strict")
			jvmTarget = "11"
		}
	}

	tasks.withType<Test> {
		useJUnitPlatform()
	}
}

나중에 알았지만 java.sourceCompatibility = JavaVersion.VERSION_11 부분이 allprojects 안에 있으면 동작을 하지 않았다.

 

subprojects 블록 추가

하위 모듈들에 대한 공통적인 설정을 준다.

리포지토리와 스프링 의존성 관리에 대해 해준다.

subprojects {
    repositories {
        mavenCentral()
    }
}

모듈에 있는 build.gradle.kt 의 아래 부분은 없어져도 된다.

repositories {
    mavenCentral()
}

dependencies 이동

새로 만든 모듈로 이동을 한다.

만약 gradle.properties 에 있는 값이 있다면 같이 이동 혹은 복사를 해주어야 한다.

 

이 상태가 되어도 아직 빨갛게 보였다.

 

의외로 groovy 플러그인의 false 적용에 문제가 있었나보다.

 ./gradlew task
Starting a Gradle Daemon, 1 incompatible and 7 stopped Daemons could not be reused, use --status for details

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/namo/git/xxx-api/build.gradle.kts' line: 9

* What went wrong:
Error resolving plugin [id: 'org.gradle.groovy', apply: false]
> Plugin 'org.gradle.groovy' is a core Gradle plugin, which is already on the classpath. Requesting it with the 'apply false' option is a no-op.

 

New Module - core

api 모듈처럼 새로운 모듈을 만든다.

plugins 의 버전이 붙어있는 부분은 지운다.

kotlin("jvm") version "1.5.10"

kotlin("jvm")

아래 부분은 지운다.

group = "com.tistory.namocom"
version = "0.0.3-SNAPSHOT"

repositories {
    mavenCentral()
}

 

의존성을 추가한다.

의존을 할 build.gradle.kt 에 implmentation 을 추가한다.

dependencies {
    implementation(project(":xxx-core"))

이후 core 부분의 코드를 옮긴다.

나는 모듈을 만드는 작업전에 package를 core로 분리해 놓았다.

 

buildJar 부분은 false로 바꾼다. (core는 부트 프로젝트가 아니라 N/A)

- Spring Boot Gradle plugin: bootJar.enabled=false not working

 

참고 사이트:

https://kotlindays.com/2019/12/06/multi-module-spring-boot-in-kotlin-dsl/index.html

 

How to Create a Multi-Module Spring Boot Project using Gradle’s Kotlin DSL

An example of using the Gradle Kotlin DSL for a multi-module Spring project

kotlindays.com