본문 바로가기
공부/프로그래밍

[gradle, springboot] multi project 설정하기

by demonic_ 2021. 3. 17.
반응형

프로젝트가 늘다보니 공통으로 쓰이는 것이 생겼고, 그러다보니 모듈을 통합+재활용 용이하게 하는 방법을 찾던 중 gradle을 이용한 multi project 설정이 좋겠다 싶어 선택했다. 다만 이건 설정뿐만 아니라 이후 유지보수하는데도 꽤 애를 먹는 부분이 있는데 때문에 팀원간의 내용공유가 잘 될수록 좋다.

 

여기서는 진행을 Intellij 로 했다. 그럼 시작해보자.

 

 

우선 Root역할을 할 프로젝트를 생성한다. 생성할때는 gradle 프로젝트로 할 것이며 이름은 blog-systems 라고 지을 것이다. 자바 버전은 11로 한다

프로젝트 이름과 폴더명은 맞추는게 좋다.

Finish를 누르면 다음과 같이 아주 간단한 프로젝트가 생성된다.

settings.gradle 에 설정한 이름이 rootProject.name 으로 잘 들어가있는지 확인한다. 그리고 build.gradle 안의 내용은 모두 삭제한다.

여기에 src 폴더가 자동 생성되었는데 사실상 사용하는 폴더는 아니다. 지워두도록 하자.

 

build.gradle에는 크게 3가지 분류로 나뉘어있다.

1) buildscript

2) allprojects

3) subprojects

 

간략하게 샘플로 만들어보자면 다음과 같다.

buildscript {
    ext {
        springBootVersion = '2.3.9.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
        classpath "io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE"
    }
}

allprojects {

}

subprojects {
    repositories {
        mavenCentral()
    }

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

    sourceCompatibility = 11

    dependencies {
        compileOnly 'org.projectlombok:lombok:1.18.16'
        annotationProcessor 'org.projectlombok:lombok:1.18.16'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testImplementation('org.springframework.boot:spring-boot-starter-test') {
            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
        }
    }
}

 

상황에 따라 적당히 분배하면 될 것이지만 각 설정의 설명을 간략하게 하자면,

buildscript 는 gradle 빌드 스크립트 자체를 위한 의존성이나 변수 Task, Plugin 을 지정할 수 있다.

allprojects 는 모든 프로젝트에 공통으로 넣을때 사용할 수 있다.

subprojects 는 allprojects 와 비슷한 역할을 하지만 차이점은 allprojects의 경우root project 마저 같이 설정된다. 즉 java로 구성하면 root project 역시도 그렇게 되는 것. 그래서 일반적으로 root project는 폴더명처럼 하고 subprojects에 주로 설정한다.

 

 

 

이제 다음 프로젝트를 생성한다. 이번에는 blog-common 이라고 지을 것이고 이 프로젝트에는 dependency를 최대한 줄이고 자주 사용하는 것들을 모아둔다. 내 경우 프로젝트별로 똑같은 Exception을 만드는 경우가 있는데 그걸 여기다 통합할 예정이다.

 

이번에도 gradle로 프로젝트를 만든다. 위와 달리 부모프로젝트가 지정되어 있고 Location 위치도 blog-systems 안에 들어가도록 한다.

이 프로젝트는 bootJar가 아닌 jar파일로 생성되고 다른 프로젝트에 첨부될 것이다. 때문에 build.gradle 을 다음과 같이 설정한다.

 

blog-common 내 build.gradle 파일

bootJar { enabled = false}
jar {enabled = true}

dependencies {
}

 

생성 후 gradle을 갱신하면 다음처럼 폴더가 생성된다.

그리고 subprojects 에서 설정한 것들이 dependencies에 들어가 있음을 확인할 수 있다.

 

그리고 root project 내 settings.gradle 내에 다음처럼 추가되어 있어야 한다.

(자동으로 추가되지만 한번 더 확인한다)

 

그럼 common 에다 종류별 Exception을 생성해보겠다.

 

멀티모듈을 사용할거라면 기본패키지를 생각하고 만들어야 하는데 여기서는 kr.personal.blog 라는 공통된 패키지로 지정한다. 다음처럼 만들었다.

 

