Gradle을 이용한 멀티 모듈

강상우·2021년 6월 30일
9

Spring

목록 보기
1/3

안녕하세요, 이번 포스팅에서는 Gradle을 이용하여 멀티 모듈을 사용하는 부분에 대해 다루려고 합니다.
틀린 부분이 있다면, 아래 댓글을 통해 알려주시면 감사드리겠습니다.
코드는 이 곳에 있으니 참고해주세요.

개요 - 왜 멀티 모듈?

먼저 멀티 모듈에 대해 알게 되고, 공부하게 된 계기를 말하며 포스팅을 시작하려합니다.
현재 진행하고 있는 축구 매칭과 관련된 사이드 프로젝트가 있습니다. 이전에도 진행을 했었지만, 개인적인 일로 못하고 있다가 새롭게 다시 시작을 하게 된지 몇 달이 흘렀습니다.

이전에 이 프로젝트를 진행하며 별다른 고민없이 넘어간 부분이 있었는데, 그 부분은 바로 겹치는 클래스들이었습니다.

랭킹을 매기는 집계 혹은 경기 전, 경기 중, 경기 후 등의 데이터 변경을 위해서는 api 서버 외에 간단한 배치 서버 프로젝트를 만들어서 추가로 두었습니다.
즉, 독립적인 프로젝트를 만들었습니다.

이때 데이터베이스에 접근하기 위해 api 서버에도 Member 엔티티가 필요하였고, 배치 서버에도 Member 엔티티가 필요하였습니다. 그 당시에는 똑같은 모델을 그대로 사용하며 만들기에 급급했던 것 같았습니다.

하지만, 이번에 다시 프로젝트를 진행하며 이 부분에 대한 해결책이 분명히 있을 것 같다는 생각을 하였고, gradle을 이용한 멀티모듈 방식이 있다는 것을 알게되었습니다.

멀티 모듈이란?

위와 같은 방식으로 개발을 할 경우에는, 각각이 독립된 프로젝트 단위이기 때문에, 어떤 도메인을 공유할 수 없는 문제가 있습니다. 이 결과, 똑같은 도메인을 각각의 프로젝트에 두게 됩니다.

멀티 모듈은 독립적인 프로젝트를 하나의 프로젝트 안의 모듈로서 가지고 있을 수 있는 구조를 제공하게 됩니다.

그림에 정확하게 표현하지는 않았지만, common module을 통해서 공통적으로 사용되는 도메인 및 공통 코드들을 넣고, 다른 모듈들에서 이를 사용하면 되는 방식으로 개발을 할 수 있게 되었습니다.

아직은 공부 단계에 있어, 이런 구조로 만든 것도 장족의 발전이라고 생각했지만, common-module의 몸집이 커지는 부분에 대한 고민도 추후 필요할 것 같습니다.
이에 대한 참고 링크입니다.

위의 구조는 제가 간단하게 만든 구조의 멀티 모듈 프로젝트입니다.
root 프로젝트인 multi-modules-template 프로젝트와 하위 프로젝트(모듈)인 module-api , module-common , module-batch 가 있습니다.

이를 만드는 방법에 대해 이제부터 다뤄보도록 하겠습니다.

멀티 모듈 만들기

새로운 프로젝트 생성

저는 스프링 부트와 gradle 6버전을 이용하여 프로젝트를 만들었습니다. gradle 6버전을 사용한 이유에 대해서도 이후 다시 한번 다뤄볼 예정입니다.
새로운 프로젝트를 Spring Initializer 를 이용하여 생성하였습니다.
만든 이 프로젝트는 멀티 모듈 프로젝트의 root가 될겁니다.

신규 모듈 생성

  • 인텔리제이 기준으로 루트 프로젝트에서 New - Module...을 선택하여 모듈을 생성합니다.
  • 모듈은 Gradle 모듈로 생성합니다.

  • 기존 root 프로젝트에 있던 src 폴더는 삭제시켜줍니다.
  • 각 모듈에는 build.gradle과 src 폴더만 존재하게 됩니다.

  • 모듈이 생성되면 settings.gradle에 위와 같이 모듈이 추가된 것을 알 수 있습니다.(만약 추가가 되지 않았다면, 직접 작성해주시면 됩니다.)

각 모듈에 필요한 의존성 추가

