본문 바로가기

Gradle

Gradle / 진행중인 프로젝트에서 부분적으로 멀티 모듈로 바꾸기

👨‍👨‍👧‍👧 멀티 모듈?

멀티 모듈이란 개념은 한 프로젝트 안에 라이브러리처럼 사용 가능한 상태로 모듈들을 구성하고 그것들을 다른 모듈에서 가져다 쓸 수 있도록 만드는 것이라고 이해할 수 있습니다.

멀티 모듈을 구글에 검색하면 참고할 만한 문서들이 정말 많이 나옵니다.

멀티 모듈 프로젝트를 구성하는 방법 또한 친절하게 자세히 나와있어 쉽게 멀티 모듈 프로젝트를 구성할 수 있습니다.

 

멀티 모듈에 관한 참고 글

- Gradle 멀티 프로젝트 관리

- 멀티모듈 설계 이야기 with Spring, Gradle

 

진행중이던 프로젝트를 멀티 모듈로 바꾸기를 원하는 분들도 많을 것입니다

하지만, gradle이나 멀티 모듈에 익숙하지 않은 분들이라면 고민하게 만드는 문제일 수 있죠.

이번 포스팅에서는 진행 중이던 프로젝트가 있다고 가정하고 해당 프로젝트를 멀티 모듈 프로젝트로 바꾸는 작업에 대해서 다뤄보겠습니다.

🖥 진행 중이던 프로젝트가 있어요

진행 중이던 프로젝트가 있다고 가정하기 위해 간단한 프로젝트 환경을 만들어 보았습니다.

작은 프로젝트이지만 누군가의 말대로 상상력을 발휘하여 domain 클래스도 많고 controller도 비해한 아주 큰 프로젝트라고 생각해주세요.

자세한 코드는 github에 있으니 참고 하시면 됩니다.

현재 진행중이던 프로젝트의 스펙은 다음과 같습니다.

lombok도 보이고 jpa도 보이네요. 역시나 이것뿐만 아니라 다른 여러가지 의존성이 많다고 상상해보시면 좋습니다.

build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.1.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.blog'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

🖥 📲📱domain을 모듈로 분리하기

현 프로젝트에서 대한 domain을 다른 모듈로 분리해 보겠습니다.

User 클래스는 다음과 같습니다.

User.java

@NoArgsConstructor
@Getter
@ToString
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String password;

    @Builder
    public User(Long id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
}

 

1. module 생성

module로 분리하기 위해서는 먼저 module을 만들어 주어야 하겠죠

root 프로젝트에서 module을 하나 만들어 주겠습니다.

 


다음과 같이 module-common이 생성되시면 성공입니다.

주목해야 할 부분은 settings.gradle 에 include 'module-common' 이 추가 되었다는 점입니다.

만약에 추가되지 않았다면 추가해주세요

2. module 로 코드 옮기기🚚

이제 module-common 으로 domain에 관련된 코드들을 옮기겠습니다.

 

프로젝트 현재 구조

 

build.gradle에서 jpa와 같은 domain에 관련된 코드들도 잊지 않고 옮겨줍니다.

root 프로젝트의 build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.1.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.blog'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

 

module-common의 build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot'
    id 'io.spring.dependency-management'
}

group 'com.blog'

//module-common 에는 실행가능한 Application을 가지고 있지 않으니 아래 내용을 포함시켜 줍니다.
bootJar { enabled = false }
jar { enabled = true }

repositories {
    mavenCentral()
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

하지만 지금 상태에서는 root 프로젝트에 속해 있는 코드들이 module-common에 있는 domain코드들을 사용하지 못합니다.

root프로젝트에서 module-common의 코드들을 사용하기 위해 root프로젝트의 build.gradle에 다음과 같이 추가해줍니다.

...
dependencies {
...
	//코드 추가
    compile project(':module-common')
}

...

그러면 에러가 사라지는 것을 확인할 수 있습니다.

3 Test 해보기🕹

그럼 이제 UserService를 테스트해봄으로써 root프로젝트에서 다른 모듈의 코드를 사용할 수 있는지 확인해보겠습니다.

UserService.java

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Long save(User user) {
        return userRepository.save(user).getId();
    }
}

UserServiceTest.java

@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    void save() {
        User user = User.builder()
                .name("홍길동")
                .password("1234")
                .build();

        Long id = userService.save(user);
        assertThat(id).isNotNull();
    }
}

Test 결과는 성공이네요

Service를 모듈로 분리하기

이제 위의 과정을 참고하여 Service도 한번 분리해보도록 하죠.

ModuleApiApplication을 선언해 주어야 테스트가 잘 돌아갑니다.

위에서 해왔던 방법과 동일하게 적용하여 위 이미지처럼 module-api를 만들었는데, build.gradle이 다른 build.gradle과 중복되는 부분이 참 많은 것 같습니다.

 

module-api의 build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot'
    id 'io.spring.dependency-management'
}

group 'com.blog'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }

    compile project(':module-common')
}

test {
    useJUnitPlatform()
}

 

module-common의 build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot'
    id 'io.spring.dependency-management'
}

group 'com.blog'

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

repositories {
    mavenCentral()
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

이번엔 중복되는 코드를 줄여보도록 하겠습니다.

중복제거🔍

모듈들의 build.gradle에서 중복되는 부분을 제거하는 방법은 간단합니다.

root프로젝트에 있는 build.gradle에 subprojects{ }를 만들고 중복되는 코드를 넣어주면 됩니다.

 

root프로젝트의 build.gradle

...

//subprojects는 include된 프로젝트들을 관리합니다.
subprojects {
    group 'com.blogcode'

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

    sourceCompatibility = 1.8

    repositories {
        mavenCentral()
    }

    dependencies {
        testImplementation('org.springframework.boot:spring-boot-starter-test') {
            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
        }
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    }

    test {
        useJUnitPlatform()
    }
}

//module-api 프로젝트가 module-common의 의존하고 있음
project(':module-api') {
    dependencies {
        compile project(':module-common')
    }
}

이렇게 하면 나머지 build.gradle은 많이 줄어들게 됩니다.

 

module-common의 build.gradle

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

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
}

module-api의 build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

 

어느덧 위에서 멀티모듈의 참고 글에서 안내하고 있는 코드와 비슷해지고 있네요.

이 글의 목적은 "진행중이던 프로젝트에서 모듈 분리를 어떻게 할 수 있을까"에 대한 글이기 때문에 어느 정도 목적을 이룬 것 같아 여기서 마무리하려고 합니다.

많이 부족한 글이지만 멀티모듈을 이해하는데 조금이나마 도움이 되었으면 좋겠습니다.

끗~

반응형