이제 API 서버를 만들어보자.

Spring Initializer로 만드는 것보다 gradle로 만든 후에 dependency에 추가하는게 더 편하다. (불필요한 것들이 많이 따라오기 때문.)

생성하고 나면 아래와 같은 구성으로 된다.

똑같이 build.gradle 안 내용을 삭제하고, 내용을 다음처럼 추가한다.

bootJar {
    archiveFileName = 'blog-api-server.jar'
}

dependencies {
    implementation project(path: ":blog-common", configuration: 'default')

    compile('org.springframework.boot:spring-boot-starter-web')
}

 

bootJar 안의 내용은 필드한 후 파일명을 지정하는 것이다.

그리고 dependencies 안을 보면 blog-common 를 참조했다. (이름 앞에 : 을 붙여야 한다.)

 

이번 내용을 벗어난 거지만 혹시나 해서 추가하는 것인데 만약 프로젝트 중에 Querydsl 이 포함되어있다면 Spring boot 2.3.x 버전 부터는 다음에러가 발생할 수 있다.

compile project 에 configuration: default 를 추가하면 해결된다.(위 설정)

Could not determine the dependencies of task ':blog-api-server:compileQuerydsl'.
> Could not resolve all task dependencies for configuration ':blog-api-server:querydsl'.
   > Could not resolve project :blog-common.
     Required by:
         project :blog-api-server
      > Cannot choose between the following variants of project :blog-common:
          - productionRuntimeClasspath
          - runtimeElements
        All of them match the consumer attributes:
          - Variant 'productionRuntimeClasspath' capability blog-systems:blog-common:unspecified:
              - Unmatched attributes:
                  - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                  - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
                  - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
          - Variant 'runtimeElements' capability blog-systems:blog-common:unspecified:
              - Unmatched attributes:
                  - Provides org.gradle.category 'library' but the consumer didn't ask for it
                  - Provides org.gradle.dependency.bundling 'external' but the consumer didn't ask for it
                  - Provides org.gradle.jvm.version '11' but the consumer didn't ask for it
                  - Provides org.gradle.libraryelements 'jar' but the consumer didn't ask for it
                  - Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it

그게 아니라면 다음처럼 설정해도 무방.

dependencies {
    implementation project(path: ":blog-common")

    compile('org.springframework.boot:spring-boot-starter-web')
}

 

 

 

 

 

그럼 이제 SpringBootApplication 과 SampleController 를 만들어보자.

패키지 경로는 동일하게 설정한다. => kr.personal.blog

추가로 api-server 이기 때문에 api_server 를 추가한다.

 

ApiServerApplication 클래스 파일 위치는 api_server 위에다 둔다. 멀티 모듈을 쓴다 하더라도 다른 모듈에서 Component로 관리해야 할 때가 있는데, Application.class 위치가 그것보다 아래 있다면 자동 빈생성을 하지 못한다.

ApiServerApplication 을 다음처럼 만든다.

package kr.personal.blog;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

컨트롤러를 추가한다.

package kr.personal.blog.api_server;

import kr.personal.blog.common.exception.BlogInvalidParameterException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @GetMapping("/api/sample")
    public String apiSample(@RequestParam(required = false) String testKey) {
        if(testKey.equals("test") == false) {
            throw new BlogInvalidParameterException("잘못된 파라미터 입니다: " + testKey);
        }

        return "call sample test api";
    }
}

 

코드를 보면 BlogInvalidParameterException 을 사용한 것을 확인할 수 있다. blog-common 안에 있는 것을 이처럼 끌어다 쓸 수 있다.

 

마지막으로 gradle을 통해 dependencies 를 확인해보자

 

blog-api-server 안 dependencies 안을 보면 project 로 blog-common 이 추가 된 것을 확읺ㄹ 수 있다.

 

실행하면 웹서버가 뜬다. 다음 호출로 확인 완료.

 

 

코드는 github에 올려두었다.

https://github.com/lemontia/gradle_multi_module

 

lemontia/gradle_multi_module

gradle multi module setting. Contribute to lemontia/gradle_multi_module development by creating an account on GitHub.

github.com

 

 

끝.

반응형

댓글