module-common

  • module-common은 간단하게 Match Entity 클래스와 이 Entity의 repository를 작성하려 합니다.
  • 이에 대한 의존성을 module-common의 build.gradle에 추가해주도록 하겠습니다.
dependencies {
    compile 'mysql:mysql-connector-java'
    compile 'org.springframework.boot:spring-boot-starter-data-jpa'
}

bootJar { enabled = false }

jar { enabled = true }
  • common 모듈은 실행 가능한 boot jar로는 패키징할 필요가 없어서, 위와 같이 bootJar는 false로, jar는 true로 작성해줍니다.

module-api

  • modul-api는 api 서버 역할을 하기 때문에 web과 관련된 의존성을 추가해줍니다.
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

root 프로젝트의 build.gradle

  • 이제 각각의 모듈에 필요한 의존성을 각각 추가해주었고, 루트 프로젝트의 build.gradle을 작성해주어야 합니다.
buildscript {
    ext {
        springBootVersion = '2.5.1'
        dependencyManagementVersion = '1.0.11.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        dependencies {
            classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
            classpath "io.spring.gradle:dependency-management-plugin:${dependencyManagementVersion}"
        }
    }
}


subprojects {
    group 'io.hala'
    version '1.0'

    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    sourceCompatibility = 11

    repositories {
        mavenCentral()
    }

    dependencies {
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
        implementation 'org.springframework.boot:spring-boot-starter-test'
    }
}


project(':module-api') {
    dependencies {
        compile project(':module-common')
    }
}

project(':module-batch') {
    dependencies {
        compile project(':module-common')
    }
}

여기서는 subprojects와 project() 코드가 중요합니다.
subprojectssettings.gradle에 include된 프로젝트를 전부 관리하는 공간입니다.
이때 만약, root 프로젝트까지 적용을 하고 싶을 때는 subprojects 대신 allprojects로 등록하면 됩니다.

project()는 하위 프로젝트의 의존성을 관리하기 때문에, module-apimodule-batchmodule-common을 추가해주었습니다.

참고로 :는 디렉토리 path를 의미합니다. 이렇게 되면 module-apimodule-batch에서 module-common을 사용할 수 있게 됩니다. 자세한 코드는 위에 첨부한 저장소를 참고해주시면 될 것 같습니다.

Gradle6을 사용한 이유

처음 제가 멀티 모듈 프로젝트를 만들때는 Gradle 7버전을 사용하였습니다. 그런데 7버전에서 compile 키워드를 사용하니 아래와 같은 에러가 발생했습니다.

Could not find method compile() for arguments Gradle

이전에도 compile 키워드가 deprecated 된다고 경고문을 보았었지만, Gradle 7에서부터는 완전히 사라졌다고 합니다. 아래는 스택오버플로우에서 가져온 글입니다.

이런 이유로 implementation을 사용하여 개발을 하였지만, implementationcompile키워드와 다르게 직접 의존하고 있는 모듈외에는 api가 노출되지 않습니다.
즉, module-api에서 module-common은 노출되지만, jpa 의존성은 module-api에 노출되지 않아 save(), findById()등의 메소드를 사용하지 못하는 상황이었습니다.

이를 해결하기 위해 위에서 작성했던 subprojects 부분에 jpa 의존성을 추가해주었지만, 이런 상황이 지속된다면 결국 모듈화를 한 의미가 없어질 것 같다는 생각이 들었습니다.

현재는 gradle 6로 버전을 낮추어, compile을 이용해 코드를 작성하였지만, compile 키워드는 완전히 deprecated되었기 때문에, 7에서도 동작할 수 있는 코드를 고민해본 후에 추후 이에 대한 내용을 업로드 하려 합니다!

Gradle6을 사용한 이유.. 그 후

위와 같은 내용을 작성한 후에, 지인으로부터 Gradle7에서 compile을 대체하는 api를 사용해보는 것은 어떻겠냐라는 조언을 받게 되었습니다.

Gradle7으로 다시 버전을 올리고 api를 통해 작성한 결과 정상적으로 동작하는 것을 알 수 있었습니다.

제 깃허브에 있는 코드 역시 api로 수정되어있으므로 참고해주시기 바랍니다 😃

참고

profile
https://sangwoo0727.github.io/ 기존 블로그 이전 중입니다.

1개의 댓글

comment-user-thumbnail
2023년 1월 3일

apply plugin: 'java-library' //없으면 api() 오류 나와요 ㅠ

답글 달기