아무거나

Gradle 멀티 프로젝트 구성 본문

Java & Kotlin/Gradle & Maven

Gradle 멀티 프로젝트 구성

전봉근 2018. 9. 19. 16:10
반응형
보통 프로젝트는 클라이언트(=사용자)에서 접근하는 서버, DB와의 접근하는 서버 등. 각 모듈별로 구분하여 구성하게 된다. 이럴 때 예를 들어 회원관련 클래스가 있다고하자. 그 클래스는 서로 다른 모듈에서 공통으로 쓰고있다고하면 수정이 있을때마다 각각 변경을 해줘야되며 그로인하여 실수할 여지가 많아진다. 이런 번거로움을 조금이라도 덜어내기 위하여 멀티 프로젝트를 구성하고자 한다. 

구성은 아래와 같다.
admin-web : 웹 페이지 서버
admin-api : api 서버
admin-common : 공통 클래스 모듈




[ IntelliJ ]

1. File -> New -> New Project -> Gradle 선택 -> JAVA 선택 후 Next 
   -> 필요정보 입력 후 Next 
        # groupId: com.bkjeon.admin
        # artifactId: bkjeon-admin
        # version: 0.0.0
   -> Use auto-import check -> Finish

    



2. build.gradle과 settings.gradle을 작성하자


    [build.gradle]

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

}
}

subprojects {

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

group = 'com.bkjeon.admin'
version = '0.0.0'
sourceCompatibility = 1.8

repositories {
mavenCentral()
}


task initSourceFolders {
sourceSets*.java.srcDirs*.each {
if( !it.exists() ) {
it.mkdirs()
}
}

sourceSets*.resources.srcDirs*.each {
if( !it.exists() ) {
it.mkdirs()
}
}
}

dependencies {
compileOnly('org.projectlombok:lombok')
}
}

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

project(':admin-web') {
dependencies {
compile project(':admin-common')
}
}

- 프로젝트 모듈마다 대부분 lombok을 사용한다는 가정하에 공통 gradle파일에 의존성을 추가하였다.

- task initSourceFolders { ... } 를 작성하여 해당 프로젝트 빌드시에 각각 프로젝트 하위모듈을 자동으로 생성하게끔 문법작성 



    [settings.gradle]

rootProject.name = 'bkjeon-admin'
include 'admin-common', 'admin-api', 'admin-web'


위의 내용을 작성하면 자동적으로 settings.gradle에 include에 추가된 이름들이 실제로 프로젝트 모듈로 추가된다.


3. admin-web 같은 경우는 템플릿엔진과 연동시에 생성된 모듈을 삭제하고 직접 프로젝트를 생성한다. ( ide를 사용한 템플릿엔진이 연동된 프로젝트로 생성하지않으면 직접 연동설정을 해줘야하는 번거로움이 있다. )









4. 프로젝트별 gradle파일을 작성하자.




    [admin-api]

// const
def swaggerVersion = '2.8.0'

dependencies {
compile project(':admin-common')

compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')

compile group: 'io.springfox', name: 'springfox-swagger2', version: swaggerVersion
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: swaggerVersion
}


    [admin-common]

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

dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime('com.h2database:h2')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')

// https://mvnrepository.com/artifact/mysql/mysql-connector-java
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.47'
}

- 위의 bootJar { ... }, jar { ... } 옵션은 스프링부트 2.0이상 일 때 bootRepackage { enabled = false } 대신에 작성하여야한다.

  ( bootRepackage의 태스크를 실행할 경우 자동적으로 압출파릴을 재작성한다. 즉 프로젝트를 jar혹은 war로 빌드할지를 설정 혹은 선언할 수 있다. )


    [admin-web]

dependencies {
compile project(':admin-common')

compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

  

5. 각각의 모듈마다 메인 메소드를 작성하자.




    [admin-api]  

package com.bkjeon.admin;

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

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


    [admin-common]

package com.bkjeon.admin;

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

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


    [admin-web]

package com.bkjeon.admin;

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

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



6. 테스트할 코드를 작성하자. (테스트는 admin-api에서 진행한다.) 구조는 아래와 같습니다.



# admin-common

[Sample.java]

package com.bkjeon.admin.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "sample")
public class Sample {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "sample_id", nullable = false, length = 11)
private Long sampleId;

@Column(name = "name", nullable = true, length = 30)
private String name;

}


[SampleRepository.java]

package com.bkjeon.admin.repository;

import com.bkjeon.admin.entity.Sample;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SampleRepository extends JpaRepository<Sample, Long> {

}


[SampleService.java]

package com.bkjeon.admin.service;

import com.bkjeon.admin.entity.Sample;
import com.bkjeon.admin.repository.SampleRepository;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class SampleService {

@Autowired
private SampleRepository sampleRepository;

@Transactional(readOnly = true)
public List<Sample> getSamples() {
return sampleRepository.findAll();
}

@Transactional(readOnly = true)
public Sample getSample(Long sampleId) {
return sampleRepository.findById(sampleId).get();
}

@Transactional
public Sample setSample(Sample command) {
return sampleRepository.save(command);
}

@Transactional
public Sample putSample(Long sampleId, Sample sample) {
sample = sampleRepository.findById(sampleId).get();
return sampleRepository.saveAndFlush(sample);
}

@Transactional
public void delSample(Long sampleId) {
sampleRepository.deleteById(sampleId);
}

}


# admin-api

[TestController.java]

package com.bkjeon.admin.api.controller;

import com.bkjeon.admin.entity.Sample;
import com.bkjeon.admin.api.service.ApiSampleService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("test")
public class TestController {

@Autowired
private ApiSampleService apiSampleService;

@GetMapping
public List<Sample> getSamples() {
return apiSampleService.getSamples();
}

@GetMapping("{sampleId}")
public Sample getSample(
@PathVariable Long sampleId
) {
return apiSampleService.getSample(sampleId);
}

@PostMapping
public Sample setSample(
@RequestBody Sample command
) {
return apiSampleService.setSample(command);
}

@PutMapping("{sampleId}")
public Sample putSample(
@PathVariable Long sampleId,
@RequestBody Sample command
) {
return apiSampleService.putSample(sampleId, command);
}

@DeleteMapping("{sampleId}")
public void delSample(
@PathVariable Long sampleId
) {
apiSampleService.delSample(sampleId);
}

}


[ApiSampleService.java]

package com.bkjeon.admin.api.service;

import com.bkjeon.admin.entity.Sample;
import com.bkjeon.admin.service.SampleService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ApiSampleService {

@Autowired
private SampleService sampleService;

public List<Sample> getSamples() {
return sampleService.getSamples();
}

public Sample getSample(Long sampleId) {
return sampleService.getSample(sampleId);
}

public Sample setSample(Sample command) {
return sampleService.setSample(command);
}

public Sample putSample(Long sampleId, Sample command) {
Sample sample = sampleService.getSample(sampleId);
sample.setName(command.getName());

return sampleService.putSample(sampleId, sample);
}

public void delSample(Long sampleId) {
sampleService.delSample(sampleId);
}

}


* admin-api와 admin-common 에 service가 둘다 있어서 혼동이 될 것이다. admin-api에 있는 서비스는 비즈니스 로직이 작성될 클래스이고 admin-common에 있는 서비스는 원하는 repository를 연동할때의 서비스이다.


7. 실행할 모듈의 application.yml을 작성하자. ( 실행하게될 모듈은 admin-api이므로 이것만 작성하자. )




   [admin-api]

spring:
profiles: local
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto : update

server:
port: 8080


8. 마지막으로 common모듈을 사용할 다른 프로젝트 빌드시에 common.jar가 제대로 같이 포함되는지 확인해보자.




# 빌드가 되면 해당 프로젝트 루트에 /build/libs/xx.jar 로 파일이 생성된다 해당 경로에서 jar파일의 압축을 풀어보자 ( admin-api-0.0.0.jar 를 예제로 압축을 풀자)

   ex)  jar xvf {path}/admin-api-0.0.0.jar

   -> 압축푼 파일중에 /BOOT-INF/lib/.. 에서 admin-common.jar가 있는지 확인하자.



9. 이제 실행을하면 정상 동작하는것을 확인할 수 있다.



참고소스 : https://github.com/bkjeon1614/java-example-code/tree/master/sample-multi-module

반응형
Comments